diff --git a/src/AKSRT/Widgets/WidgetForumPlateInfo.php b/src/AKSRT/Widgets/WidgetForumPlateInfo.php index 0a1a2957..f8f78952 100644 --- a/src/AKSRT/Widgets/WidgetForumPlateInfo.php +++ b/src/AKSRT/Widgets/WidgetForumPlateInfo.php @@ -54,11 +54,12 @@ class WidgetForumPlateInfo extends \WP_Widget { : home_url('/'); ob_start(); - echo '
'; + echo '
'; if ($thumb) { - echo '
'; + $ph = get_template_directory_uri() . '/assets/img/placeholder.svg'; + echo '
'; } elseif ($icon !== '') { - echo '
'; + echo '
'; } echo '
'; echo '' . esc_html(get_the_title($plate)) . ''; @@ -66,7 +67,7 @@ class WidgetForumPlateInfo extends \WP_Widget { echo '
' . esc_html($desc) . '
'; } if ($show_stats) { - echo '
'; + echo '
'; echo ' ' . esc_html((string) $posts) . ''; echo ' ' . esc_html((string) $reply) . ''; if ($views > 0) { diff --git a/src/AKSRT/Widgets/WidgetForumPlateModerator.php b/src/AKSRT/Widgets/WidgetForumPlateModerator.php index 5ad4a27f..c0f6c3d2 100644 --- a/src/AKSRT/Widgets/WidgetForumPlateModerator.php +++ b/src/AKSRT/Widgets/WidgetForumPlateModerator.php @@ -74,13 +74,17 @@ class WidgetForumPlateModerator extends \WP_Widget { } ob_start(); - echo '
'; + echo '
'; if ($title_text !== '') { - echo '
' . esc_html($title_text) . '
'; + echo '
' . esc_html($title_text) . '
'; } echo '
'; 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); if (!$user) { continue; @@ -105,7 +109,7 @@ class WidgetForumPlateModerator extends \WP_Widget { echo ''; } echo '
'; - return ['html' => (string) ob_get_clean()]; + return ['html' => (string) ob_get_clean()]; } public function widget($args, $instance): void { diff --git a/src/AKSRT/Widgets/WidgetForumTabTopics.php b/src/AKSRT/Widgets/WidgetForumTabTopics.php index c96212b6..f1ef2d76 100644 --- a/src/AKSRT/Widgets/WidgetForumTabTopics.php +++ b/src/AKSRT/Widgets/WidgetForumTabTopics.php @@ -88,7 +88,7 @@ class WidgetForumTabTopics extends \WP_Widget { .then(function(r){return r.json()}).then(function(j){ var d=(j&&j.data)||j||{}; var html=d.html||''; - panel.innerHTML=html||'

暂无内容

'; + panel.innerHTML=html||'
暂无内容
'; panel.setAttribute('data-loaded','1'); panel.classList.remove('is-ajax-loading'); }).catch(function(){panel.classList.remove('is-ajax-loading');}); diff --git a/src/AKSRT/Widgets/WidgetForumTopicTerms.php b/src/AKSRT/Widgets/WidgetForumTopicTerms.php index 14da9c03..b4a37aff 100644 --- a/src/AKSRT/Widgets/WidgetForumTopicTerms.php +++ b/src/AKSRT/Widgets/WidgetForumTopicTerms.php @@ -83,7 +83,7 @@ class WidgetForumTopicTerms extends \WP_Widget { continue; } $icon = (string) get_term_meta($term->term_id, '_aksrt_icon', true); - echo ''; + echo ''; if ($icon !== '') { echo ' '; } @@ -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 } // IAS-style wrap for remote refresh. - echo '
'; + echo '
'; echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo '
'; echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped diff --git a/src/AKSRT/Widgets/WidgetUserRanking.php b/src/AKSRT/Widgets/WidgetUserRanking.php index 4cf6eada..1bb5dd5a 100644 --- a/src/AKSRT/Widgets/WidgetUserRanking.php +++ b/src/AKSRT/Widgets/WidgetUserRanking.php @@ -1,5 +1,7 @@ __('按积分、文章数或粉丝数排行的用户列表。', 'aksrt')] - ); - } + public function __construct() { + parent::__construct( + 'aksrt_user_ranking', + __('用户排行榜', 'aksrt'), + [ + 'description' => __('按积分、文章数或粉丝数排行的用户列表。', 'aksrt'), + 'classname' => 'widget_aksrt_user_ranking', + ] + ); + } - public function widget($args, $instance): void { - $title = !empty($instance['title']) ? $instance['title'] : __('排行榜', 'aksrt'); - $rank_by = !empty($instance['rank_by']) ? $instance['rank_by'] : 'credits'; - $count = max(1, min(20, (int) ($instance['count'] ?? 5))); - $show_avatar = !empty($instance['show_avatar']); + public function widget($args, $instance): void { + $title = !empty($instance['title']) ? (string) $instance['title'] : __('排行榜', 'aksrt'); + $rank_by = !empty($instance['rank_by']) ? (string) $instance['rank_by'] : 'credits'; + $count = max(1, min(20, (int) ($instance['count'] ?? 5))); + $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')); - $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 - 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 + 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($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; + } - if (empty($users)) { - echo '

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

'; - 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_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 = 'TOP' . (int) $rank . ''; + } - echo '
'; - // 头像 - 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 ''; - echo ''; - echo ''; - } + echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } - // 昵称 + 可选签名/附加信息 - echo '
'; - echo '' . esc_html($user['name']) . ''; - if (!empty($user['desc'])) { - echo '
' . esc_html((string) $user['desc']) . '
'; - } - echo '
'; + /** + * @return list + */ + private function get_ranking(string $rank_by, int $count, string $desc_mode = 'desc'): array { + $users = []; - // 数值 - echo '' . esc_html($user['value']) . ''; + 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; - echo ''; - } - echo ''; + 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; - 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 { - $users = []; + 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; - 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) { - $users[] = [ - 'id' => (int) $row['user_id'], - 'name' => $user->display_name, - 'value' => number_format_i18n((int) $row['credits']) . ' 积分', - 'desc' => $this->user_extra_desc((int) $row['user_id'], $user, $desc_mode), - ]; - } - } - 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; + } - 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) . ' 篇', - 'desc' => $this->user_extra_desc($user->ID, $user, $desc_mode), - ]; - } - } - break; + return $users; + } - case 'checkin_all_day': - global $wpdb; - $rows = $wpdb->get_results($wpdb->prepare( - "SELECT user_id, meta_value as val FROM {$wpdb->usermeta} - WHERE meta_key IN ('aksrt_checkin_all_day','checkin_all_day') - ORDER BY CAST(meta_value AS UNSIGNED) DESC - LIMIT %d", - $count * 2 - ), ARRAY_A); - $seen = []; - foreach ((array) $rows as $row) { - $uid = (int) $row['user_id']; - if (isset($seen[$uid])) continue; - $seen[$uid] = true; - $user = get_userdata($uid); - if ($user) { - $users[] = [ - 'id' => $uid, - 'name' => $user->display_name, - 'value' => number_format_i18n((int) $row['val']) . ' 天', - 'desc' => $this->user_extra_desc($uid, $user, $desc_mode), - ]; - } - if (count($users) >= $count) break; - } - break; + /** + * @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; + } - case 'checkin_continuous_day': - global $wpdb; - $rows = $wpdb->get_results($wpdb->prepare( - "SELECT user_id, meta_value as val FROM {$wpdb->usermeta} - WHERE meta_key IN ('aksrt_checkin_continuous_day','checkin_continuous_day') - ORDER BY CAST(meta_value AS UNSIGNED) DESC - LIMIT %d", - $count * 2 - ), ARRAY_A); - $seen = []; - foreach ((array) $rows as $row) { - $uid = (int) $row['user_id']; - if (isset($seen[$uid])) continue; - $seen[$uid] = true; - $user = get_userdata($uid); - if ($user) { - $users[] = [ - 'id' => $uid, - 'name' => $user->display_name, - 'value' => number_format_i18n((int) $row['val']) . ' 天', - 'desc' => $this->user_extra_desc($uid, $user, $desc_mode), - ]; - } - if (count($users) >= $count) break; - } - break; + 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) : ''; + } + } - 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) { - $users[] = [ - 'id' => (int) $row['following_id'], - 'name' => $user->display_name, - 'value' => number_format_i18n((int) $row['cnt']) . ' 粉丝', - 'desc' => $this->user_extra_desc((int) $row['following_id'], $user, $desc_mode), - ]; - } - } - } - } - break; - } + 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 = !isset($instance['show_avatar']) || !empty($instance['show_avatar']); + ?> +

+ + +

+

+ + +

+

+ + +

+

+ +

+

+ + +

+

+ +

+

+ +

+ 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'); - $rank_by = !empty($instance['rank_by']) ? $instance['rank_by'] : 'credits'; - $count = !empty($instance['count']) ? (int) $instance['count'] : 5; - $show_avatar = !empty($instance['show_avatar']); - ?> -

- - -

-

- - -

-

- - -

-

- -

-

- - -

-

- -

-

- -

-