- Rewrite post_list/comment schema (PHP + JS) with the reference theme's field set, order, defaults and dependencies; smilie, comment location and quick-reply fields excluded - Map PostDisplay::get_comment_display_settings() to the new keys without legacy fallback: close_comments, comment_paginate_type (ajax_lists), user_edit_comment master switch, WP comment_order for sorting - Wire comment_box_hide visibility gate (signin / vip / vip_2 / auth) - Drop dead comment settings (order/per_page/depth/avatar/hot threshold/ empty text/city), the dead post_close_comments field, and city readers now fully owned by the IP-geo extension - Add comment settings reference contract test
580 lines
23 KiB
PHP
580 lines
23 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_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_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 .= '<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_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) . '">' . 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);
|
||
$post_n = aksrt_cut_count(count_user_posts($user_id, 'post', true));
|
||
$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-blue tag-posts" data-toggle="tooltip" title="' . esc_attr(sprintf(__('共%s篇文章', 'aksrt'), $post_n)) . '" href="' . esc_url($home) . '">' . aksrt_renderer_svg_icon('post') . $post_n . '</a>';
|
||
$html .= '<a class="but c-green tag-comment" data-toggle="tooltip" title="' . esc_attr(sprintf(__('共%s条评论', 'aksrt'), $com_n)) . '" href="' . esc_url($home) . '">' . aksrt_renderer_svg_icon('comment') . $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)) . '">' . aksrt_renderer_svg_icon('like') . $like_n . '</span>';
|
||
}
|
||
$html .= '<span class="badg c-red tag-view" data-toggle="tooltip" title="' . esc_attr(sprintf(__('人气值 %s', 'aksrt'), $view_n)) . '">' . aksrt_renderer_svg_icon('hot') . $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;
|
||
}
|
||
}
|