From b4d04ae34668cb4c9475aa105397c31380cc932f Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 17 Jul 2026 01:12:09 +0800 Subject: [PATCH] feat(users): medal auto-grant + aksrt_get_user_name chrome MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../permissions/PermissionsCapabilities.vue | 6 + inc/classes/class-aksrt-user-badges.php | 132 ++++++++++++++++++ inc/helpers/aksrt-identity.php | 113 +++++++++++++++ inc/helpers/aksrt-single-render.php | 27 +++- templates/parts/single-author-bar.php | 37 +++-- 5 files changed, 299 insertions(+), 16 deletions(-) diff --git a/assets/admin/src/pages/permissions/PermissionsCapabilities.vue b/assets/admin/src/pages/permissions/PermissionsCapabilities.vue index a7bfcda3..015d4a1e 100644 --- a/assets/admin/src/pages/permissions/PermissionsCapabilities.vue +++ b/assets/admin/src/pages/permissions/PermissionsCapabilities.vue @@ -11,6 +11,12 @@ + + 转账/提现(balance_transfer、points_transfer、balance_withdraw)默认 exclude all,与 Zibll 一致:未在矩阵中为角色/等级/VIP/认证打开前,前台入口会隐藏或拒绝。 + medal_manually_set 控制谁可授予徽章;管理员 manage_options 始终可通过。 + + +
diff --git a/inc/classes/class-aksrt-user-badges.php b/inc/classes/class-aksrt-user-badges.php index c7e1e59b..f018a9d7 100644 --- a/inc/classes/class-aksrt-user-badges.php +++ b/inc/classes/class-aksrt-user-badges.php @@ -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); + } + } + } diff --git a/inc/helpers/aksrt-identity.php b/inc/helpers/aksrt-identity.php index 8ff34f17..1862c1ed 100644 --- a/inc/helpers/aksrt-identity.php +++ b/inc/helpers/aksrt-identity.php @@ -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 = '' . $vip_html . esc_html($display) . ''; + + $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 = ' ' . esc_html($label) . ''; + } elseif ($type === 'level' && !empty($args['level']) && $level_html === '') { + $level_html = ' ' . esc_html($label) . ''; + } elseif ($type === 'badge' && !empty($args['medal']) && $medal_html === '' && !empty($signal['wear'])) { + if ($icon_url !== '') { + $medal_html = '' . esc_attr($label) . ''; + } else { + $medal_html = ' ' . esc_html($label) . ''; + } + } + } + // 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 = '' . esc_attr($label) . ''; + } else { + $medal_html = ' ' . esc_html($label) . ''; + } + break; + } + } + + $follow_html = ''; + if (!empty($args['follow']) && get_current_user_id() !== $user_id) { + if (is_user_logged_in()) { + $follow_html = ''; + } else { + $login = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(); + $follow_html = ''; + } + } + + // Order: name → auth → wear medal → level → follow (Zibll). + $html = '' . $name_link . $auth_html . $medal_html . $level_html . $follow_html . ''; + /** + * 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. diff --git a/inc/helpers/aksrt-single-render.php b/inc/helpers/aksrt-single-render.php index c108113d..2f01bdf7 100644 --- a/inc/helpers/aksrt-single-render.php +++ b/inc/helpers/aksrt-single-render.php @@ -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')) :