feat(users): medal auto-grant + aksrt_get_user_name chrome
Add AKSRT_User_Badges::auto_evaluate on login for get_type metrics, introduce aksrt_get_user_name (auth→wear medal→level→follow order), wire single author bar/card, and document transfer/withdraw default-closed semantics on capabilities page. audit errors=0.
This commit is contained in:
@@ -11,6 +11,12 @@
|
||||
</template>
|
||||
</PageHeader>
|
||||
|
||||
<n-alert type="warning" :show-icon="true" style="margin: 12px 0 16px;">
|
||||
转账/提现(balance_transfer、points_transfer、balance_withdraw)默认 <b>exclude all</b>,与 Zibll 一致:未在矩阵中为角色/等级/VIP/认证打开前,前台入口会隐藏或拒绝。
|
||||
medal_manually_set 控制谁可授予徽章;管理员 manage_options 始终可通过。
|
||||
</n-alert>
|
||||
|
||||
|
||||
<n-spin :show="loading" style="width: 100%; min-height: 240px;">
|
||||
<n-card class="aksrt-section-card" v-if="summary.cap_count">
|
||||
<div class="aksrt-section-header">
|
||||
|
||||
@@ -404,4 +404,136 @@ class AKSRT_User_Badges {
|
||||
'message' => __('已收回徽章', 'aksrt'),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Auto-grant medals whose get_type metrics meet get_val (skip manually_add).
|
||||
*/
|
||||
public static function auto_evaluate(int $user_id): void {
|
||||
if ($user_id <= 0 || !self::enabled() || !class_exists('AKSRT_Settings')) {
|
||||
return;
|
||||
}
|
||||
$settings = AKSRT_Settings::get_user_identity_settings();
|
||||
$medal = is_array($settings['medal'] ?? null) ? $settings['medal'] : [];
|
||||
$items = is_array($medal['items_by_slug'] ?? null) ? $medal['items_by_slug'] : [];
|
||||
if (!$items) {
|
||||
// Fall back to dictionary only — no get_type → nothing to auto-grant.
|
||||
return;
|
||||
}
|
||||
$owned = self::get_owned($user_id);
|
||||
$owned_map = array_fill_keys($owned, true);
|
||||
foreach ($items as $slug => $item) {
|
||||
if (!is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
$slug = sanitize_key((string) $slug);
|
||||
if ($slug === '' || !empty($owned_map[$slug])) {
|
||||
continue;
|
||||
}
|
||||
$type = sanitize_key((string) ($item['get_type'] ?? 'manually_add'));
|
||||
if ($type === '' || $type === 'manually_add') {
|
||||
continue;
|
||||
}
|
||||
$need = max(0, (float) ($item['get_val'] ?? 0));
|
||||
$have = (float) self::metric_value($user_id, $type);
|
||||
if ($have >= $need) {
|
||||
self::grant($user_id, $slug, 0);
|
||||
$owned_map[$slug] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Numeric metric for a medal get_type.
|
||||
*/
|
||||
public static function metric_value(int $user_id, string $type): float {
|
||||
switch ($type) {
|
||||
case 'registration_time':
|
||||
$user = get_userdata($user_id);
|
||||
if (!$user) {
|
||||
return 0.0;
|
||||
}
|
||||
$reg = get_date_from_gmt($user->user_registered, 'Y-m-d H:i:s');
|
||||
$now = current_time('Y-m-d H:i:s');
|
||||
return max(0.0, (strtotime($now) - strtotime($reg)) / 86400);
|
||||
|
||||
case 'checkin_all_day':
|
||||
// Prefer dedicated check-in total meta if present.
|
||||
$v = get_user_meta($user_id, 'aksrt_checkin_all_day', true);
|
||||
if ($v === '' || $v === false) {
|
||||
$v = get_user_meta($user_id, 'checkin_all_day', true);
|
||||
}
|
||||
return max(0.0, (float) $v);
|
||||
|
||||
case 'new_post':
|
||||
return (float) count_user_posts($user_id, 'post', true);
|
||||
|
||||
case 'bbs_post':
|
||||
$types = post_type_exists('forum_topic') ? 'forum_topic' : (post_type_exists('topic') ? 'topic' : '');
|
||||
return $types !== '' ? (float) count_user_posts($user_id, $types, true) : 0.0;
|
||||
|
||||
case 'comment':
|
||||
$counts = get_comments(['user_id' => $user_id, 'count' => true, 'status' => 'approve']);
|
||||
return (float) $counts;
|
||||
|
||||
case 'followed':
|
||||
$meta = get_user_meta($user_id, 'followed-user', true);
|
||||
if (!is_array($meta)) {
|
||||
$meta = get_user_meta($user_id, 'aksrt_followed_users', true);
|
||||
}
|
||||
return is_array($meta) ? (float) count($meta) : 0.0;
|
||||
|
||||
case 'like':
|
||||
return (float) get_user_meta($user_id, 'aksrt_posts_like_sum', true);
|
||||
|
||||
case 'comment_like':
|
||||
return (float) get_user_meta($user_id, 'aksrt_comment_like_sum', true);
|
||||
|
||||
case 'bbs_adopt':
|
||||
return (float) get_user_meta($user_id, 'aksrt_bbs_adopt_count', true);
|
||||
|
||||
case 'bbs_comment_hot':
|
||||
return (float) get_user_meta($user_id, 'aksrt_bbs_comment_hot_count', true);
|
||||
|
||||
case 'bbs_post_hot':
|
||||
return (float) get_user_meta($user_id, 'aksrt_bbs_post_hot_count', true);
|
||||
|
||||
case 'bbs_essence':
|
||||
return (float) get_user_meta($user_id, 'aksrt_bbs_essence_count', true);
|
||||
|
||||
case 'bbs_score':
|
||||
return (float) get_user_meta($user_id, 'aksrt_bbs_score_sum', true);
|
||||
|
||||
case 'views':
|
||||
return (float) get_user_meta($user_id, 'aksrt_posts_views_sum', true);
|
||||
|
||||
case 'pay_income':
|
||||
return (float) get_user_meta($user_id, 'aksrt_pay_income_sum', true);
|
||||
|
||||
case 'pay_rebate':
|
||||
return (float) get_user_meta($user_id, 'aksrt_pay_rebate_sum', true);
|
||||
|
||||
default:
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
self::auto_evaluate($user_id);
|
||||
}
|
||||
}
|
||||
|
||||
public function on_current_user_probe(): void {
|
||||
$uid = get_current_user_id();
|
||||
if ($uid > 0) {
|
||||
self::auto_evaluate($uid);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -91,6 +91,119 @@ if (!function_exists('aksrt_render_user_identity_chips')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_get_user_name')) {
|
||||
/**
|
||||
* Zibll zib_get_user_name parity: name link + auth + wear medal + level + optional follow.
|
||||
*
|
||||
* @param array|int $args User id or args array.
|
||||
*/
|
||||
function aksrt_get_user_name($args = []): string {
|
||||
$defaults = [
|
||||
'id' => 0,
|
||||
'class' => 'flex ac flex1',
|
||||
'name_class' => '',
|
||||
'vip' => false,
|
||||
'medal' => true,
|
||||
'auth' => true,
|
||||
'level' => true,
|
||||
'follow' => false,
|
||||
];
|
||||
if (is_numeric($args)) {
|
||||
$defaults['id'] = (int) $args;
|
||||
$args = $defaults;
|
||||
} else {
|
||||
$args = wp_parse_args(is_array($args) ? $args : [], $defaults);
|
||||
}
|
||||
$user_id = (int) $args['id'];
|
||||
if ($user_id <= 0) {
|
||||
$user_id = get_current_user_id();
|
||||
}
|
||||
if ($user_id <= 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$user = get_userdata($user_id);
|
||||
if (!$user) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$home = function_exists('get_author_posts_url') ? get_author_posts_url($user_id) : '';
|
||||
$display = $user->display_name;
|
||||
$vip_html = '';
|
||||
if (!empty($args['vip']) && function_exists('aksrt_render_vip_badge')) {
|
||||
$vip_html = (string) aksrt_render_vip_badge($user_id, 'sm');
|
||||
}
|
||||
$name_class = trim('display-name text-ellipsis ' . (string) $args['name_class']);
|
||||
$name_link = '<a class="' . esc_attr($name_class) . '" href="' . esc_url($home) . '">' . $vip_html . esc_html($display) . '</a>';
|
||||
|
||||
$auth_html = '';
|
||||
$medal_html = '';
|
||||
$level_html = '';
|
||||
$signals = aksrt_get_user_identity_signals($user_id);
|
||||
foreach ($signals as $signal) {
|
||||
$type = (string) ($signal['type'] ?? '');
|
||||
$label = trim((string) ($signal['label'] ?? ''));
|
||||
if ($label === '') {
|
||||
continue;
|
||||
}
|
||||
$icon_url = esc_url((string) ($signal['icon_url'] ?? ''));
|
||||
$title = (string) ($signal['title'] ?? $label);
|
||||
if ($type === 'verify' && !empty($args['auth']) && $auth_html === '') {
|
||||
$auth_html = '<span class="badg badg-sm ml3 aksrt-identity-chip aksrt-identity-chip--success" title="' . esc_attr($title) . '"><i class="fa fa-check-circle" aria-hidden="true"></i> ' . esc_html($label) . '</span>';
|
||||
} elseif ($type === 'level' && !empty($args['level']) && $level_html === '') {
|
||||
$level_html = '<span class="badg badg-sm ml3 aksrt-identity-chip aksrt-identity-chip--info" title="' . esc_attr($title) . '"><i class="fa fa-layer-group" aria-hidden="true"></i> ' . esc_html($label) . '</span>';
|
||||
} elseif ($type === 'badge' && !empty($args['medal']) && $medal_html === '' && !empty($signal['wear'])) {
|
||||
if ($icon_url !== '') {
|
||||
$medal_html = '<img class="medal-icon img-icon ml3 aksrt-identity-medal is-wear" src="' . $icon_url . '" alt="' . esc_attr($label) . '" title="' . esc_attr($title) . '" loading="lazy" decoding="async" />';
|
||||
} else {
|
||||
$medal_html = '<span class="badg badg-sm ml3 aksrt-identity-chip is-wear" title="' . esc_attr($title) . '"><i class="fa fa-award" aria-hidden="true"></i> ' . esc_html($label) . '</span>';
|
||||
}
|
||||
}
|
||||
}
|
||||
// If medal requested but no wear flag, use first badge with icon.
|
||||
if (!empty($args['medal']) && $medal_html === '') {
|
||||
foreach ($signals as $signal) {
|
||||
if ((string) ($signal['type'] ?? '') !== 'badge') {
|
||||
continue;
|
||||
}
|
||||
$label = trim((string) ($signal['label'] ?? ''));
|
||||
$icon_url = esc_url((string) ($signal['icon_url'] ?? ''));
|
||||
$title = (string) ($signal['title'] ?? $label);
|
||||
if ($label === '') {
|
||||
continue;
|
||||
}
|
||||
if ($icon_url !== '') {
|
||||
$medal_html = '<img class="medal-icon img-icon ml3 aksrt-identity-medal" src="' . $icon_url . '" alt="' . esc_attr($label) . '" title="' . esc_attr($title) . '" loading="lazy" decoding="async" />';
|
||||
} else {
|
||||
$medal_html = '<span class="badg badg-sm ml3 aksrt-identity-chip" title="' . esc_attr($title) . '"><i class="fa fa-award" aria-hidden="true"></i> ' . esc_html($label) . '</span>';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$follow_html = '';
|
||||
if (!empty($args['follow']) && get_current_user_id() !== $user_id) {
|
||||
if (is_user_logged_in()) {
|
||||
$follow_html = '<button type="button" class="focus-color ml10 follow flex0 but" data-aksrt-follow="' . (int) $user_id . '"><i class="fa fa-heart em09" aria-hidden="true"></i> ' . esc_html__('关注', 'aksrt') . '</button>';
|
||||
} else {
|
||||
$login = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url();
|
||||
$follow_html = '<a class="focus-color ml10 follow flex0 but" href="' . esc_url($login) . '"><i class="fa fa-heart em09" aria-hidden="true"></i> ' . esc_html__('关注', 'aksrt') . '</a>';
|
||||
}
|
||||
}
|
||||
|
||||
// Order: name → auth → wear medal → level → follow (Zibll).
|
||||
$html = '<name class="' . esc_attr((string) $args['class']) . '">' . $name_link . $auth_html . $medal_html . $level_html . $follow_html . '</name>';
|
||||
/**
|
||||
* Filter rendered user name chrome.
|
||||
*
|
||||
* @param string $html
|
||||
* @param int $user_id
|
||||
* @param array $args
|
||||
*/
|
||||
return (string) apply_filters('aksrt_user_show_name', $html, $user_id, $args);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_is_blog_comment_hot')) {
|
||||
/**
|
||||
* "神评" for ordinary post comments when likes cross threshold.
|
||||
|
||||
@@ -331,7 +331,18 @@ if (!function_exists('aksrt_single_render_author_card')) :
|
||||
$author_url = get_author_posts_url($author_id);
|
||||
$author_name = get_the_author();
|
||||
$bio = trim((string) get_the_author_meta('description'));
|
||||
$identity_chips = function_exists('aksrt_render_user_identity_chips')
|
||||
$name_html = function_exists('aksrt_get_user_name')
|
||||
? aksrt_get_user_name([
|
||||
'id' => $author_id,
|
||||
'class' => 'user-name flex jc ac',
|
||||
'vip' => true,
|
||||
'medal' => true,
|
||||
'auth' => true,
|
||||
'level' => true,
|
||||
'follow' => false,
|
||||
])
|
||||
: '';
|
||||
$identity_chips = ($name_html === '' && function_exists('aksrt_render_user_identity_chips'))
|
||||
? aksrt_render_user_identity_chips($author_id, 'card')
|
||||
: '';
|
||||
$avatar = function_exists('aksrt_get_avatar_html')
|
||||
@@ -362,12 +373,16 @@ if (!function_exists('aksrt_single_render_author_card')) :
|
||||
<div class="user-content">
|
||||
<div class="user-avatar"><?php echo $avatar; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
|
||||
<div class="user-info mt20 mb10">
|
||||
<div class="user-name flex jc ac">
|
||||
<a class="display-name" href="<?php echo esc_url($author_url); ?>"><?php echo esc_html($author_name); ?></a>
|
||||
<?php if ($identity_chips !== '') : ?>
|
||||
<?php echo $identity_chips; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<?php if ($name_html !== '') : ?>
|
||||
<?php echo $name_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<?php else : ?>
|
||||
<div class="user-name flex jc ac">
|
||||
<a class="display-name" href="<?php echo esc_url($author_url); ?>"><?php echo esc_html($author_name); ?></a>
|
||||
<?php if ($identity_chips !== '') : ?>
|
||||
<?php echo $identity_chips; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php if ($bio !== '') : ?>
|
||||
<div class="user-desc mt10 muted-2-color em09"><?php echo esc_html($bio); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -48,10 +48,23 @@ $aksrt_msg_url = function_exists('aksrt_get_messages_page_url') ? aksrt_get_mess
|
||||
$login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(get_permalink());
|
||||
$is_self = is_user_logged_in() && get_current_user_id() === $author_id;
|
||||
|
||||
// Identity chips: VIP + verify/level/badges (shared helper).
|
||||
$identity_chips = function_exists('aksrt_render_user_identity_chips')
|
||||
? aksrt_render_user_identity_chips($author_id, 'author')
|
||||
: '';
|
||||
// Identity: prefer aksrt_get_user_name (Zibll order); fall back to chips.
|
||||
$identity_chips = '';
|
||||
$name_html = '';
|
||||
if (function_exists('aksrt_get_user_name')) {
|
||||
$name_html = aksrt_get_user_name([
|
||||
'id' => $author_id,
|
||||
'class' => 'name flex ac flex1',
|
||||
'name_class' => '',
|
||||
'vip' => true,
|
||||
'medal' => true,
|
||||
'auth' => true,
|
||||
'level' => true,
|
||||
'follow' => false,
|
||||
]);
|
||||
} elseif (function_exists('aksrt_render_user_identity_chips')) {
|
||||
$identity_chips = aksrt_render_user_identity_chips($author_id, 'author');
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="<?php echo esc_attr($classes); ?>">
|
||||
@@ -67,12 +80,16 @@ $identity_chips = function_exists('aksrt_render_user_identity_chips')
|
||||
</a>
|
||||
<div class="user-right flex flex1 ac jsb ml10">
|
||||
<div class="flex1">
|
||||
<span class="name flex ac flex1">
|
||||
<a class="display-name text-ellipsis" href="<?php echo esc_url($author_url); ?>"><?php echo esc_html($author_name); ?></a>
|
||||
<?php if ($identity_chips !== '') : ?>
|
||||
<?php echo $identity_chips; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
<?php if ($name_html !== '') : ?>
|
||||
<?php echo $name_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<?php else : ?>
|
||||
<span class="name flex ac flex1">
|
||||
<a class="display-name text-ellipsis" href="<?php echo esc_url($author_url); ?>"><?php echo esc_html($author_name); ?></a>
|
||||
<?php if ($identity_chips !== '') : ?>
|
||||
<?php echo $identity_chips; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<div class="px12-sm muted-2-color text-ellipsis">
|
||||
<?php if ($show_time) : ?>
|
||||
<span class="icon-circle" title="<?php echo esc_attr($abs_fmt); ?>" data-toggle="tooltip"><?php echo esc_html($time_text); ?></span>
|
||||
|
||||
Reference in New Issue
Block a user