__('按积分、文章数或粉丝数排行的用户列表。', 'aksrt'), 'classname' => 'widget_aksrt_user_ranking', ] ); } public function widget($args, $instance): void { $title = !empty($instance['title']) ? (string) $instance['title'] : __('排行榜', 'aksrt'); // Zibll zib_widget_ui_user_ranking: `orderby` (default checkin_all_day) + `number`. // Accept AKSRT legacy `rank_by`/`count` as aliases. $rank_by = (string) ($instance['orderby'] ?? $instance['rank_by'] ?? 'checkin_all_day'); // Map Zibll `points` → AKSRT `credits`; `followed-user-count` → `followers`. $rank_aliases = [ 'points' => 'credits', 'followed-user-count' => 'followers', 'checkin_all_day' => 'checkin_all_day', 'checkin_continuous_day' => 'checkin_continuous_day', 'credits' => 'credits', 'posts' => 'posts', 'followers' => 'followers', ]; $rank_by = $rank_aliases[$rank_by] ?? 'checkin_all_day'; $count = max(1, min(20, (int) ($instance['number'] ?? $instance['count'] ?? 6))); $show_avatar = !isset($instance['show_avatar']) || !empty($instance['show_avatar']); $desc_mode = sanitize_key((string) ($instance['desc'] ?? 'desc')); $top_badge = !empty($instance['top_badge']); $affix = function_exists('aksrt_widget_affix_attrs') ? aksrt_widget_affix_attrs($instance) : ['class' => '', 'attr' => '']; $users = $this->get_ranking($rank_by, $count, $desc_mode); echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $box_cls = 'zib-widget user-ranking-box aksrt-user-ranking-box' . $affix['class']; echo '
'; if (!empty($instance['checkin_btn']) && is_user_logged_in() && function_exists('aksrt_header_user_checkin_btn')) { $cbtn = aksrt_header_user_checkin_btn(get_current_user_id()); if ($cbtn !== '') { echo '
' . $cbtn . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } } if (empty($users)) { echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无排行数据', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped : '

' . esc_html__('暂无排行数据', 'aksrt') . '

'; echo '
'; echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped return; } $ph = get_template_directory_uri() . '/assets/img/placeholder.svg'; $rank = 0; foreach ($users as $user) { $rank++; $uid = (int) $user['id']; $user_url = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($uid) : get_author_posts_url($uid); $top_bagd = ''; if ($top_badge && $rank <= 3) { $tone = $rank === 1 ? 'jb-red' : ($rank === 2 ? 'jb-yellow' : 'b-gray'); $top_bagd = 'TOP' . (int) $rank . ''; } $name_html = function_exists('aksrt_get_user_name') ? aksrt_get_user_name([ 'id' => $uid, 'class' => 'flex ac flex1', 'follow' => false, 'medal' => true, 'level' => true, ]) : '' . esc_html((string) $user['name']) . ''; $avatar_html = ''; if ($show_avatar) { $avatar_url = function_exists('aksrt_get_avatar_url') ? aksrt_get_avatar_url($uid, ['size' => 42]) : get_avatar_url($uid, ['size' => 42]); $avatar_html = ''; } $desc = (string) ($user['desc'] ?? ''); $value_label = (string) ($user['value_label'] ?? $user['value'] ?? ''); $value_icon = (string) ($user['value_icon'] ?? 'fa-trophy'); $right_badge = '' . esc_html((string) ($user['value_num'] ?? $user['value'] ?? '')) . ''; $mt = $rank > 1 ? ' mt20' : ''; echo '
'; echo $top_bagd; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo '
'; echo $avatar_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo '
'; echo '
'; echo '
' . $name_html . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped if ($desc !== '') { echo '
' . esc_html($desc) . '
'; } echo '
'; echo '
' . $right_badge . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo '
'; } echo ''; echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } /** * @return list */ private function get_ranking(string $rank_by, int $count, string $desc_mode = 'desc'): array { $users = []; switch ($rank_by) { case 'credits': global $wpdb; $rows = $wpdb->get_results($wpdb->prepare( "SELECT user_id, meta_value as credits FROM {$wpdb->usermeta} WHERE meta_key = 'aksrt_credits' ORDER BY CAST(meta_value AS UNSIGNED) DESC LIMIT %d", $count ), ARRAY_A); foreach ((array) $rows as $row) { $user = get_userdata((int) $row['user_id']); if ($user) { $num = (int) $row['credits']; $users[] = [ 'id' => (int) $row['user_id'], 'name' => $user->display_name, 'value' => number_format_i18n($num) . ' 积分', 'value_num' => (string) number_format_i18n($num), 'value_label' => sprintf(__('积分:%s', 'aksrt'), number_format_i18n($num)), 'value_icon' => 'fa-diamond', 'desc' => $this->user_extra_desc((int) $row['user_id'], $user, $desc_mode), ]; } } break; case 'posts': $blog_users = get_users([ 'number' => $count, 'orderby' => 'post_count', 'order' => 'DESC', 'who' => 'authors', ]); foreach ($blog_users as $user) { $post_count = count_user_posts($user->ID, 'post', true); if ($post_count > 0) { $users[] = [ 'id' => $user->ID, 'name' => $user->display_name, 'value' => number_format_i18n($post_count) . ' 篇', 'value_num' => (string) number_format_i18n($post_count), 'value_label' => sprintf(__('共%d篇文章', 'aksrt'), $post_count), 'value_icon' => 'fa-file-text-o', 'desc' => $this->user_extra_desc($user->ID, $user, $desc_mode), ]; } } break; case 'checkin_all_day': $users = $this->meta_rank('aksrt_checkin_all_day', ['checkin_all_day'], $count, $desc_mode, 'fa-calendar-check-o', __('累计签到%d天', 'aksrt'), ' 天'); break; case 'checkin_continuous_day': $users = $this->meta_rank('aksrt_checkin_continuous_day', ['checkin_continuous_day'], $count, $desc_mode, 'fa-calendar-check-o', __('连续签到%d天', 'aksrt'), ' 天'); break; case 'followers': if (class_exists('\AKSRT\User\Follow')) { global $wpdb; $follow_table = \AKSRT\Core\DBMigrator::table('follow'); if ($follow_table) { $rows = $wpdb->get_results($wpdb->prepare( "SELECT following_id, COUNT(*) as cnt FROM {$follow_table} GROUP BY following_id ORDER BY cnt DESC LIMIT %d", $count ), ARRAY_A); foreach ((array) $rows as $row) { $user = get_userdata((int) $row['following_id']); if ($user) { $num = (int) $row['cnt']; $users[] = [ 'id' => (int) $row['following_id'], 'name' => $user->display_name, 'value' => number_format_i18n($num) . ' 粉丝', 'value_num' => (string) number_format_i18n($num), 'value_label' => sprintf(__('共%d个粉丝', 'aksrt'), $num), 'value_icon' => 'fa-heart', 'desc' => $this->user_extra_desc((int) $row['following_id'], $user, $desc_mode), ]; } } } } break; } return $users; } /** * @param list $alt_keys * @return list */ private function meta_rank(string $primary_key, array $alt_keys, int $count, string $desc_mode, string $icon, string $label_tpl, string $suffix): array { global $wpdb; $keys = array_merge([$primary_key], $alt_keys); $in = implode(',', array_fill(0, count($keys), '%s')); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $sql = $wpdb->prepare( "SELECT user_id, meta_value as val FROM {$wpdb->usermeta} WHERE meta_key IN ($in) ORDER BY CAST(meta_value AS UNSIGNED) DESC LIMIT %d", ...array_merge($keys, [$count * 2]) ); $rows = $wpdb->get_results($sql, ARRAY_A); $seen = []; $users = []; foreach ((array) $rows as $row) { $uid = (int) $row['user_id']; if (isset($seen[$uid])) { continue; } $seen[$uid] = true; $user = get_userdata($uid); if (!$user) { continue; } $num = (int) $row['val']; $users[] = [ 'id' => $uid, 'name' => $user->display_name, 'value' => number_format_i18n($num) . $suffix, 'value_num' => (string) number_format_i18n($num), 'value_label' => sprintf($label_tpl, $num), 'value_icon' => $icon, 'desc' => $this->user_extra_desc($uid, $user, $desc_mode), ]; if (count($users) >= $count) { break; } } return $users; } private function user_extra_desc(int $user_id, $user, string $mode): string { switch ($mode) { case 'user_registered': if ($user && !empty($user->user_registered)) { $days = max(0, (int) floor((time() - strtotime($user->user_registered)) / DAY_IN_SECONDS)); return sprintf(__('加入 %d 天', 'aksrt'), $days); } return ''; case 'last_login': $t = (string) get_user_meta($user_id, 'aksrt_last_login', true); if ($t === '') { $t = (string) get_user_meta($user_id, 'last_login', true); } return $t !== '' ? sprintf(__('最近登录 %s', 'aksrt'), $t) : ''; case 'last_checkin': $t = (string) get_user_meta($user_id, 'aksrt_last_checkin', true); if ($t === '') { $t = (string) get_user_meta($user_id, 'last_checkin', true); } return $t !== '' ? sprintf(__('最近签到 %s', 'aksrt'), $t) : ''; case 'desc': default: $bio = get_user_meta($user_id, 'description', true); if (!is_string($bio) || $bio === '') { $bio = $user ? (string) $user->description : ''; } return $bio !== '' ? wp_trim_words($bio, 12) : ''; } } public function form($instance): void { $title = !empty($instance['title']) ? $instance['title'] : __('排行榜', 'aksrt'); // Zibll field is `orderby` (default checkin_all_day); accept legacy `rank_by`. $rank_by = (string) ($instance['orderby'] ?? $instance['rank_by'] ?? 'checkin_all_day'); $count = !empty($instance['number']) ? (int) $instance['number'] : (!empty($instance['count']) ? (int) $instance['count'] : 6); $show_avatar = !isset($instance['show_avatar']) || !empty($instance['show_avatar']); ?>

render_common_form_section($instance); ?> update_common_fields($new_instance, $old_instance, [ 'title' => sanitize_text_field($new_instance['title'] ?? ''), 'orderby' => $rank_by, 'rank_by' => $rank_by, // keep alias for any legacy readers. 'number' => max(1, min(20, (int) ($new_instance['number'] ?? $new_instance['count'] ?? 6))), 'count' => max(1, min(20, (int) ($new_instance['number'] ?? $new_instance['count'] ?? 6))), 'show_avatar' => !empty($new_instance['show_avatar']) ? 1 : 0, 'checkin_btn' => !empty($new_instance['checkin_btn']) ? 1 : 0, 'top_badge' => !empty($new_instance['top_badge']) ? 1 : 0, 'desc' => in_array($new_instance['desc'] ?? '', ['desc', 'user_registered', 'last_login', 'last_checkin'], true) ? $new_instance['desc'] : 'desc', ]); } }