*/ 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. * @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_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); // 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 .= ''; $n++; continue; } // Font Awesome: map short names to fa-* if needed. if ($icon !== '' && !str_contains($icon, 'fa-')) { $icon = 'fa-' . $icon; } $html .= ''; if ($icon !== '') { $html .= ' '; } $html .= esc_html($label) . ''; $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' => 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('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 = '' . 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 = $icon_url !== '' ? '' : ' ' . esc_html($label) . ''; } elseif ($type === 'level' && !empty($args['level']) && $level_html === '') { if ($icon_url !== '') { $level_html = ''; } else { // Text fallback mirrors zibll LV badge: small pill, never oversized. $level_html = '' . esc_html($label) . ''; } } elseif ($type === 'badge' && !empty($args['medal']) && $medal_html === '' && !empty($signal['wear'])) { if ($icon_url !== '') { $medal_html = ''; } 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 = ''; } 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 . $vip_html . $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_render_user_medal_entry')) { /** * Render the compact Zibll-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[] = ''; 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 = ''; } $classes = 'aksrt-medal-entry aksrt-medal-entry--' . sanitize_html_class($context); return '' . $icons_html . '' . esc_html($label) . ''; } } 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, zibll parity). * * Returns an 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 ''; } // Text fallback (rare): small pill labelled with badge text. $extra = $class !== '' ? ' ' . esc_attr($class) : ''; return ' ' . esc_html($label) . ''; } } 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; } }