feat(widgets): ranking identity, moderators follow, plate chrome

Align UserRanking to zibll user-ranking-item + aksrt_get_user_name + badg,
render moderators via shared rows with follow, deepen plate info cover/stats,
and clean forum tab/terms empty shells.
This commit is contained in:
kate522 committed 2026-07-21 10:39:17 +08:00
1 parent 9c5ac01119
commit 8b5ca8f287
5 files changed
+335 -299

No files matched your search

+5 -4
View File
@@ -54,11 +54,12 @@ class WidgetForumPlateInfo extends \WP_Widget {
: home_url('/'); : home_url('/');
ob_start(); ob_start();
echo '<div class="aksrt-plate-info ajax-item">'; echo '<div class="forum-header plate-info-sidebar aksrt-plate-info ajax-item">';
if ($thumb) { if ($thumb) {
echo '<div class="aksrt-plate-info__cover" style="background-image:url(' . esc_url($thumb) . ')"></div>'; $ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
echo '<div class="aksrt-plate-info__cover plate-thumb"><img class="aksrt-lazy lazyload fit-cover" data-src="' . esc_url($thumb) . '" src="' . esc_url($ph) . '" alt="" loading="lazy" /></div>';
} elseif ($icon !== '') { } elseif ($icon !== '') {
echo '<div class="aksrt-plate-info__cover is-icon"><i class="' . esc_attr($icon) . '" aria-hidden="true"></i></div>'; echo '<div class="aksrt-plate-info__cover plate-thumb is-icon"><i class="' . esc_attr($icon) . '" aria-hidden="true"></i></div>';
} }
echo '<div class="aksrt-plate-info__body">'; echo '<div class="aksrt-plate-info__body">';
echo '<a class="aksrt-plate-info__title" href="' . esc_url($url) . '">' . esc_html(get_the_title($plate)) . '</a>'; echo '<a class="aksrt-plate-info__title" href="' . esc_url($url) . '">' . esc_html(get_the_title($plate)) . '</a>';
@@ -66,7 +67,7 @@ class WidgetForumPlateInfo extends \WP_Widget {
echo '<div class="aksrt-plate-info__desc">' . esc_html($desc) . '</div>'; echo '<div class="aksrt-plate-info__desc">' . esc_html($desc) . '</div>';
} }
if ($show_stats) { if ($show_stats) {
echo '<div class="aksrt-plate-info__stats">'; echo '<div class="aksrt-plate-info__stats count-mates flex jsb">';
echo '<span title="' . esc_attr__('帖子', 'aksrt') . '"><i class="fas fa-file-alt" aria-hidden="true"></i> ' . esc_html((string) $posts) . '</span>'; echo '<span title="' . esc_attr__('帖子', 'aksrt') . '"><i class="fas fa-file-alt" aria-hidden="true"></i> ' . esc_html((string) $posts) . '</span>';
echo '<span title="' . esc_attr__('回复', 'aksrt') . '"><i class="fas fa-comment" aria-hidden="true"></i> ' . esc_html((string) $reply) . '</span>'; echo '<span title="' . esc_attr__('回复', 'aksrt') . '"><i class="fas fa-comment" aria-hidden="true"></i> ' . esc_html((string) $reply) . '</span>';
if ($views > 0) { if ($views > 0) {
@@ -74,13 +74,17 @@ class WidgetForumPlateModerator extends \WP_Widget {
} }
ob_start(); ob_start();
echo '<div class="aksrt-plate-mods style-' . esc_attr($style) . ' ajax-item">'; echo '<div class="zib-widget moderator-box aksrt-plate-mods style-' . esc_attr($style) . ' ajax-item">';
if ($title_text !== '') { if ($title_text !== '') {
echo '<div class="aksrt-plate-mods__head"><div class="title-theme">' . esc_html($title_text) . '</div></div>'; echo '<div class="mb10 flex jsb ac aksrt-plate-mods__head"><div class="title-theme">' . esc_html($title_text) . '</div></div>';
} }
echo '<div class="aksrt-plate-mods__list">'; echo '<div class="aksrt-plate-mods__list">';
foreach ($people as $row) { foreach ($people as $row) {
$uid = (int) $row['id']; $uid = (int) $row['id'];
if (function_exists('aksrt_render_moderator_row')) {
echo aksrt_render_moderator_row($uid, (string) $row['role'], $show_avatar, $style); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
continue;
}
$user = get_userdata($uid); $user = get_userdata($uid);
if (!$user) { if (!$user) {
continue; continue;
@@ -105,7 +109,7 @@ class WidgetForumPlateModerator extends \WP_Widget {
echo '</a>'; echo '</a>';
} }
echo '</div></div>'; echo '</div></div>';
return ['html' => (string) ob_get_clean()]; return ['html' => (string) ob_get_clean()];
} }
public function widget($args, $instance): void { public function widget($args, $instance): void {
+1 -1
View File
@@ -88,7 +88,7 @@ class WidgetForumTabTopics extends \WP_Widget {
.then(function(r){return r.json()}).then(function(j){ .then(function(r){return r.json()}).then(function(j){
var d=(j&&j.data)||j||{}; var d=(j&&j.data)||j||{};
var html=d.html||''; var html=d.html||'';
panel.innerHTML=html||'<p class="aksrt-ftab__empty">暂无内容</p>'; panel.innerHTML=html||'<div class="ajax-item aksrt-ftab__empty muted-2-color">暂无内容</div>';
panel.setAttribute('data-loaded','1'); panel.setAttribute('data-loaded','1');
panel.classList.remove('is-ajax-loading'); panel.classList.remove('is-ajax-loading');
}).catch(function(){panel.classList.remove('is-ajax-loading');}); }).catch(function(){panel.classList.remove('is-ajax-loading');});
+2 -2
View File
@@ -83,7 +83,7 @@ class WidgetForumTopicTerms extends \WP_Widget {
continue; continue;
} }
$icon = (string) get_term_meta($term->term_id, '_aksrt_icon', true); $icon = (string) get_term_meta($term->term_id, '_aksrt_icon', true);
echo '<a class="aksrt-ftag-item" href="' . esc_url($link) . '">'; echo '<a class="aksrt-ftag-item but p2-10' . ($style === 'card' ? ' is-card' : '') . '" href="' . esc_url($link) . '">';
if ($icon !== '') { if ($icon !== '') {
echo '<i class="' . esc_attr($icon) . '" aria-hidden="true"></i> '; echo '<i class="' . esc_attr($icon) . '" aria-hidden="true"></i> ';
} }
@@ -106,7 +106,7 @@ class WidgetForumTopicTerms extends \WP_Widget {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} }
// IAS-style wrap for remote refresh. // IAS-style wrap for remote refresh.
echo '<div class="zib-widget padding-6 ajaxpager" data-aksrt-ajaxpager>'; echo '<div class="zib-widget padding-6 aksrt-ftag-wrap">';
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>'; echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+319 -288
View File
@@ -1,5 +1,7 @@
<?php <?php
/** /**
* User ranking lists (zibll zib_user_ranking_lists parity).
*
* @package AKSRT * @package AKSRT
*/ */
@@ -9,314 +11,343 @@ defined('ABSPATH') || exit;
class WidgetUserRanking extends \WP_Widget { class WidgetUserRanking extends \WP_Widget {
public function __construct() { public function __construct() {
parent::__construct( parent::__construct(
'aksrt_user_ranking', 'aksrt_user_ranking',
__('用户排行榜', 'aksrt'), __('用户排行榜', 'aksrt'),
['description' => __('按积分、文章数或粉丝数排行的用户列表。', 'aksrt')] [
); 'description' => __('按积分、文章数或粉丝数排行的用户列表。', 'aksrt'),
} 'classname' => 'widget_aksrt_user_ranking',
]
);
}
public function widget($args, $instance): void { public function widget($args, $instance): void {
$title = !empty($instance['title']) ? $instance['title'] : __('排行榜', 'aksrt'); $title = !empty($instance['title']) ? (string) $instance['title'] : __('排行榜', 'aksrt');
$rank_by = !empty($instance['rank_by']) ? $instance['rank_by'] : 'credits'; $rank_by = !empty($instance['rank_by']) ? (string) $instance['rank_by'] : 'credits';
$count = max(1, min(20, (int) ($instance['count'] ?? 5))); $count = max(1, min(20, (int) ($instance['count'] ?? 5)));
$show_avatar = !empty($instance['show_avatar']); $show_avatar = !isset($instance['show_avatar']) || !empty($instance['show_avatar']);
$desc_mode = sanitize_key((string) ($instance['desc'] ?? 'desc'));
$top_badge = !empty($instance['top_badge']);
$users = $this->get_ranking($rank_by, $count, $desc_mode);
$desc_mode = sanitize_key((string) ($instance['desc'] ?? 'desc')); echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$users = $this->get_ranking($rank_by, $count, $desc_mode); echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo '<div class="zib-widget user-ranking-box aksrt-user-ranking-box">';
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 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 '<div class="aksrt-user-ranking__checkin mb10">' . $cbtn . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
if (!empty($instance['checkin_btn']) && is_user_logged_in() && function_exists('aksrt_header_user_checkin_btn')) { if (empty($users)) {
$cbtn = aksrt_header_user_checkin_btn(get_current_user_id()); echo function_exists('aksrt_get_widget_null')
if ($cbtn !== '') { ? aksrt_get_widget_null(__('暂无排行数据', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="aksrt-user-ranking__checkin mb10">' . $cbtn . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped : '<p class="aksrt-user-ranking__empty muted-2-color">' . esc_html__('暂无排行数据', 'aksrt') . '</p>';
} echo '</div>';
} echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return;
}
if (empty($users)) { $ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
echo '<p class="aksrt-user-ranking__empty muted-2-color">' . esc_html__('暂无排行数据', 'aksrt') . '</p>'; $rank = 0;
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped foreach ($users as $user) {
return; $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_badge = !empty($instance['top_badge']); $top_bagd = '';
if ($top_badge && $rank <= 3) {
$tone = $rank === 1 ? 'jb-red' : ($rank === 2 ? 'jb-yellow' : 'b-gray');
$top_bagd = '<badge class="img-badge left hot ' . esc_attr($tone) . '"><i>TOP' . (int) $rank . '</i></badge>';
}
echo '<ul class="aksrt-user-ranking">'; $name_html = function_exists('aksrt_get_user_name')
$rank = 0; ? aksrt_get_user_name([
foreach ($users as $user) { 'id' => $uid,
$rank++; 'class' => 'flex ac flex1',
$rank_class = $rank <= 3 ? ' aksrt-user-ranking__item--top' : ''; 'follow' => false,
$user_url = function_exists('aksrt_get_user_home_url') 'medal' => true,
? aksrt_get_user_home_url($user['id']) 'level' => true,
: get_author_posts_url($user['id']); ])
: '<a class="font-bold" href="' . esc_url($user_url) . '">' . esc_html((string) $user['name']) . '</a>';
echo '<li class="aksrt-user-ranking__item relative' . esc_attr($rank_class) . '">'; $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 = '<a class="avatar-img" href="' . esc_url($user_url) . '"><img class="avatar aksrt-lazy lazyload" data-src="' . esc_url($avatar_url) . '" src="' . esc_url($ph) . '" alt="" loading="lazy" width="36" height="36" /></a>';
}
if ($top_badge && $rank <= 3) { $desc = (string) ($user['desc'] ?? '');
$tone = $rank === 1 ? 'jb-red' : ($rank === 2 ? 'jb-yellow' : 'b-gray'); $value_label = (string) ($user['value_label'] ?? $user['value'] ?? '');
echo '<badge class="img-badge left hot ' . esc_attr($tone) . '"><i>TOP' . (int) $rank . '</i></badge>'; $value_icon = (string) ($user['value_icon'] ?? 'fa-trophy');
} $right_badge = '<span class="badg" title="' . esc_attr($value_label) . '"><i class="fa ' . esc_attr($value_icon) . '" aria-hidden="true"></i><span class="ml6">' . esc_html((string) ($user['value_num'] ?? $user['value'] ?? '')) . '</span></span>';
// 排名 $mt = $rank > 1 ? ' mt20' : '';
echo '<span class="aksrt-user-ranking__rank">' . esc_html((string) $rank) . '</span>'; echo '<div class="user-ranking-item aksrt-user-ranking__item relative' . esc_attr($mt) . ($rank <= 3 ? ' is-top' : '') . '">';
echo $top_bagd; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="user-info flex ac">';
echo $avatar_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="user-right flex flex1 ac jsb ml10">';
echo '<div class="flex1 min-w0">';
echo '<div class="font-bold aksrt-user-ranking__name">' . $name_html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ($desc !== '') {
echo '<div class="mt3 em09 muted-color text-ellipsis aksrt-user-ranking__desc">' . esc_html($desc) . '</div>';
}
echo '</div>';
echo '<div class="ml20 flex0 aksrt-user-ranking__value">' . $right_badge . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div></div></div>';
}
echo '</div>';
// 头像 echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ($show_avatar) { }
$avatar_url = function_exists('aksrt_get_avatar_url')
? aksrt_get_avatar_url($user['id'], ['size' => 36])
: get_avatar_url($user['id'], ['size' => 36]);
echo '<a href="' . esc_url($user_url) . '" class="aksrt-user-ranking__avatar">';
echo '<img src="' . esc_url($avatar_url) . '" alt=""' . (function_exists('aksrt_lazy_loading') ? aksrt_lazy_loading('avatar') : ' loading="lazy"') . ' width="36" height="36">';
echo '</a>';
}
// 昵称 + 可选签名/附加信息 /**
echo '<div class="aksrt-user-ranking__meta">'; * @return list<array{id:int,name:string,value:string,value_num:string,value_label:string,value_icon:string,desc:string}>
echo '<a href="' . esc_url($user_url) . '" class="aksrt-user-ranking__name">' . esc_html($user['name']) . '</a>'; */
if (!empty($user['desc'])) { private function get_ranking(string $rank_by, int $count, string $desc_mode = 'desc'): array {
echo '<div class="aksrt-user-ranking__desc em09 muted-2-color">' . esc_html((string) $user['desc']) . '</div>'; $users = [];
}
echo '</div>';
// 数值 switch ($rank_by) {
echo '<span class="aksrt-user-ranking__value">' . esc_html($user['value']) . '</span>'; 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;
echo '</li>'; case 'posts':
} $blog_users = get_users([
echo '</ul>'; '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;
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 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;
private function get_ranking(string $rank_by, int $count, string $desc_mode = 'desc'): array { case 'checkin_continuous_day':
$users = []; $users = $this->meta_rank('aksrt_checkin_continuous_day', ['checkin_continuous_day'], $count, $desc_mode, 'fa-calendar-check-o', __('连续签到%d天', 'aksrt'), ' 天');
break;
switch ($rank_by) { case 'followers':
case 'credits': if (class_exists('\AKSRT\User\Follow')) {
global $wpdb; global $wpdb;
$rows = $wpdb->get_results($wpdb->prepare( $follow_table = \AKSRT\Core\DBMigrator::table('follow');
"SELECT user_id, meta_value as credits FROM {$wpdb->usermeta} if ($follow_table) {
WHERE meta_key = 'aksrt_credits' $rows = $wpdb->get_results($wpdb->prepare(
ORDER BY CAST(meta_value AS UNSIGNED) DESC "SELECT following_id, COUNT(*) as cnt
LIMIT %d", FROM {$follow_table}
$count GROUP BY following_id
), ARRAY_A); ORDER BY cnt DESC
foreach ((array) $rows as $row) { LIMIT %d",
$user = get_userdata((int) $row['user_id']); $count
if ($user) { ), ARRAY_A);
$users[] = [ foreach ((array) $rows as $row) {
'id' => (int) $row['user_id'], $user = get_userdata((int) $row['following_id']);
'name' => $user->display_name, if ($user) {
'value' => number_format_i18n((int) $row['credits']) . ' 积分', $num = (int) $row['cnt'];
'desc' => $this->user_extra_desc((int) $row['user_id'], $user, $desc_mode), $users[] = [
]; 'id' => (int) $row['following_id'],
} 'name' => $user->display_name,
} 'value' => number_format_i18n($num) . ' 粉丝',
break; '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;
}
case 'posts': return $users;
$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) . ' 篇',
'desc' => $this->user_extra_desc($user->ID, $user, $desc_mode),
];
}
}
break;
case 'checkin_all_day': /**
global $wpdb; * @param list<string> $alt_keys
$rows = $wpdb->get_results($wpdb->prepare( * @return list<array{id:int,name:string,value:string,value_num:string,value_label:string,value_icon:string,desc:string}>
"SELECT user_id, meta_value as val FROM {$wpdb->usermeta} */
WHERE meta_key IN ('aksrt_checkin_all_day','checkin_all_day') private function meta_rank(string $primary_key, array $alt_keys, int $count, string $desc_mode, string $icon, string $label_tpl, string $suffix): array {
ORDER BY CAST(meta_value AS UNSIGNED) DESC global $wpdb;
LIMIT %d", $keys = array_merge([$primary_key], $alt_keys);
$count * 2 $in = implode(',', array_fill(0, count($keys), '%s'));
), ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$seen = []; $sql = $wpdb->prepare(
foreach ((array) $rows as $row) { "SELECT user_id, meta_value as val FROM {$wpdb->usermeta}
$uid = (int) $row['user_id']; WHERE meta_key IN ($in)
if (isset($seen[$uid])) continue; ORDER BY CAST(meta_value AS UNSIGNED) DESC
$seen[$uid] = true; LIMIT %d",
$user = get_userdata($uid); ...array_merge($keys, [$count * 2])
if ($user) { );
$users[] = [ $rows = $wpdb->get_results($sql, ARRAY_A);
'id' => $uid, $seen = [];
'name' => $user->display_name, $users = [];
'value' => number_format_i18n((int) $row['val']) . ' 天', foreach ((array) $rows as $row) {
'desc' => $this->user_extra_desc($uid, $user, $desc_mode), $uid = (int) $row['user_id'];
]; if (isset($seen[$uid])) {
} continue;
if (count($users) >= $count) break; }
} $seen[$uid] = true;
break; $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;
}
case 'checkin_continuous_day': private function user_extra_desc(int $user_id, $user, string $mode): string {
global $wpdb; switch ($mode) {
$rows = $wpdb->get_results($wpdb->prepare( case 'user_registered':
"SELECT user_id, meta_value as val FROM {$wpdb->usermeta} if ($user && !empty($user->user_registered)) {
WHERE meta_key IN ('aksrt_checkin_continuous_day','checkin_continuous_day') $days = max(0, (int) floor((time() - strtotime($user->user_registered)) / DAY_IN_SECONDS));
ORDER BY CAST(meta_value AS UNSIGNED) DESC return sprintf(__('加入 %d 天', 'aksrt'), $days);
LIMIT %d", }
$count * 2 return '';
), ARRAY_A); case 'last_login':
$seen = []; $t = (string) get_user_meta($user_id, 'aksrt_last_login', true);
foreach ((array) $rows as $row) { if ($t === '') {
$uid = (int) $row['user_id']; $t = (string) get_user_meta($user_id, 'last_login', true);
if (isset($seen[$uid])) continue; }
$seen[$uid] = true; return $t !== '' ? sprintf(__('最近登录 %s', 'aksrt'), $t) : '';
$user = get_userdata($uid); case 'last_checkin':
if ($user) { $t = (string) get_user_meta($user_id, 'aksrt_last_checkin', true);
$users[] = [ if ($t === '') {
'id' => $uid, $t = (string) get_user_meta($user_id, 'last_checkin', true);
'name' => $user->display_name, }
'value' => number_format_i18n((int) $row['val']) . ' 天', return $t !== '' ? sprintf(__('最近签到 %s', 'aksrt'), $t) : '';
'desc' => $this->user_extra_desc($uid, $user, $desc_mode), case 'desc':
]; default:
} $bio = get_user_meta($user_id, 'description', true);
if (count($users) >= $count) break; if (!is_string($bio) || $bio === '') {
} $bio = $user ? (string) $user->description : '';
break; }
return $bio !== '' ? wp_trim_words($bio, 12) : '';
}
}
case 'followers': public function form($instance): void {
if (class_exists('\AKSRT\User\Follow')) { $title = !empty($instance['title']) ? $instance['title'] : __('排行榜', 'aksrt');
global $wpdb; $rank_by = !empty($instance['rank_by']) ? $instance['rank_by'] : 'credits';
$follow_table = \AKSRT\Core\DBMigrator::table('follow'); $count = !empty($instance['count']) ? (int) $instance['count'] : 5;
if ($follow_table) { $show_avatar = !isset($instance['show_avatar']) || !empty($instance['show_avatar']);
$rows = $wpdb->get_results($wpdb->prepare( ?>
"SELECT following_id, COUNT(*) as cnt <p>
FROM {$follow_table} <label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
GROUP BY following_id <input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
ORDER BY cnt DESC </p>
LIMIT %d", <p>
$count <label for="<?php echo esc_attr($this->get_field_id('rank_by')); ?>"><?php esc_html_e('排行依据:', 'aksrt'); ?></label>
), ARRAY_A); <select class="widefat" id="<?php echo esc_attr($this->get_field_id('rank_by')); ?>" name="<?php echo esc_attr($this->get_field_name('rank_by')); ?>">
foreach ((array) $rows as $row) { <option value="credits" <?php selected($rank_by, 'credits'); ?>><?php esc_html_e('积分', 'aksrt'); ?></option>
$user = get_userdata((int) $row['following_id']); <option value="posts" <?php selected($rank_by, 'posts'); ?>><?php esc_html_e('文章数', 'aksrt'); ?></option>
if ($user) { <option value="followers" <?php selected($rank_by, 'followers'); ?>><?php esc_html_e('粉丝数', 'aksrt'); ?></option>
$users[] = [ <option value="checkin_all_day" <?php selected($rank_by, 'checkin_all_day'); ?>><?php esc_html_e('累计签到', 'aksrt'); ?></option>
'id' => (int) $row['following_id'], <option value="checkin_continuous_day" <?php selected($rank_by, 'checkin_continuous_day'); ?>><?php esc_html_e('连续签到', 'aksrt'); ?></option>
'name' => $user->display_name, </select>
'value' => number_format_i18n((int) $row['cnt']) . ' 粉丝', </p>
'desc' => $this->user_extra_desc((int) $row['following_id'], $user, $desc_mode), <p>
]; <label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('显示数量:', 'aksrt'); ?></label>
} <input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" value="<?php echo esc_attr((string) $count); ?>" min="1" max="20">
} </p>
} <p>
} <label>
break; <input type="checkbox" id="<?php echo esc_attr($this->get_field_id('show_avatar')); ?>" name="<?php echo esc_attr($this->get_field_name('show_avatar')); ?>" value="1" <?php checked($show_avatar); ?>>
} <?php esc_html_e('显示头像', 'aksrt'); ?>
</label>
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('desc')); ?>"><?php esc_html_e('附加显示:', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('desc')); ?>" name="<?php echo esc_attr($this->get_field_name('desc')); ?>">
<option value="desc" <?php selected(($instance['desc'] ?? 'desc'), 'desc'); ?>><?php esc_html_e('用户签名', 'aksrt'); ?></option>
<option value="user_registered" <?php selected(($instance['desc'] ?? ''), 'user_registered'); ?>><?php esc_html_e('加入天数', 'aksrt'); ?></option>
<option value="last_login" <?php selected(($instance['desc'] ?? ''), 'last_login'); ?>><?php esc_html_e('最后登录', 'aksrt'); ?></option>
<option value="last_checkin" <?php selected(($instance['desc'] ?? ''), 'last_checkin'); ?>><?php esc_html_e('最后签到', 'aksrt'); ?></option>
</select>
</p>
<p>
<label>
<input type="checkbox" name="<?php echo esc_attr($this->get_field_name('checkin_btn')); ?>" value="1" <?php checked(!empty($instance['checkin_btn'])); ?>>
<?php esc_html_e('顶部显示签到按钮', 'aksrt'); ?>
</label>
</p>
<p>
<label>
<input type="checkbox" name="<?php echo esc_attr($this->get_field_name('top_badge')); ?>" value="1" <?php checked(!empty($instance['top_badge'])); ?>>
<?php esc_html_e('前三名 TOP 角标', 'aksrt'); ?>
</label>
</p>
<?php
}
return $users; public function update($new_instance, $old_instance): array {
} $instance = [];
$instance['title'] = sanitize_text_field($new_instance['title'] ?? '');
$instance['rank_by'] = in_array($new_instance['rank_by'] ?? '', ['credits', 'posts', 'followers', 'checkin_all_day', 'checkin_continuous_day'], true) ? $new_instance['rank_by'] : 'credits';
private function user_extra_desc(int $user_id, $user, string $mode): string { $instance['count'] = max(1, min(20, (int) ($new_instance['count'] ?? 5)));
switch ($mode) { $instance['show_avatar'] = !empty($new_instance['show_avatar']) ? 1 : 0;
case 'user_registered': $instance['checkin_btn'] = !empty($new_instance['checkin_btn']) ? 1 : 0;
if ($user && !empty($user->user_registered)) { $instance['top_badge'] = !empty($new_instance['top_badge']) ? 1 : 0;
$days = max(0, (int) floor((time() - strtotime($user->user_registered)) / DAY_IN_SECONDS)); $instance['desc'] = in_array($new_instance['desc'] ?? '', ['desc', 'user_registered', 'last_login', 'last_checkin'], true) ? $new_instance['desc'] : 'desc';
return sprintf(__('加入 %d 天', 'aksrt'), $days); return $instance;
} }
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');
$rank_by = !empty($instance['rank_by']) ? $instance['rank_by'] : 'credits';
$count = !empty($instance['count']) ? (int) $instance['count'] : 5;
$show_avatar = !empty($instance['show_avatar']);
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('rank_by')); ?>"><?php esc_html_e('排行依据:', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('rank_by')); ?>" name="<?php echo esc_attr($this->get_field_name('rank_by')); ?>">
<option value="credits" <?php selected($rank_by, 'credits'); ?>><?php esc_html_e('积分', 'aksrt'); ?></option>
<option value="posts" <?php selected($rank_by, 'posts'); ?>><?php esc_html_e('文章数', 'aksrt'); ?></option>
<option value="followers" <?php selected($rank_by, 'followers'); ?>><?php esc_html_e('粉丝数', 'aksrt'); ?></option>
<option value="checkin_all_day" <?php selected($rank_by, 'checkin_all_day'); ?>><?php esc_html_e('累计签到', 'aksrt'); ?></option>
<option value="checkin_continuous_day" <?php selected($rank_by, 'checkin_continuous_day'); ?>><?php esc_html_e('连续签到', 'aksrt'); ?></option>
</select>
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('显示数量:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" value="<?php echo esc_attr((string) $count); ?>" min="1" max="20">
</p>
<p>
<label>
<input type="checkbox" id="<?php echo esc_attr($this->get_field_id('show_avatar')); ?>" name="<?php echo esc_attr($this->get_field_name('show_avatar')); ?>" value="1" <?php checked($show_avatar); ?>>
<?php esc_html_e('显示头像', 'aksrt'); ?>
</label>
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('desc')); ?>"><?php esc_html_e('附加显示:', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('desc')); ?>" name="<?php echo esc_attr($this->get_field_name('desc')); ?>">
<option value="desc" <?php selected(($instance['desc'] ?? 'desc'), 'desc'); ?>><?php esc_html_e('用户签名', 'aksrt'); ?></option>
<option value="user_registered" <?php selected(($instance['desc'] ?? ''), 'user_registered'); ?>><?php esc_html_e('加入天数', 'aksrt'); ?></option>
<option value="last_login" <?php selected(($instance['desc'] ?? ''), 'last_login'); ?>><?php esc_html_e('最后登录', 'aksrt'); ?></option>
<option value="last_checkin" <?php selected(($instance['desc'] ?? ''), 'last_checkin'); ?>><?php esc_html_e('最后签到', 'aksrt'); ?></option>
</select>
</p>
<p>
<label>
<input type="checkbox" name="<?php echo esc_attr($this->get_field_name('checkin_btn')); ?>" value="1" <?php checked(!empty($instance['checkin_btn'])); ?>>
<?php esc_html_e('顶部显示签到按钮', 'aksrt'); ?>
</label>
</p>
<p>
<label>
<input type="checkbox" name="<?php echo esc_attr($this->get_field_name('top_badge')); ?>" value="1" <?php checked(!empty($instance['top_badge'])); ?>>
<?php esc_html_e('前三名 TOP 角标', 'aksrt'); ?>
</label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
$instance = [];
$instance['title'] = sanitize_text_field($new_instance['title'] ?? '');
$instance['rank_by'] = in_array($new_instance['rank_by'] ?? '', ['credits', 'posts', 'followers', 'checkin_all_day', 'checkin_continuous_day'], true) ? $new_instance['rank_by'] : 'credits';
$instance['count'] = max(1, min(20, (int) ($new_instance['count'] ?? 5)));
$instance['show_avatar'] = !empty($new_instance['show_avatar']);
$instance['checkin_btn'] = !empty($new_instance['checkin_btn']);
$instance['top_badge'] = !empty($new_instance['top_badge']);
$instance['desc'] = in_array($new_instance['desc'] ?? '', ['desc', 'user_registered', 'last_login', 'last_checkin'], true) ? $new_instance['desc'] : 'desc';
return $instance;
}
} }