Files
wordpress-theme-aksrt/inc/helpers/aksrt-identity.php
T
Claude 4325ce0c1c fix(ui): render user level icon inline beside the name
The level (等级) badge was wrapping to its own line below the username and
rendering at the intrinsic PNG size in most surfaces. Centralize the badge
sizing after zibll's `.img-icon` (height: 1.1em, inline-block) in base.css so
every route gets the same compact inline badge, and keep the name row from
flex-wrapping.

- Add shared `.aksrt-user-level-icon / .aksrt-identity-medal` rule + nowrap name
  rows in base.css (loaded on all routes).
- Header dropdown: append the level icon next to the display name (was a
  separate badges row); constrain the name to ellipsis so the badge never wraps.
- User center profile header & author hero: render the level icon inside the
  <h1> name row; drop the duplicate level chip from the signals row.
- Single article author bar / after-box author card: nowrap the name row and
  size the level icon to 1.2em (was unstyled → intrinsic PNG size).
- Comments: inner <name> nowrap with a name clamp; medal sized to 1em.
- Consolidate the per-route `.aksrt-user-level-icon` duplicates in
  user-center/level.css and profile/layout.css onto the shared rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 18:13:41 +08:00

257 lines
9.8 KiB
PHP

<?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_render_user_identity_chips')) {
/**
* Render compact identity chips (verify / level / badges).
*
* Badge signals with icon_url render as Zibll-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.
* }
*/
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_render_vip_badge')) {
$vip = aksrt_render_vip_badge($user_id, 'sm');
if (is_string($vip) && $vip !== '') {
$html .= $vip;
}
}
$signals = aksrt_get_user_identity_signals($user_id);
// 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;
}
$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 class="' . esc_attr($classes) . '" src="' . $icon_url . '" alt="' . esc_attr($label) . '" title="' . esc_attr($title) . '" loading="lazy" 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_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 === '') {
if ($icon_url !== '') {
$level_html = '<img class="aksrt-user-level-icon img-icon ml3" src="' . $icon_url . '" alt="' . esc_attr($label) . '" title="' . esc_attr($title) . '" loading="lazy" decoding="async" />';
} else {
// Text fallback mirrors zibll 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 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.
*/
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;
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_comment_display_settings')) {
$display = \AKSRT\Settings\Settings::get_comment_display_settings();
if (isset($display['hot_threshold'])) {
$threshold = max(1, (int) $display['hot_threshold']);
}
}
$all = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
if (isset($all['comment_hot_threshold'])) {
$threshold = max(1, (int) $all['comment_hot_threshold']);
}
$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;
}
}