__('按积分、文章数或粉丝数排行的用户列表。', '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',
]);
}
}