Files
wordpress-theme-aksrt/inc/helpers/aksrt-identity.php
T
kate522 6f815678ec feat(frontend): replicate reference header user box 1:1
顶导航点击小人弹出的用户信息浮层,从 AKSRT 定制结构改为与参考主题 zib_header_user_box() 逐行同构:头像链接包裹、钱包双瓷砖、会员条、签到、消息图标、统计徽章行全部改用参考主题遗留类,渲染交回 main.css(.sub-user-box 242px、.avatar-img 38px、.msg-news-icon、badge.top、.payvip-icon 深色渐变加 gem 水印、.vip-expdate-tag、.muted-box)。

头像改走 aksrt_get_avatar_box(用户ID, avatar-img, true, false),得到参考的 a 链接包裹 avatar-img 结构。消息图标改参考标记 msg-news-icon abs-right 加 toggle-radius msg-icon,未读徽章用 badge.top,avatar_dropdown 位置门控契约保留。管理端行在「后台管理」前插入「支付中心」,跳 admin.php?page=aksrt_pay,即参考主题「商城中心」槽位,图标 fa-credit-card。

钱包重写为参考双瓷砖:余额在前(c-blue-2 加 fa-money-bill-wave)、积分在后(c-yellow 加 fa-coins),分别按充值开关与积分总开关门控,两块皆关则不渲染,并保留 data-aksrt-wallet-value 与 data-aksrt-points-value 两个 JS 刷新钩子。会员条去掉 AKSRT 自创的 vipbg-v2 level-N 金色背景,改参考的 payvip-icon 加 badg jb-yellow vip-expdate-tag,到期文案按参考语义分为已过期、永久会员、裸日期三种。签到入口由 button 改为 a.but block c-yellow,并去掉随之失效的 compact 形参,data-signin 挂载与 wp_footer 验证码不变。统计行删掉文章徽章(本站不做帖子),图标由 SVG sprite 换 FA5+ 的 fa-comment-dots、fa-thumbs-up、fa-fire。

会员等级图标契约不动:aksrt_get_vip_icon 与 User\Level::get_badge_html 的调用与参数保持原样。

顺带清死代码:aksrt_header_user_badges_row 原本取到函数后直接返回空串,现在真正输出统计行;删掉 header-userbox.css 里整套 bespoke 规则与 vip-modal.css 的 vipbg-v2 死规则。

显式偏差:统计行链接保持扁平用户主页 URL(AKSRT 用户主页是干净 URL 加 JS tab,没有 query tab 路由);签到入口沿用积分签到总开关门控,不新增管理端可配置项。

public.css 与 public.min.css 用 tools/build-public-assets.mjs 重建:该 bundle 打包了 header-userbox.css 且作为依赖先于单模块入队,旧的 width 260px 会压过新源码、把卡片钉在 260px 而不是参考的 242px。

验证:node --test tests/**/*.test.cjs 573 项全通过;六个改动 PHP 文件 php -l 无语法错误;卡片用到的 FA5 token 逐个核对 5.15.4 全部命中。未做真实视觉效果验收。
2026-10-10 16:17:01 +08:00

579 lines
23 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Shared identity chrome: VIP / verify / level / badges for author bar & comments.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
if (!function_exists('aksrt_get_user_identity_signals')) {
/**
* @return list<array{type?:string,label:string,icon:string,tone:string,title?:string,icon_url?:string,slug?:string,wear?:bool}>
*/
function aksrt_get_user_identity_signals(int $user_id): array {
if ($user_id <= 0) {
return [];
}
if (class_exists('\AKSRT\User\Home')) {
$signals = \AKSRT\User\Home::get_instance()->get_profile_signals($user_id);
return is_array($signals) ? array_values($signals) : [];
}
return [];
}
}
if (!function_exists('aksrt_current_user_is_verified')) {
/**
* Whether the current user carries a verification (认证) signal.
*
* @return bool
*/
function aksrt_current_user_is_verified(): bool {
$user_id = get_current_user_id();
if ($user_id <= 0 || !function_exists('aksrt_get_user_identity_signals')) {
return false;
}
foreach (aksrt_get_user_identity_signals($user_id) as $signal) {
if ((string) ($signal['type'] ?? '') === 'verify') {
return true;
}
}
return false;
}
}
if (!function_exists('aksrt_render_user_identity_chips')) {
/**
* Render compact identity chips (verify / level / badges).
*
* Badge signals with icon_url render as legacy-like medal images.
*
* @param int $user_id
* @param string $context author|comment|card
* @param array $args {
* Optional. Additional options.
*
* @type bool $include_vip Whether to prepend VIP badge. Default true.
* @type bool $skip_level Whether to omit the level chip (rendered inline elsewhere). Default false.
* @type bool $skip_badges Whether to omit badge signals from this row. Default false.
* }
*/
function aksrt_render_user_identity_chips(int $user_id, string $context = 'author', array $args = []): string {
if ($user_id <= 0) {
return '';
}
$args = wp_parse_args($args, [
'include_vip' => true,
'skip_level' => false,
]);
$html = '';
if (!empty($args['include_vip']) && function_exists('aksrt_get_vip_icon')) {
$vip = aksrt_get_vip_icon($user_id);
if (is_string($vip) && $vip !== '') {
$html .= $vip;
}
}
$signals = aksrt_get_user_identity_signals($user_id);
// Keep the public identity order stable: certification, worn badge, level.
usort($signals, static function (array $left, array $right): int {
$priority = ['verify' => 0, 'badge' => 1, 'level' => 2];
return ($priority[(string) ($left['type'] ?? '')] ?? 3) <=> ($priority[(string) ($right['type'] ?? '')] ?? 3);
});
if ($context === 'comment') {
$signals = array_values(array_filter($signals, static function (array $signal): bool {
return (string) ($signal['type'] ?? '') !== 'badge' || !empty($signal['wear']);
}));
}
// Comments stay compact; author/card can show more.
$limit = $context === 'comment' ? 3 : 6;
$n = 0;
foreach ($signals as $signal) {
if ($n >= $limit) {
break;
}
$label = trim((string) ($signal['label'] ?? ''));
if ($label === '') {
continue;
}
$type = sanitize_html_class((string) ($signal['type'] ?? ''));
// Level is rendered inline next to the name by the caller; skip it here.
if (!empty($args['skip_level']) && $type === 'level') {
continue;
}
if (!empty($args['skip_badges']) && $type === 'badge') {
continue;
}
$tone = sanitize_html_class((string) ($signal['tone'] ?? 'info'));
$icon = sanitize_html_class((string) ($signal['icon'] ?? 'check'));
$icon_url = esc_url((string) ($signal['icon_url'] ?? ''));
$title = (string) ($signal['title'] ?? $label);
$is_wear = !empty($signal['wear']);
// Image medal when dictionary provides icon_url.
if ($icon_url !== '') {
$classes = 'medal-icon img-icon ml3 aksrt-identity-medal aksrt-identity-medal--' . $context;
if ($is_wear) {
$classes .= ' is-wear';
}
if ($type !== '') {
$classes .= ' aksrt-identity-medal--' . $type;
}
$html .= '<img' . aksrt_get_lazy_attr('lazy_other', $icon_url, $classes) . ' alt="' . esc_attr($label) . '" title="' . esc_attr($title) . '" decoding="async">';
$n++;
continue;
}
// Font Awesome: map short names to fa-* if needed.
if ($icon !== '' && !str_contains($icon, 'fa-')) {
$icon = 'fa-' . $icon;
}
$html .= '<span class="badg badg-sm ml3 aksrt-identity-chip aksrt-identity-chip--' . esc_attr($tone) . ' aksrt-identity-chip--' . esc_attr($context) . ($is_wear ? ' is-wear' : '') . '" title="' . esc_attr($title) . '">';
if ($icon !== '') {
$html .= '<i class="fa ' . esc_attr($icon) . '" aria-hidden="true"></i> ';
}
$html .= esc_html($label) . '</span>';
$n++;
}
return $html;
}
}
if (!function_exists('aksrt_get_user_follow')) {
/**
* Render the canonical user follow control.
*/
function aksrt_get_user_follow(string $class, int $follow_id, ?string $text = null, ?string $ok_text = null, ?string $guest_text = null): string {
$current_user_id = get_current_user_id();
if ($follow_id <= 0 || $current_user_id === $follow_id) {
return '';
}
$text = $text ?? __('关注', 'aksrt');
$ok_text = $ok_text ?? __('已关注', 'aksrt');
$guest_text = $guest_text ?? $text;
$class = trim($class);
if (!is_user_logged_in()) {
$login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url();
return '<a class="' . esc_attr($class) . '" href="' . esc_url($login_url) . '"><i class="fa far fa-heart em09" aria-hidden="true"></i> <span>' . esc_html($guest_text) . '</span></a>';
}
$is_following = class_exists('\AKSRT\User\Follow')
&& \AKSRT\User\Follow::get_instance()->is_following($current_user_id, $follow_id);
$state_class = $is_following ? ' is-following' : '';
$title = $is_following ? __('点击取消关注', 'aksrt') : __('关注该用户', 'aksrt');
$label = $is_following ? $ok_text : $text;
$icon_class = $is_following ? 'fa fas fa-heart em09' : 'fa far fa-heart em09';
return '<a href="javascript:;" class="' . esc_attr($class . $state_class) . '" data-aksrt-follow="' . $follow_id . '" title="' . esc_attr($title) . '"><i class="' . esc_attr($icon_class) . '" aria-hidden="true"></i> <span data-aksrt-follow-label>' . esc_html($label) . '</span></a>';
}
}
if (!function_exists('aksrt_get_user_name')) {
/**
* legacy 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' => true,
'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('aksrt_get_user_home_url')
? aksrt_get_user_home_url($user_id)
: get_author_posts_url($user_id);
$display = $user->display_name;
$vip_html = '';
if (!empty($args['vip'])) {
// 禁封徽章占据 VIP 图标位(参考主题 user_name_badge 过滤器覆盖语义)。
$vip_html = function_exists('aksrt_get_user_ban_name_badge')
? aksrt_get_user_ban_name_badge($user_id)
: '';
if ($vip_html === '' && function_exists('aksrt_get_vip_icon')) {
$vip_html = (string) aksrt_get_vip_icon($user_id);
}
}
$name_class = trim('display-name text-ellipsis ' . (string) $args['name_class']);
$name_link = '<a class="' . esc_attr($name_class) . '" href="' . esc_url($home) . '">' . 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 = $icon_url !== ''
? '<img' . aksrt_get_lazy_attr('lazy_other', $icon_url, 'aksrt-identity-certification-icon img-icon ml3') . ' alt="' . esc_attr($label) . '" title="' . esc_attr($title) . '" decoding="async">'
: '<span class="badg badg-sm ml3 aksrt-identity-chip aksrt-identity-chip--success" title="' . esc_attr($title) . '"><i class="fa fas fa-check-circle" aria-hidden="true"></i> ' . esc_html($label) . '</span>';
} elseif ($type === 'level' && !empty($args['level']) && $level_html === '') {
if ($icon_url !== '') {
$level_html = '<img' . aksrt_get_lazy_attr('lazy_other', $icon_url, 'aksrt-user-level-icon img-icon ml3') . ' alt="' . esc_attr($label) . '" title="' . esc_attr($title) . '" decoding="async">';
} else {
// Text fallback mirrors legacy LV badge: small pill, never oversized.
$level_html = '<span class="badg badg-sm ml3 aksrt-user-level-badge" title="' . esc_attr($title) . '">' . esc_html($label) . '</span>';
}
} elseif ($type === 'badge' && !empty($args['medal']) && $medal_html === '' && !empty($signal['wear'])) {
if ($icon_url !== '') {
$medal_html = '<img' . aksrt_get_lazy_attr('lazy_other', $icon_url, 'medal-icon img-icon ml3 aksrt-identity-medal is-wear') . ' alt="' . esc_attr($label) . '" title="' . esc_attr($title) . '" decoding="async">';
} else {
$medal_html = '<span class="badg badg-sm ml3 aksrt-identity-chip is-wear" title="' . esc_attr($title) . '"><i class="fa fas fa-trophy" 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' . aksrt_get_lazy_attr('lazy_other', $icon_url, 'medal-icon img-icon ml3 aksrt-identity-medal') . ' alt="' . esc_attr($label) . '" title="' . esc_attr($title) . '" decoding="async">';
} else {
$medal_html = '<span class="badg badg-sm ml3 aksrt-identity-chip" title="' . esc_attr($title) . '"><i class="fa fas fa-trophy" aria-hidden="true"></i> ' . esc_html($label) . '</span>';
}
break;
}
}
$follow_html = !empty($args['follow'])
? aksrt_get_user_follow('focus-color ml10 follow flex0', $user_id)
: '';
// Order: name → auth → wear medal → level → follow (legacy).
$html = '<name class="' . esc_attr((string) $args['class']) . '">' . $name_link . $vip_html . $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_get_user_posts_meta_sum')) {
/**
* 求和用户已发布文章的某个 meta(legacy get_user_posts_meta_sum parity)。
*
* @param int $user_id 用户 ID。
* @param string $meta_key 文章 meta 键。
*/
function aksrt_get_user_posts_meta_sum(int $user_id, string $meta_key): int {
global $wpdb;
if ($user_id <= 0 || $meta_key === '') {
return 0;
}
$cache_group = 'aksrt_user_posts_' . $meta_key . '_sum';
$cached = wp_cache_get($user_id, $cache_group);
if (false !== $cached) {
return (int) $cached;
}
$sum = $wpdb->get_var($wpdb->prepare(
"SELECT SUM(_m.meta_value + 0) FROM {$wpdb->posts} _p INNER JOIN {$wpdb->postmeta} _m ON _p.ID = _m.post_id"
. " WHERE _p.post_author = %d AND _m.meta_key = %s AND _p.post_status = 'publish'",
$user_id,
$meta_key
));
$sum = $sum ? (int) $sum : 0;
wp_cache_set($user_id, $sum, $cache_group, 43200);
return $sum;
}
}
if (!function_exists('aksrt_get_user_badges')) {
/**
* legacy parity: 作者资料卡统计徽章行(评论 / 粉丝|点赞 / 人气值)。
* 本站不做帖子,故不输出文章徽章。
*
* @param int $user_id 用户 ID。
*/
function aksrt_get_user_badges(int $user_id): string {
if ($user_id <= 0) {
return '';
}
$home = aksrt_get_user_home_url($user_id);
$com_n = aksrt_cut_count((int) get_comments([
'user_id' => $user_id,
'status' => 'approve',
'count' => true,
'post_type' => ['post'],
]));
$followed_n = class_exists('\AKSRT\User\Follow')
? aksrt_cut_count((int) \AKSRT\User\Follow::get_instance()->get_follower_count($user_id))
: 0;
$like_n = aksrt_cut_count(aksrt_get_user_posts_meta_sum($user_id, '_aksrt_like_count'));
$view_n = aksrt_cut_count(aksrt_get_user_posts_meta_sum($user_id, 'aksrt_post_views'));
$html = '';
$html .= '<a class="but c-green tag-comment" data-toggle="tooltip" title="' . esc_attr(sprintf(__('共%s条评论', 'aksrt'), $com_n)) . '" href="' . esc_url($home) . '"><i class="fa fas fa-comment-dots" aria-hidden="true"></i>' . $com_n . '</a>';
if ($followed_n) {
$html .= '<a class="but c-yellow tag-follow" data-toggle="tooltip" title="' . esc_attr(sprintf(__('共%s个粉丝', 'aksrt'), $followed_n)) . '" href="' . esc_url($home) . '"><i class="fa fas fa-heart em09" aria-hidden="true"></i>' . $followed_n . '</a>';
} elseif ($like_n) {
$html .= '<span class="badg c-yellow tag-like" data-toggle="tooltip" title="' . esc_attr(sprintf(__('获得%s个点赞', 'aksrt'), $like_n)) . '"><i class="fa fas fa-thumbs-up" aria-hidden="true"></i>' . $like_n . '</span>';
}
$html .= '<span class="badg c-red tag-view" data-toggle="tooltip" title="' . esc_attr(sprintf(__('人气值 %s', 'aksrt'), $view_n)) . '"><i class="fa fas fa-fire" aria-hidden="true"></i>' . $view_n . '</span>';
/**
* Filter the author-card count badges row.
*
* @param string $html Badges HTML.
* @param int $user_id User ID.
*/
return (string) apply_filters('aksrt_user_count_badges', $html, $user_id);
}
}
if (!function_exists('aksrt_render_user_medal_entry')) {
/**
* Render the compact legacy-style badge overview entry.
*
* The configured limit controls preview icons only; the count remains
* the complete owned badge count returned by the privacy-aware DTO.
*
* @param int $user_id
* @param string $context user-center|user-home
* @param bool $can_wear Whether the viewer may initially wear a badge.
*/
function aksrt_render_user_medal_entry(int $user_id, string $context = 'user-home', bool $can_wear = false): string {
if ($user_id <= 0 || !class_exists('AKSRT\\User\\Badges') || !class_exists('AKSRT\\Settings\\Settings')) {
return '';
}
$settings = \AKSRT\Settings\Settings::get_user_identity_settings();
$medal = is_array($settings['medal'] ?? null) ? $settings['medal'] : [];
if (empty($medal['enabled']) || ($context === 'user-home' && empty($medal['public_display']))) {
return '';
}
$data = \AKSRT\User\Badges::list_for_user($user_id);
if (empty($data['enabled']) || ($context === 'user-home' && empty($data['public_display']))) {
return '';
}
$count = max(0, (int) ($data['count'] ?? 0));
$is_manager = \AKSRT\User\Badges::can_manually_set(get_current_user_id());
if ($context === 'user-home' && $count === 0 && !$is_manager) {
return '';
}
$action = $is_manager && $context === 'user-home' && $count === 0 ? 'aksrt_user_medal_manually_add_modal' : 'aksrt_user_medal_info_modal';
$modal_class = $action === 'aksrt_user_medal_manually_add_modal'
? 'aksrt-badge-modal aksrt-badge-modal--grant'
: 'aksrt-badge-modal aksrt-badge-modal--overview';
$query = ['action' => $action, 'id' => $user_id];
$url = add_query_arg($query, admin_url('admin-ajax.php'));
$limit = max(1, min(8, (int) ($medal['limit'] ?? 3)));
$icons = [];
foreach ((array) ($data['categories'] ?? []) as $category) {
foreach ((array) ($category['items'] ?? []) as $item) {
if (empty($item['owned'])) {
continue;
}
$icon_url = esc_url((string) ($item['icon_url'] ?? ''));
if ($icon_url === '') {
continue;
}
$label = sanitize_text_field((string) ($item['label'] ?? __('徽章', 'aksrt')));
$icons[] = '<img' . aksrt_get_lazy_attr('lazy_other', $icon_url, 'img-icon medal-icon aksrt-medal-entry__icon') . ' alt="' . esc_attr($label) . '" title="' . esc_attr($label) . '" decoding="async">';
if (count($icons) >= $limit) {
break 2;
}
}
}
$label = $count > 0 ? sprintf(_n('%d枚徽章', '%d枚徽章', $count, 'aksrt'), $count) : ($is_manager && $context === 'user-home' ? __('授予徽章', 'aksrt') : __('获取徽章', 'aksrt'));
$icons_html = implode('', $icons);
if ($icons_html === '') {
$icons_html = '<span class="aksrt-medal-entry__fallback" aria-hidden="true"><i class="fa fas fa-trophy"></i></span>';
}
$classes = 'aksrt-medal-entry aksrt-medal-entry--' . sanitize_html_class($context);
return '<a class="' . esc_attr($classes . ' aksrt-modal-link') . '" data-aksrt-modal="remote" data-aksrt-modal-url="' . esc_url($url) . '" data-aksrt-modal-class="' . esc_attr($modal_class) . '" data-aksrt-modal-mobile-bottom="true" data-aksrt-modal-cache="false" href="' . esc_url($url) . '" aria-label="' . esc_attr($label) . '"><span class="aksrt-medal-entry__icons">' . $icons_html . '</span><span class="aksrt-medal-entry__count">' . esc_html($label) . '</span><i class="fa fas fa-angle-right aksrt-medal-entry__arrow" aria-hidden="true"></i></a>';
}
}
if (!function_exists('aksrt_user_has_visible_badges')) {
/**
* Whether a user owns at least one badge that exists in the current catalog.
*
* Used by user-center surfaces to decide whether to show the "我的徽章"
* button. Prevents rendering a button that opens an empty modal when the
* badge dictionary has no entries or none of the user's badges match it.
*
* @param int $user_id Target user (0 = current user).
* @return bool
*/
function aksrt_user_has_visible_badges(int $user_id = 0): bool {
if ($user_id <= 0) {
$user_id = get_current_user_id();
}
if ($user_id <= 0 || !class_exists('AKSRT\\User\\Badges')) {
return false;
}
$map = \AKSRT\User\Badges::get_dictionary_map();
if (empty($map)) {
return false;
}
$owned = \AKSRT\User\Badges::get_owned($user_id);
foreach ((array) $owned as $slug) {
if (is_string($slug) && $slug !== '' && isset($map[$slug])) {
return true;
}
}
return false;
}
}
if (!function_exists('aksrt_get_worn_medal_icon')) {
/**
* Render the worn medal icon HTML for a user (standalone, legacy parity).
*
* Returns an <img> tag when a badge is worn and has an icon_url in the
* dictionary. Handles the edge case where the worn slug lives in user meta
* but the badge class is unavailable (defensive: rebuilds from settings).
*
* @param int $user_id Target user (0 = current user).
* @param string $class Extra CSS classes ( space-separated ).
* @return string HTML safe for echo; '' when nothing to render.
*/
function aksrt_get_worn_medal_icon(int $user_id = 0, string $class = ''): string {
if ($user_id <= 0) {
$user_id = get_current_user_id();
}
if ($user_id <= 0) {
return '';
}
$enabled = false;
$slug = '';
$icon = '';
$label = '';
if (class_exists('AKSRT\\User\\Badges') && is_callable(['AKSRT\\User\\Badges', 'enabled'])) {
if (\AKSRT\User\Badges::enabled()) {
$enabled = true;
}
}
if (!$enabled && class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_user_identity_settings')) {
$ident = \AKSRT\Settings\Settings::get_user_identity_settings();
if (!empty($ident['badge']['enabled'])) {
$enabled = true;
}
}
if (!$enabled) {
return '';
}
// Class-backed path (canonical).
if (class_exists('AKSRT\\User\\Badges')
&& is_callable(['AKSRT\\User\\Badges', 'get_wear'])
&& is_callable(['AKSRT\\User\\Badges', 'get_dictionary_map'])) {
$slug = sanitize_key((string) \AKSRT\User\Badges::get_wear($user_id));
$map = \AKSRT\User\Badges::get_dictionary_map();
$entry = is_array($map[$slug] ?? null) ? $map[$slug] : [];
if (is_array($entry) && !empty($entry)) {
$icon = isset($entry['icon_url']) ? esc_url((string) $entry['icon_url']) : '';
$label = isset($entry['label']) ? sanitize_text_field((string) $entry['label']) : '';
}
}
// Fallback when class is missing or returned nothing: rebuild from settings.
if ($slug === '' && $icon === '' && class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_user_identity_settings')) {
$ident = \AKSRT\Settings\Settings::get_user_identity_settings();
$slug = sanitize_key((string) get_user_meta($user_id, 'aksrt_wear_badge', true));
if ($slug !== '') {
$dictionary = is_array($ident['badge']['dictionary'] ?? null) ? $ident['badge']['dictionary'] : [];
foreach ((array) $dictionary as $item) {
if (is_array($item) && !empty($item['slug']) && (string) $item['slug'] === $slug) {
$icon = isset($item['icon_url']) ? esc_url((string) $item['icon_url']) : '';
$label = isset($item['label']) ? sanitize_text_field((string) $item['label']) : '';
break;
}
}
}
}
if ($slug === '' || ($icon === '' && $label === '')) {
return '';
}
if ($icon !== '') {
$extra = $class !== '' ? ' ' . esc_attr($class) : '';
return '<img' . aksrt_get_lazy_attr('lazy_other', $icon, 'medal-icon img-icon ml3 aksrt-identity-medal is-wear' . $extra) . ' alt="' . esc_attr($label) . '" title="' . esc_attr($label) . '" decoding="async">';
}
// Text fallback (rare): small pill labelled with badge text.
$extra = $class !== '' ? ' ' . esc_attr($class) : '';
return '<span class="badg badg-sm ml3 aksrt-identity-medal is-wear' . $extra . '" title="' . esc_attr($label) . '"><i class="fa fas fa-trophy" aria-hidden="true"></i> ' . esc_html($label) . '</span>';
}
}
if (!function_exists('aksrt_is_blog_comment_hot')) {
/**
* "神评" for ordinary post comments when likes cross the fixed threshold
* (参考主题固定 10 赞,主题不再提供该阈值配置).
*/
function aksrt_is_blog_comment_hot($comment): bool {
$comment_id = $comment instanceof WP_Comment ? (int) $comment->comment_ID : (int) $comment;
if ($comment_id <= 0) {
return false;
}
if ('1' === (string) get_comment_meta($comment_id, '_aksrt_comment_hot', true)) {
return true;
}
$threshold = 10;
$likes = function_exists('aksrt_get_comment_likes') ? aksrt_get_comment_likes($comment_id) : (int) get_comment_meta($comment_id, 'aksrt_comment_likes', true);
if ($likes < $threshold) {
return false;
}
update_comment_meta($comment_id, '_aksrt_comment_hot', '1');
return true;
}
}