diff --git a/assets/css/frontend/widget-batch5.css b/assets/css/frontend/widget-batch5.css index a8c0a29e..835d1ac8 100644 --- a/assets/css/frontend/widget-batch5.css +++ b/assets/css/frontend/widget-batch5.css @@ -776,3 +776,14 @@ flex-direction: column; align-items: flex-start; } + +/* User ranking extras (batch8) */ +.aksrt-user-ranking__item { align-items: center; } +.aksrt-user-ranking__meta { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; } +.aksrt-user-ranking__desc { + font-size: 11px; + color: var(--muted-2-color, #999); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/src/AKSRT/Core/WidgetAjax.php b/src/AKSRT/Core/WidgetAjax.php index c16a409e..5cd0166d 100644 --- a/src/AKSRT/Core/WidgetAjax.php +++ b/src/AKSRT/Core/WidgetAjax.php @@ -1,6 +1,6 @@ 'GET', - 'callback' => [$this, 'rest_main_posts'], - 'permission_callback' => '__return_true', - ]); - register_rest_route('aksrt/v1', '/widgets/tab-posts', [ - 'methods' => 'GET', - 'callback' => [$this, 'rest_tab_posts'], - 'permission_callback' => '__return_true', - ]); - register_rest_route('aksrt/v1', '/widgets/paid-posts', [ - 'methods' => 'GET', - 'callback' => [$this, 'rest_paid_posts'], - 'permission_callback' => '__return_true', - ]); - register_rest_route('aksrt/v1', '/widgets/category-tabs', [ - 'methods' => 'GET', - 'callback' => [$this, 'rest_category_tabs'], - 'permission_callback' => '__return_true', - ]); + $routes = [ + '/widgets/posts' => 'rest_main_posts', + '/widgets/tab-posts' => 'rest_tab_posts', + '/widgets/paid-posts' => 'rest_paid_posts', + '/widgets/category-tabs' => 'rest_category_tabs', + '/widgets/hot-posts' => 'rest_hot_posts', + '/widgets/oneline-posts' => 'rest_oneline_posts', + '/widgets/recent-comments'=> 'rest_recent_comments', + '/widgets/user-lists' => 'rest_user_lists', + '/widgets/forum-topics' => 'rest_forum_topics', + '/widgets/forum-terms' => 'rest_forum_terms', + ]; + foreach ($routes as $path => $cb) { + register_rest_route('aksrt/v1', $path, [ + 'methods' => 'GET', + 'callback' => [$this, $cb], + 'permission_callback' => '__return_true', + ]); + } } public function rest_main_posts(\WP_REST_Request $request) { if (!class_exists('\AKSRT\Widgets\WidgetMainPosts')) { - return new \WP_REST_Response(['success' => false, 'message' => 'unavailable'], 500); + return $this->fail(); } $instance = [ 'count' => max(1, min(30, (int) ($request->get_param('count') ?? 6))), @@ -62,21 +61,12 @@ class WidgetAjax { 'paginate' => sanitize_key((string) ($request->get_param('paginate') ?? 'ajax')), ]; $page = max(1, (int) ($request->get_param('page') ?? $request->get_param('paged') ?? 1)); - $payload = \AKSRT\Widgets\WidgetMainPosts::query_payload($instance, $page); - return new \WP_REST_Response([ - 'success' => true, - 'html' => (string) ($payload['html'] ?? ''), - 'paginate_html' => (string) ($payload['paginate_html'] ?? ''), - 'has_more' => !empty($payload['has_more']), - 'page' => (int) ($payload['page'] ?? $page), - 'max_pages' => (int) ($payload['max_pages'] ?? 1), - 'total' => (int) ($payload['total'] ?? 0), - ], 200); + return $this->payload(\AKSRT\Widgets\WidgetMainPosts::query_payload($instance, $page), $page); } public function rest_tab_posts(\WP_REST_Request $request) { if (!class_exists('\AKSRT\Widgets\TabPostsWidget')) { - return new \WP_REST_Response(['success' => false, 'message' => 'unavailable'], 500); + return $this->fail(); } $default_count = max(1, min(20, (int) ($request->get_param('count') ?? 5))); $tab = [ @@ -94,15 +84,12 @@ class WidgetAjax { } else { $html = '
' . $html . '
'; } - return new \WP_REST_Response([ - 'success' => true, - 'html' => $html, - ], 200); + return new \WP_REST_Response(['success' => true, 'html' => $html], 200); } public function rest_paid_posts(\WP_REST_Request $request) { if (!class_exists('\AKSRT\Widgets\WidgetPaidPosts')) { - return new \WP_REST_Response(['success' => false, 'message' => 'unavailable'], 500); + return $this->fail(); } $instance = [ 'count' => max(1, min(20, (int) ($request->get_param('count') ?? 6))), @@ -110,16 +97,7 @@ class WidgetAjax { 'paginate' => sanitize_key((string) ($request->get_param('paginate') ?? 'ajax')), ]; $page = max(1, (int) ($request->get_param('page') ?? $request->get_param('paged') ?? 1)); - $payload = \AKSRT\Widgets\WidgetPaidPosts::query_payload($instance, $page); - return new \WP_REST_Response([ - 'success' => true, - 'html' => (string) ($payload['html'] ?? ''), - 'paginate_html' => (string) ($payload['paginate_html'] ?? ''), - 'has_more' => !empty($payload['has_more']), - 'page' => (int) ($payload['page'] ?? $page), - 'max_pages' => (int) ($payload['max_pages'] ?? 1), - 'total' => (int) ($payload['total'] ?? 0), - ], 200); + return $this->payload(\AKSRT\Widgets\WidgetPaidPosts::query_payload($instance, $page), $page); } public function rest_category_tabs(\WP_REST_Request $request) { @@ -152,7 +130,111 @@ class WidgetAjax { echo ''; } wp_reset_postdata(); - $html = (string) ob_get_clean(); + return new \WP_REST_Response(['success' => true, 'html' => (string) ob_get_clean()], 200); + } + + public function rest_hot_posts(\WP_REST_Request $request) { + if (!class_exists('\AKSRT\Widgets\HotPostsWidget')) { + return $this->fail(); + } + $instance = [ + 'count' => max(1, min(20, (int) ($request->get_param('count') ?? 5))), + 'order_by' => sanitize_key((string) ($request->get_param('order_by') ?? 'views')), + 'days' => max(0, (int) ($request->get_param('days') ?? 30)), + 'show_thumb' => $request->get_param('show_thumb') === null ? 1 : (int) $request->get_param('show_thumb'), + 'paginate' => sanitize_key((string) ($request->get_param('paginate') ?? 'ajax')), + ]; + $page = max(1, (int) ($request->get_param('page') ?? 1)); + return $this->payload(\AKSRT\Widgets\HotPostsWidget::query_payload($instance, $page), $page); + } + + public function rest_oneline_posts(\WP_REST_Request $request) { + if (!class_exists('\AKSRT\Widgets\WidgetOnelinePosts')) { + return $this->fail(); + } + $instance = [ + 'count' => max(1, min(20, (int) ($request->get_param('count') ?? 8))), + 'style' => sanitize_key((string) ($request->get_param('style') ?? 'card')), + 'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'date')), + 'cat' => max(0, (int) ($request->get_param('cat') ?? 0)), + 'limit_day' => max(0, (int) ($request->get_param('limit_day') ?? 0)), + ]; + $html = \AKSRT\Widgets\WidgetOnelinePosts::render_html($instance); return new \WP_REST_Response(['success' => true, 'html' => $html], 200); } + + public function rest_recent_comments(\WP_REST_Request $request) { + if (!class_exists('\AKSRT\Widgets\RecentCommentsWidget')) { + return $this->fail(); + } + $instance = [ + 'count' => max(1, min(20, (int) ($request->get_param('count') ?? 5))), + 'show_avatar' => $request->get_param('show_avatar') === null ? 1 : (int) $request->get_param('show_avatar'), + 'outpost' => max(0, (int) ($request->get_param('outpost') ?? 0)), + 'outer' => sanitize_text_field((string) ($request->get_param('outer') ?? '')), + ]; + $html = \AKSRT\Widgets\RecentCommentsWidget::render_html($instance); + return new \WP_REST_Response(['success' => true, 'html' => $html], 200); + } + + public function rest_user_lists(\WP_REST_Request $request) { + if (!class_exists('\AKSRT\Widgets\WidgetUserLists')) { + return $this->fail(); + } + $instance = [ + 'number' => max(1, min(50, (int) ($request->get_param('number') ?? 8))), + 'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'user_registered')), + 'order' => sanitize_key((string) ($request->get_param('order') ?? 'DESC')), + 'include' => sanitize_text_field((string) ($request->get_param('include') ?? '')), + 'exclude' => sanitize_text_field((string) ($request->get_param('exclude') ?? '')), + 'hide_box' => (int) ($request->get_param('hide_box') ?? 0), + ]; + $html = \AKSRT\Widgets\WidgetUserLists::render_html($instance); + return new \WP_REST_Response(['success' => true, 'html' => $html], 200); + } + + public function rest_forum_topics(\WP_REST_Request $request) { + if (!class_exists('\AKSRT\Widgets\WidgetForumHotTopics')) { + return $this->fail(); + } + $instance = [ + 'count' => max(1, min(20, (int) ($request->get_param('count') ?? 5))), + 'period' => sanitize_key((string) ($request->get_param('period') ?? 'week')), + 'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'views')), + 'paginate' => sanitize_key((string) ($request->get_param('paginate') ?? 'ajax')), + ]; + $page = max(1, (int) ($request->get_param('page') ?? 1)); + return $this->payload(\AKSRT\Widgets\WidgetForumHotTopics::query_payload($instance, $page), $page); + } + + public function rest_forum_terms(\WP_REST_Request $request) { + if (!class_exists('\AKSRT\Widgets\WidgetForumTopicTerms')) { + return $this->fail(); + } + $instance = [ + 'number' => max(1, min(40, (int) ($request->get_param('number') ?? 12))), + 'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'count')), + 'hide_empty' => $request->get_param('hide_empty') === null ? 1 : (int) $request->get_param('hide_empty'), + 'include' => sanitize_text_field((string) ($request->get_param('include') ?? '')), + 'style' => sanitize_key((string) ($request->get_param('style') ?? 'mini')), + ]; + $html = \AKSRT\Widgets\WidgetForumTopicTerms::render_html($instance); + return new \WP_REST_Response(['success' => true, 'html' => $html], 200); + } + + private function fail(): \WP_REST_Response { + return new \WP_REST_Response(['success' => false, 'message' => 'unavailable'], 500); + } + + private function payload(array $payload, int $page): \WP_REST_Response { + return new \WP_REST_Response([ + 'success' => true, + 'html' => (string) ($payload['html'] ?? ''), + 'paginate_html' => (string) ($payload['paginate_html'] ?? ''), + 'has_more' => !empty($payload['has_more']), + 'page' => (int) ($payload['page'] ?? $page), + 'max_pages' => (int) ($payload['max_pages'] ?? 1), + 'total' => (int) ($payload['total'] ?? 0), + ], 200); + } } diff --git a/src/AKSRT/Widgets/HotPostsWidget.php b/src/AKSRT/Widgets/HotPostsWidget.php index f24d9b81..fb011cc5 100644 --- a/src/AKSRT/Widgets/HotPostsWidget.php +++ b/src/AKSRT/Widgets/HotPostsWidget.php @@ -1,5 +1,7 @@ esc_html__('按浏览量/评论数/点赞数排序的热门文章列表', 'aksrt'), - 'customize_selective_refresh' => true, - ] - ); - } + public function __construct() { + parent::__construct( + 'aksrt_hot_posts', + esc_html__('AKSRT 热榜文章', 'aksrt'), + [ + 'description' => esc_html__('按浏览量/评论数/点赞数排序的热门文章列表', 'aksrt'), + 'customize_selective_refresh' => true, + ] + ); + } - public function widget($args, $instance): void { - echo $args['before_widget']; + public static function build_query_args(array $instance, int $paged = 1): array { + $count = max(1, min(20, (int) ($instance['count'] ?? 5))); + $order_by = sanitize_key((string) ($instance['order_by'] ?? 'views')); + $days = max(0, (int) ($instance['days'] ?? 30)); + $query_args = [ + 'post_type' => 'post', + 'post_status' => 'publish', + 'posts_per_page' => $count, + 'paged' => max(1, $paged), + 'ignore_sticky_posts' => true, + ]; + if ($days > 0 && $days < 365) { + $query_args['date_query'] = [['after' => $days . ' days ago']]; + } + switch ($order_by) { + case 'comments': + $query_args['orderby'] = 'comment_count'; + $query_args['order'] = 'DESC'; + break; + case 'likes': + $query_args['meta_key'] = '_aksrt_like_count'; + $query_args['orderby'] = 'meta_value_num'; + $query_args['order'] = 'DESC'; + break; + case 'favorites': + $query_args['meta_key'] = '_aksrt_favorite_count'; + $query_args['orderby'] = 'meta_value_num'; + $query_args['order'] = 'DESC'; + break; + case 'downloads': + $query_args['meta_key'] = '_aksrt_res_download_count'; + $query_args['orderby'] = 'meta_value_num'; + $query_args['order'] = 'DESC'; + break; + case 'views': + default: + $query_args['meta_key'] = 'aksrt_post_views'; + $query_args['orderby'] = 'meta_value_num'; + $query_args['order'] = 'DESC'; + break; + } + return $query_args; + } - $title = apply_filters('widget_title', $instance['title'] ?? __('热门文章', 'aksrt'), $instance, $this->id_base); - $count = absint($instance['count'] ?? 5); - $order_by = $instance['order_by'] ?? 'views'; - $days = absint($instance['days'] ?? 30); - $show_thumb = !empty($instance['show_thumb']); + public static function render_items_html(\WP_Query $query, array $instance, int $start = 0): string { + if (!$query->have_posts()) { + return ''; + } + $show_thumb = !empty($instance['show_thumb']); + ob_start(); + $index = $start; + while ($query->have_posts()) { + $query->the_post(); + $index++; + $rank_class = $index <= 3 ? ' rank-top' : ''; + $views = (int) get_post_meta(get_the_ID(), 'aksrt_post_views', true); + ?> +
+ + + + + + +
+ +
+ + +
+
+
+ ' . $items . '' : ''; + if ($html === '' && $page === 1) { + $html = function_exists('aksrt_get_widget_null') + ? aksrt_get_widget_null(__('暂无热门文章', 'aksrt')) + : '

' . esc_html__('暂无热门文章', 'aksrt') . '

'; + } + $max_pages = max(1, (int) $q->max_num_pages); + $total = (int) $q->found_posts; + $has_more = $page < $max_pages; + $paginate_html = ''; + if ($paginate === 'ajax' && $has_more) { + $next = add_query_arg([ + 'page' => $page + 1, + 'count' => $count, + 'order_by' => sanitize_key((string) ($instance['order_by'] ?? 'views')), + 'days' => max(0, (int) ($instance['days'] ?? 30)), + 'show_thumb' => !empty($instance['show_thumb']) ? 1 : 0, + 'paginate' => 'ajax', + ], rest_url('aksrt/v1/widgets/hot-posts')); + $paginate_html = function_exists('aksrt_get_ajax_next_paginate') + ? aksrt_get_ajax_next_paginate($total, $page, $count, $next) + : '
' . esc_html__('加载更多', 'aksrt') . '
'; + } else { + $paginate_html = '
'; + } + return compact('html', 'paginate_html', 'has_more', 'page', 'max_pages', 'total'); + } - $query_args = [ - 'post_type' => 'post', - 'post_status' => 'publish', - 'posts_per_page' => $count, - 'ignore_sticky_posts' => true, - 'no_found_rows' => true, - ]; + public function widget($args, $instance): void { + echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $title = apply_filters('widget_title', $instance['title'] ?? __('热门文章', 'aksrt'), $instance, $this->id_base); + if ($title) { + echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + $instance['paginate'] = sanitize_key((string) ($instance['paginate'] ?? 'ajax')); + $payload = self::query_payload($instance, 1); + $rest = rest_url('aksrt/v1/widgets/hot-posts'); + echo '
'; + echo $payload['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $payload['paginate_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo ''; + echo '
'; + echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } - if ($days > 0 && $days < 365) { - $query_args['date_query'] = [['after' => $days . ' days ago']]; - } + public function form($instance): void { + $title = $instance['title'] ?? __('热门文章', 'aksrt'); + $count = (int) ($instance['count'] ?? 5); + $order_by = $instance['order_by'] ?? 'views'; + $days = (int) ($instance['days'] ?? 30); + ?> +

+ + +

+

+ + +

+

+ + +

+

+ + +

+

+ +

+

+ +

+ have_posts()) { - echo '
'; - $index = 0; - while ($query->have_posts()) { - $query->the_post(); - $index++; - $rank_class = $index <= 3 ? ' rank-top' : ''; - $views = (int) get_post_meta(get_the_ID(), 'aksrt_post_views', true); - ?> -
- - - - - - -
- -
- - -
-
-
- '; - wp_reset_postdata(); - } else { - echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无热门文章', 'aksrt')) : '

' . esc_html__('暂无热门文章', 'aksrt') . '

'; - } - - echo $args['after_widget']; - } - - public function form($instance): void { - $title = $instance['title'] ?? __('热门文章', 'aksrt'); - $count = (int) ($instance['count'] ?? 5); - $order_by = $instance['order_by'] ?? 'views'; - $days = (int) ($instance['days'] ?? 30); - ?> -

- - -

-

- - -

-

- - -

-

- - -

- -

- -

- sanitize_text_field($new_instance['title'] ?? ''), - 'count' => absint($new_instance['count'] ?? 5), - 'order_by' => in_array($new_instance['order_by'] ?? '', ['views', 'comments', 'likes', 'favorites', 'downloads'], true) ? $new_instance['order_by'] : 'views', - 'days' => absint($new_instance['days'] ?? 30), - 'show_thumb' => !empty($new_instance['show_thumb']) ? 1 : 0, - ]; - } + public function update($new_instance, $old_instance): array { + return [ + 'title' => sanitize_text_field($new_instance['title'] ?? ''), + 'count' => absint($new_instance['count'] ?? 5), + 'order_by' => in_array($new_instance['order_by'] ?? '', ['views', 'comments', 'likes', 'favorites', 'downloads'], true) ? $new_instance['order_by'] : 'views', + 'days' => absint($new_instance['days'] ?? 30), + 'show_thumb' => !empty($new_instance['show_thumb']) ? 1 : 0, + 'paginate' => sanitize_key((string) ($new_instance['paginate'] ?? 'ajax')) === 'none' ? 'none' : 'ajax', + ]; + } } diff --git a/src/AKSRT/Widgets/RecentCommentsWidget.php b/src/AKSRT/Widgets/RecentCommentsWidget.php index 5820e1aa..bfaa45d7 100644 --- a/src/AKSRT/Widgets/RecentCommentsWidget.php +++ b/src/AKSRT/Widgets/RecentCommentsWidget.php @@ -1,6 +1,6 @@ id_base); + public static function render_html(array $instance): string { $count = max(1, min(20, (int) ($instance['count'] ?? $instance['limit'] ?? 5))); $show_avatar = !isset($instance['show_avatar']) || !empty($instance['show_avatar']); $outpost = max(0, (int) ($instance['outpost'] ?? 0)); $outer = sanitize_text_field((string) ($instance['outer'] ?? '')); - - if ($title !== '') { - echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } - $query = [ 'number' => $count, 'status' => 'approve', @@ -37,10 +30,6 @@ class RecentCommentsWidget extends \WP_Widget { if ($outpost > 0) { $query['post__not_in'] = [$outpost]; } - if ($outer !== '') { - // Exclude comments containing outer keywords (comma separated). - // Implemented post-filter below. - } $comments = get_comments($query); if ($outer !== '' && $comments) { $keys = array_filter(array_map('trim', preg_split('/[,,]/', $outer) ?: [])); @@ -56,32 +45,40 @@ class RecentCommentsWidget extends \WP_Widget { })); } } - - if ($comments) { - echo '
'; - foreach ($comments as $comment) { - $post = get_post($comment->comment_post_ID); - if (!$post) { - continue; - } - echo '
'; - if ($show_avatar) { - echo '
' . get_avatar($comment, 32) . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } - echo '
'; - echo '
'; - echo '' . esc_html($comment->comment_author) . ''; - echo '' . esc_html(human_time_diff(strtotime($comment->comment_date), current_time('timestamp'))) . ''; - echo '
'; - echo '

' . esc_html(wp_trim_words($comment->comment_content, 15, '...')) . '

'; - echo '
' . esc_html(get_the_title($post)) . '
'; - echo '
'; - } - echo '
'; - } else { - echo '

' . esc_html__('暂无评论', 'aksrt') . '

'; + if (!$comments) { + return '

' . esc_html__('暂无评论', 'aksrt') . '

'; } + ob_start(); + echo '
'; + foreach ($comments as $comment) { + $post = get_post($comment->comment_post_ID); + if (!$post) { + continue; + } + echo '
'; + if ($show_avatar) { + echo '
' . get_avatar($comment, 32) . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + echo '
'; + echo '
'; + echo '' . esc_html($comment->comment_author) . ''; + echo '' . esc_html(human_time_diff(strtotime($comment->comment_date), current_time('timestamp'))) . ''; + echo '
'; + echo '

' . esc_html(wp_trim_words($comment->comment_content, 15, '...')) . '

'; + echo '
' . esc_html(get_the_title($post)) . '
'; + echo '
'; + } + echo '
'; + return (string) ob_get_clean(); + } + public function widget($args, $instance): void { + echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $title = apply_filters('widget_title', (string) ($instance['title'] ?? __('最新评论', 'aksrt')), $instance, $this->id_base); + if ($title !== '') { + echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + echo self::render_html($instance); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } @@ -97,6 +94,9 @@ class RecentCommentsWidget extends \WP_Widget {

+

+ +

-

- -

sanitize_text_field((string) ($new_instance['title'] ?? '')), 'count' => max(1, min(20, (int) ($new_instance['count'] ?? 5))), + 'show_avatar' => !empty($new_instance['show_avatar']) ? 1 : 0, 'outpost' => max(0, (int) ($new_instance['outpost'] ?? 0)), 'outer' => sanitize_text_field((string) ($new_instance['outer'] ?? '')), - 'show_avatar' => !empty($new_instance['show_avatar']) ? 1 : 0, ]; } } diff --git a/src/AKSRT/Widgets/WidgetForumHotTopics.php b/src/AKSRT/Widgets/WidgetForumHotTopics.php index a6cb4776..aa14b29e 100644 --- a/src/AKSRT/Widgets/WidgetForumHotTopics.php +++ b/src/AKSRT/Widgets/WidgetForumHotTopics.php @@ -1,5 +1,7 @@ __('显示论坛热门话题列表。', 'aksrt')] - ); - } + public function __construct() { + parent::__construct( + 'aksrt_forum_hot_topics', + __('论坛 - 热门话题', 'aksrt'), + ['description' => __('显示论坛热门话题列表。', 'aksrt')] + ); + } - public function widget($args, $instance): void { - $title = !empty($instance['title']) ? $instance['title'] : __('热门话题', 'aksrt'); - $count = !empty($instance['count']) ? max(1, min(20, (int) $instance['count'])) : 5; - $period = !empty($instance['period']) ? sanitize_key($instance['period']) : 'week'; + public static function build_query_args(array $instance, int $paged = 1): array { + $count = max(1, min(20, (int) ($instance['count'] ?? 5))); + $period = sanitize_key((string) ($instance['period'] ?? 'week')); + $orderby = sanitize_key((string) ($instance['orderby'] ?? 'views')); + $date_query = []; + if ($period === 'week') { + $date_query = [['after' => '7 days ago']]; + } elseif ($period === 'month') { + $date_query = [['after' => '30 days ago']]; + } + $query = [ + 'post_type' => 'aksrt_forum_topic', + 'posts_per_page' => $count, + 'paged' => max(1, $paged), + 'post_status' => 'publish', + 'date_query' => $date_query, + ]; + if ($orderby === 'comments') { + $query['orderby'] = 'comment_count'; + $query['order'] = 'DESC'; + } elseif ($orderby === 'date') { + $query['orderby'] = 'date'; + $query['order'] = 'DESC'; + } else { + $query['meta_key'] = '_aksrt_views'; + $query['orderby'] = 'meta_value_num'; + $query['order'] = 'DESC'; + } + return $query; + } - echo $args['before_widget']; - echo $args['before_title'] . esc_html($title) . $args['after_title']; + public static function render_items_html(\WP_Query $query, int $start = 0): string { + if (!$query->have_posts()) { + return ''; + } + ob_start(); + $i = $start; + while ($query->have_posts()) { + $query->the_post(); + $i++; + $views = (int) get_post_meta(get_the_ID(), '_aksrt_views', true); + echo '
  • '; + echo '' . esc_html((string) $i) . ''; + echo ''; + echo esc_html(get_the_title()); + echo ''; + echo ' ' . esc_html((string) $views) . ''; + echo '
  • '; + } + wp_reset_postdata(); + return (string) ob_get_clean(); + } - $date_query = []; - if ($period === 'week') { - $date_query = [['after' => '7 days ago']]; - } elseif ($period === 'month') { - $date_query = [['after' => '30 days ago']]; - } + /** + * @return array{html:string,paginate_html:string,has_more:bool,page:int,max_pages:int,total:int} + */ + public static function query_payload(array $instance, int $page = 1): array { + $page = max(1, $page); + $count = max(1, min(20, (int) ($instance['count'] ?? 5))); + $paginate = sanitize_key((string) ($instance['paginate'] ?? 'ajax')); + $q = new \WP_Query(self::build_query_args($instance, $page)); + $items = self::render_items_html($q, ($page - 1) * $count); + $html = $items !== '' ? '' : ''; + if ($html === '' && $page === 1) { + $html = '

    ' . esc_html__('暂无话题', 'aksrt') . '

    '; + } + $max_pages = max(1, (int) $q->max_num_pages); + $total = (int) $q->found_posts; + $has_more = $page < $max_pages; + $paginate_html = ''; + if ($paginate === 'ajax' && $has_more) { + $next = add_query_arg([ + 'page' => $page + 1, + 'count' => $count, + 'period' => sanitize_key((string) ($instance['period'] ?? 'week')), + 'orderby' => sanitize_key((string) ($instance['orderby'] ?? 'views')), + 'paginate' => 'ajax', + ], rest_url('aksrt/v1/widgets/forum-topics')); + $paginate_html = function_exists('aksrt_get_ajax_next_paginate') + ? aksrt_get_ajax_next_paginate($total, $page, $count, $next) + : '
    '; + } else { + $paginate_html = '
    '; + } + return compact('html', 'paginate_html', 'has_more', 'page', 'max_pages', 'total'); + } - $query = new \WP_Query([ - 'post_type' => 'aksrt_forum_topic', - 'posts_per_page' => $count, - 'post_status' => 'publish', - 'meta_key' => '_aksrt_views', - 'orderby' => 'meta_value_num', - 'order' => 'DESC', - 'date_query' => $date_query, - ]); + public function widget($args, $instance): void { + $title = !empty($instance['title']) ? $instance['title'] : __('热门话题', 'aksrt'); + $instance['paginate'] = sanitize_key((string) ($instance['paginate'] ?? 'ajax')); + $payload = self::query_payload($instance, 1); - if ($query->have_posts()) { - echo ''; - wp_reset_postdata(); - } else { - echo '

    ' . esc_html__('暂无话题', 'aksrt') . '

    '; - } + 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 + $rest = rest_url('aksrt/v1/widgets/forum-topics'); + echo '
    '; + echo $payload['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $payload['paginate_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo ''; + echo '
    '; + echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } - echo $args['after_widget']; - } + public function form($instance): void { + $title = !empty($instance['title']) ? $instance['title'] : __('热门话题', 'aksrt'); + $count = !empty($instance['count']) ? (int) $instance['count'] : 5; + $period = !empty($instance['period']) ? $instance['period'] : 'week'; + ?> +

    + + +

    +

    + + +

    +

    + + +

    +

    + +

    + -

    - - -

    -

    - - -

    -

    - - -

    - sanitize_text_field($new_instance['title'] ?? ''), + 'count' => max(1, min(20, (int) ($new_instance['count'] ?? 5))), + 'period' => in_array($new_instance['period'] ?? '', ['all', 'week', 'month'], true) ? $new_instance['period'] : 'week', + 'paginate' => sanitize_key((string) ($new_instance['paginate'] ?? 'ajax')) === 'none' ? 'none' : 'ajax', + ]; + } } diff --git a/src/AKSRT/Widgets/WidgetForumRecentTopics.php b/src/AKSRT/Widgets/WidgetForumRecentTopics.php index 97565ea4..2202bda8 100644 --- a/src/AKSRT/Widgets/WidgetForumRecentTopics.php +++ b/src/AKSRT/Widgets/WidgetForumRecentTopics.php @@ -1,5 +1,7 @@ __('显示论坛最新话题列表。', 'aksrt')] - ); - } + public function __construct() { + parent::__construct( + 'aksrt_forum_recent_topics', + __('论坛 - 最新话题', 'aksrt'), + ['description' => __('显示论坛最新话题列表。', 'aksrt')] + ); + } - public function widget($args, $instance): void { - $title = !empty($instance['title']) ? $instance['title'] : __('最新话题', 'aksrt'); - $count = !empty($instance['count']) ? max(1, min(20, (int) $instance['count'])) : 5; + public function widget($args, $instance): void { + $title = !empty($instance['title']) ? $instance['title'] : __('最新话题', 'aksrt'); + $instance['orderby'] = 'date'; + $instance['period'] = 'all'; + $instance['paginate'] = sanitize_key((string) ($instance['paginate'] ?? 'ajax')); + $payload = WidgetForumHotTopics::query_payload($instance, 1); - echo $args['before_widget']; - echo $args['before_title'] . esc_html($title) . $args['after_title']; + 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 + $rest = rest_url('aksrt/v1/widgets/forum-topics'); + echo '
    '; + echo $payload['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $payload['paginate_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo ''; + echo '
    '; + echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } - $query = new \WP_Query([ - 'post_type' => 'aksrt_forum_topic', - 'posts_per_page' => $count, - 'post_status' => 'publish', - 'orderby' => 'date', - 'order' => 'DESC', - ]); + public function form($instance): void { + $title = !empty($instance['title']) ? $instance['title'] : __('最新话题', 'aksrt'); + $count = !empty($instance['count']) ? (int) $instance['count'] : 5; + ?> +

    + + +

    +

    + + +

    +

    + +

    + have_posts()) { - echo ''; - wp_reset_postdata(); - } else { - echo '

    ' . esc_html__('暂无话题', 'aksrt') . '

    '; - } - - echo $args['after_widget']; - } - - public function form($instance): void { - $title = !empty($instance['title']) ? $instance['title'] : __('最新话题', 'aksrt'); - $count = !empty($instance['count']) ? (int) $instance['count'] : 5; - ?> -

    - - -

    -

    - - -

    - sanitize_text_field($new_instance['title'] ?? ''), + 'count' => max(1, min(20, (int) ($new_instance['count'] ?? 5))), + 'paginate' => sanitize_key((string) ($new_instance['paginate'] ?? 'ajax')) === 'none' ? 'none' : 'ajax', + ]; + } } diff --git a/src/AKSRT/Widgets/WidgetForumTopicTerms.php b/src/AKSRT/Widgets/WidgetForumTopicTerms.php index 26f426af..14da9c03 100644 --- a/src/AKSRT/Widgets/WidgetForumTopicTerms.php +++ b/src/AKSRT/Widgets/WidgetForumTopicTerms.php @@ -1,6 +1,6 @@ */ + public static function parse_ids_static(string $raw): array { + if ($raw === '') { + return []; + } + $ids = []; + foreach (preg_split('/[\s,]+/', $raw) ?: [] as $p) { + $id = (int) $p; + if ($id > 0) { + $ids[] = $id; + } + } + return array_values(array_unique($ids)); + } + + public static function render_html(array $instance): string { $tax = taxonomy_exists('aksrt_forum_tag') ? 'aksrt_forum_tag' : (taxonomy_exists('aksrt_forum_topic_cat') ? 'aksrt_forum_topic_cat' : ''); if ($tax === '') { - return; + return ''; } - $number = max(1, min(40, (int) ($instance['number'] ?? 12))); $orderby = sanitize_key((string) ($instance['orderby'] ?? 'count')); $hide_empty = !isset($instance['hide_empty']) || !empty($instance['hide_empty']); - $include = $this->parse_ids((string) ($instance['include'] ?? '')); + $include = self::parse_ids_static((string) ($instance['include'] ?? '')); $style = sanitize_key((string) ($instance['style'] ?? 'mini')); $term_args = [ 'taxonomy' => $tax, 'number' => $number, - 'hide_empty' => $hide_empty, + 'hide_empty' => (bool) $hide_empty, ]; if ($include) { $term_args['include'] = $include; $term_args['orderby'] = 'include'; + } elseif ($orderby === 'name') { + $term_args['orderby'] = 'name'; + $term_args['order'] = 'ASC'; + } elseif ($orderby === 'views') { + $term_args['meta_key'] = '_aksrt_views'; + $term_args['orderby'] = 'meta_value_num'; + $term_args['order'] = 'DESC'; } else { - if ($orderby === 'name') { - $term_args['orderby'] = 'name'; - $term_args['order'] = 'ASC'; - } elseif ($orderby === 'views') { - $term_args['meta_key'] = '_aksrt_views'; - $term_args['orderby'] = 'meta_value_num'; - $term_args['order'] = 'DESC'; - } else { - $term_args['orderby'] = 'count'; - $term_args['order'] = 'DESC'; - } + $term_args['orderby'] = 'count'; + $term_args['order'] = 'DESC'; } $terms = get_terms($term_args); if (is_wp_error($terms) || !$terms) { - return; + return function_exists('aksrt_get_ajax_null') + ? aksrt_get_ajax_null(__('暂无话题', 'aksrt')) + : '

    ' . esc_html__('暂无话题', 'aksrt') . '

    '; } - - echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - $title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base); - if ($title !== '') { - echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } - - $list_cls = $style === 'card' ? 'aksrt-ftag-list is-card' : 'aksrt-ftag-list is-mini'; + ob_start(); + $list_cls = $style === 'card' ? 'aksrt-ftag-list is-card ajax-item' : 'aksrt-ftag-list is-mini ajax-item'; echo '
    '; foreach ($terms as $term) { $link = get_term_link($term); @@ -84,23 +92,28 @@ class WidgetForumTopicTerms extends \WP_Widget { echo ''; } echo '
    '; + return (string) ob_get_clean(); + } + public function widget($args, $instance): void { + $html = self::render_html($instance); + if ($html === '') { + return; + } + echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base); + if ($title !== '') { + echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + // IAS-style wrap for remote refresh. + echo '
    '; + echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo '
    '; echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } - /** @return array */ private function parse_ids(string $raw): array { - if ($raw === '') { - return []; - } - $ids = []; - foreach (preg_split('/[\s,]+/', $raw) ?: [] as $p) { - $id = (int) $p; - if ($id > 0) { - $ids[] = $id; - } - } - return array_values(array_unique($ids)); + return self::parse_ids_static($raw); } public function form($instance): void { diff --git a/src/AKSRT/Widgets/WidgetOnelinePosts.php b/src/AKSRT/Widgets/WidgetOnelinePosts.php index 98a445df..5051c58e 100644 --- a/src/AKSRT/Widgets/WidgetOnelinePosts.php +++ b/src/AKSRT/Widgets/WidgetOnelinePosts.php @@ -1,6 +1,6 @@ 'post', 'post_status' => 'publish', @@ -62,20 +57,23 @@ class WidgetOnelinePosts extends \WP_Widget { $query['orderby'] = 'date'; $query['order'] = 'DESC'; } + return $query; + } - $q = new \WP_Query($query); + public static function render_html(array $instance): string { + $style = sanitize_key((string) ($instance['style'] ?? 'card')); + if (!in_array($style, ['card', 'mini', 'list'], true)) { + $style = 'card'; + } + $q = new \WP_Query(self::build_query_args($instance)); if (!$q->have_posts()) { wp_reset_postdata(); - return; + return function_exists('aksrt_get_ajax_null') + ? aksrt_get_ajax_null(__('暂无文章', 'aksrt')) + : '

    ' . esc_html__('暂无文章', 'aksrt') . '

    '; } - - echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - $title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base); - if ($title !== '') { - echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } - - echo '
    '; + ob_start(); + echo '
    '; while ($q->have_posts()) { $q->the_post(); $thumb = get_the_post_thumbnail_url(get_the_ID(), $style === 'card' ? 'medium' : 'thumbnail'); @@ -94,7 +92,20 @@ class WidgetOnelinePosts extends \WP_Widget { } echo '
    '; wp_reset_postdata(); + return (string) ob_get_clean(); + } + public function widget($args, $instance): void { + $html = self::render_html($instance); + if ($html === '') { + return; + } + echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base); + if ($title !== '') { + echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } diff --git a/src/AKSRT/Widgets/WidgetProductOneline.php b/src/AKSRT/Widgets/WidgetProductOneline.php index 22cdcd5f..81848bcd 100644 --- a/src/AKSRT/Widgets/WidgetProductOneline.php +++ b/src/AKSRT/Widgets/WidgetProductOneline.php @@ -1,5 +1,7 @@ __('AKSRT 单行横滑商品 Swiper 组件。', 'aksrt')] - ); - } + public function __construct() { + \WP_Widget::__construct( + 'aksrt_product_oneline', + __('横滑商品', 'aksrt'), + ['description' => __('AKSRT 单行横滑商品列表(AJAX 同源)。', 'aksrt')] + ); + } - public function widget($args, $instance): void { - $instance['style'] = 'oneline'; - parent::widget($args, $instance); - } + public function widget($args, $instance): void { + $instance['style'] = 'oneline'; + // Prefer no paginate for horizontal scroller (zibll loads fixed count). + if (!isset($instance['paginate'])) { + $instance['paginate'] = 'none'; + } + parent::widget($args, $instance); + } - public function form($instance): void { - $instance = wp_parse_args($instance, ['style' => 'oneline']); - parent::form($instance); - } + public function form($instance): void { + $instance = wp_parse_args($instance, ['style' => 'oneline', 'paginate' => 'none']); + parent::form($instance); + } + + public function update($new_instance, $old_instance): array { + $out = parent::update($new_instance, $old_instance); + $out['style'] = 'oneline'; + return $out; + } } diff --git a/src/AKSRT/Widgets/WidgetUserLists.php b/src/AKSRT/Widgets/WidgetUserLists.php index 52012158..8da26633 100644 --- a/src/AKSRT/Widgets/WidgetUserLists.php +++ b/src/AKSRT/Widgets/WidgetUserLists.php @@ -1,6 +1,6 @@ */ + public static function parse_ids_static(string $raw): array { + if ($raw === '') { + return []; + } + $ids = []; + foreach (preg_split('/[\s,]+/', $raw) ?: [] as $p) { + $id = (int) $p; + if ($id > 0) { + $ids[] = $id; + } + } + return array_values(array_unique($ids)); + } + + public static function render_html(array $instance): string { $number = max(1, min(50, (int) ($instance['number'] ?? 8))); $orderby = sanitize_key((string) ($instance['orderby'] ?? 'user_registered')); $order = strtoupper((string) ($instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC'; - $include = $this->parse_ids((string) ($instance['include'] ?? '')); - $exclude = $this->parse_ids((string) ($instance['exclude'] ?? '')); + $include = self::parse_ids_static((string) ($instance['include'] ?? '')); + $exclude = self::parse_ids_static((string) ($instance['exclude'] ?? '')); $hide_box = !empty($instance['hide_box']); $query = [ - 'number' => $number, - 'order' => $order, - 'fields' => ['ID', 'display_name', 'user_nicename'], + 'number' => $number, + 'order' => $order, + 'fields' => ['ID', 'display_name', 'user_nicename'], ]; - if ($include) { $query['include'] = $include; $query['orderby'] = 'include'; @@ -52,7 +66,6 @@ class WidgetUserLists extends \WP_Widget { $query['orderby'] = 'meta_value'; break; case 'followed-user-count': - // Fallback: registered date; fan ranking needs denormalized meta when available. $query['meta_key'] = 'aksrt_follower_count'; $query['orderby'] = 'meta_value_num'; break; @@ -66,11 +79,12 @@ class WidgetUserLists extends \WP_Widget { $users = get_users($query); if (!$users) { - return; + return function_exists('aksrt_get_ajax_null') + ? aksrt_get_ajax_null(__('暂无用户', 'aksrt')) + : '

    ' . esc_html__('暂无用户', 'aksrt') . '

    '; } - // If follower meta empty for all, re-query by registered when fan order requested. - if ($orderby === 'followed-user-count' && !$include) { + if ($orderby === 'followed-user-count' && !$include && class_exists('\AKSRT\User\Follow')) { $has_meta = false; foreach ($users as $u) { if ((int) get_user_meta($u->ID, 'aksrt_follower_count', true) > 0) { @@ -78,8 +92,7 @@ class WidgetUserLists extends \WP_Widget { break; } } - if (!$has_meta && class_exists('\AKSRT\User\Follow')) { - // Rank by live follower counts (small N only). + if (!$has_meta) { usort($users, static function ($a, $b) { $fa = \AKSRT\User\Follow::get_instance()->get_follower_count((int) $a->ID); $fb = \AKSRT\User\Follow::get_instance()->get_follower_count((int) $b->ID); @@ -88,13 +101,8 @@ class WidgetUserLists extends \WP_Widget { } } - echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - $title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base); - if ($title !== '') { - echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } - - $wrap = $hide_box ? 'aksrt-user-lists is-bare' : 'aksrt-user-lists'; + ob_start(); + $wrap = $hide_box ? 'aksrt-user-lists is-bare ajax-item' : 'aksrt-user-lists ajax-item'; echo '
    '; foreach ($users as $user) { $uid = (int) $user->ID; @@ -111,26 +119,22 @@ class WidgetUserLists extends \WP_Widget { echo ''; } echo '
    '; + return (string) ob_get_clean(); + } + public function widget($args, $instance): void { + $html = self::render_html($instance); + echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base); + if ($title !== '') { + echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } - /** - * @return array - */ private function parse_ids(string $raw): array { - if ($raw === '') { - return []; - } - $parts = preg_split('/[\s,]+/', $raw) ?: []; - $ids = []; - foreach ($parts as $p) { - $id = (int) $p; - if ($id > 0) { - $ids[] = $id; - } - } - return array_values(array_unique($ids)); + return self::parse_ids_static($raw); } public function form($instance): void { @@ -187,13 +191,13 @@ class WidgetUserLists extends \WP_Widget { $orderby = 'user_registered'; } return [ - 'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')), - 'number' => max(1, min(50, (int) ($new_instance['number'] ?? 8))), - 'include' => sanitize_text_field((string) ($new_instance['include'] ?? '')), - 'exclude' => sanitize_text_field((string) ($new_instance['exclude'] ?? '')), - 'orderby' => $orderby, - 'order' => strtoupper((string) ($new_instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC', - 'hide_box'=> !empty($new_instance['hide_box']) ? 1 : 0, + 'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')), + 'number' => max(1, min(50, (int) ($new_instance['number'] ?? 8))), + 'include' => sanitize_text_field((string) ($new_instance['include'] ?? '')), + 'exclude' => sanitize_text_field((string) ($new_instance['exclude'] ?? '')), + 'orderby' => $orderby, + 'order' => strtoupper((string) ($new_instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC', + 'hide_box' => !empty($new_instance['hide_box']) ? 1 : 0, ]; } }