From 75e046ee7f242346a5e4e6610f1e6e97db2bfcf8 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 20 Jul 2026 21:58:32 +0800 Subject: [PATCH] feat(widgets): hot posts AJAX load-more + REST expansion HotPostsWidget query_payload; WidgetAjax routes for hot/oneline/ comments/users/forum topics/terms. --- src/AKSRT/Core/WidgetAjax.php | 180 +++++++++++---- src/AKSRT/Widgets/HotPostsWidget.php | 334 ++++++++++++++++----------- 2 files changed, 324 insertions(+), 190 deletions(-) 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) + : '
'; + } 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', + ]; + } }