diff --git a/src/AKSRT/Forum/Forum.php b/src/AKSRT/Forum/Forum.php index 5c504996..f04561e8 100644 --- a/src/AKSRT/Forum/Forum.php +++ b/src/AKSRT/Forum/Forum.php @@ -246,6 +246,40 @@ class Forum { $this->view_builder()->ajax_board_topics(); } + public function register_widget_routes(): void { + register_rest_route('aksrt/v1', '/forum/frontend/widgets/topics', [ + 'methods' => 'GET', + 'callback' => [$this, 'rest_widget_topics'], + 'permission_callback' => '__return_true', + ]); + } + + public function rest_widget_topics(\WP_REST_Request $request) { + if (!class_exists('\AKSRT\Widgets\WidgetForumTopicLists')) { + return new \WP_REST_Response(['success' => false, 'message' => 'widget unavailable'], 500); + } + $instance = [ + 'count' => max(1, min(30, (int) ($request->get_param('count') ?? 8))), + 'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'date')), + 'style' => sanitize_key((string) ($request->get_param('style') ?? 'mini')), + 'plate_id' => max(0, (int) ($request->get_param('plate_id') ?? 0)), + '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\WidgetForumTopicLists::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); + } + + + public function render_board_cards(array $boards): void { $this->view_builder()->render_board_cards($boards); } @@ -396,6 +430,7 @@ class Forum { // Public plate/board topic list (sort tabs) add_action('wp_ajax_aksrt_forum_board_topics', [$this, 'ajax_board_topics']); add_action('wp_ajax_nopriv_aksrt_forum_board_topics', [$this, 'ajax_board_topics']); + add_action('rest_api_init', [$this, 'register_widget_routes']); add_action('admin_init', [$this, 'maybe_migrate_boards'], 25); add_filter('template_include', [$this, 'template_routing']); diff --git a/src/AKSRT/Widgets/WidgetForumTopicLists.php b/src/AKSRT/Widgets/WidgetForumTopicLists.php index 72d559e1..b12ae482 100644 --- a/src/AKSRT/Widgets/WidgetForumTopicLists.php +++ b/src/AKSRT/Widgets/WidgetForumTopicLists.php @@ -1,6 +1,6 @@ esc_html__('按排序/版块筛选展示论坛帖子。', 'aksrt'), + 'description' => esc_html__('按排序/版块筛选展示论坛帖子(支持 AJAX 加载更多)。', 'aksrt'), 'classname' => 'widget_aksrt_forum_topic_lists', ] ); } - public function widget($args, $instance): void { - if (!post_type_exists('aksrt_forum_topic')) { - return; - } - - $count = max(1, min(30, (int) ($instance['count'] ?? 8))); - $orderby = sanitize_key((string) ($instance['orderby'] ?? 'date')); - $style = sanitize_key((string) ($instance['style'] ?? 'mini')); + /** + * @param array $instance + * @param int $paged + * @return array + */ + public static function build_query_args(array $instance, int $paged = 1): array { + $count = max(1, min(30, (int) ($instance['count'] ?? 8))); + $orderby = sanitize_key((string) ($instance['orderby'] ?? 'date')); $plate_id = (int) ($instance['plate_id'] ?? 0); - $current_plate = !empty($instance['current_plate']); - - if ($current_plate) { - $resolved = $this->resolve_current_plate_id(); - if ($resolved <= 0) { - return; - } - $plate_id = $resolved; - } $query = [ 'post_type' => 'aksrt_forum_topic', 'post_status' => 'publish', 'posts_per_page' => $count, + 'paged' => max(1, $paged), 'order' => 'DESC', ]; @@ -59,35 +51,41 @@ class WidgetForumTopicLists extends \WP_Widget { case 'modified': $query['orderby'] = 'modified'; break; + case 'essence': + $query['meta_query'] = [[ + 'key' => '_aksrt_essence', + 'value' => '1', + ]]; + $query['orderby'] = 'date'; + break; default: $query['orderby'] = 'date'; } if ($plate_id > 0) { - $query['meta_query'] = [[ - 'key' => '_aksrt_plate_id', - 'value' => $plate_id, - ]]; + $query['meta_query'] = array_values(array_filter(array_merge( + isset($query['meta_query']) ? $query['meta_query'] : [], + [[ + 'key' => '_aksrt_plate_id', + 'value' => $plate_id, + ]] + ))); } - $q = new \WP_Query($query); + return $query; + } + + public static function render_items_html(\WP_Query $q, string $style = 'mini'): string { if (!$q->have_posts()) { - wp_reset_postdata(); - return; + 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 - } - $list_class = $style === 'detail' ? 'aksrt-ftopic-list is-detail' : 'aksrt-ftopic-list is-mini'; + ob_start(); echo ''; wp_reset_postdata(); + return (string) ob_get_clean(); + } + + /** + * @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); + $style = sanitize_key((string) ($instance['style'] ?? 'mini')); + $paginate = sanitize_key((string) ($instance['paginate'] ?? 'ajax')); + $q = new \WP_Query(self::build_query_args($instance, $page)); + $html = self::render_items_html($q, $style); + if ($html === '' && $page === 1) { + $html = function_exists('aksrt_get_ajax_null') + ? aksrt_get_ajax_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( + array_filter([ + 'page' => $page + 1, + 'count' => max(1, min(30, (int) ($instance['count'] ?? 8))), + 'orderby' => sanitize_key((string) ($instance['orderby'] ?? 'date')), + 'style' => $style, + 'plate_id' => (int) ($instance['plate_id'] ?? 0), + 'paginate' => 'ajax', + ]), + rest_url('aksrt/v1/forum/frontend/widgets/topics') + ); + $paginate_html = function_exists('aksrt_get_ajax_next_paginate') + ? aksrt_get_ajax_next_paginate($total, $page, max(1, (int) ($instance['count'] ?? 8)), $next) + : '
' . esc_html__('加载更多', 'aksrt') . '
'; + } else { + $paginate_html = '
'; + } + return [ + 'html' => $html, + 'paginate_html' => $paginate_html, + 'has_more' => $has_more, + 'page' => $page, + 'max_pages' => $max_pages, + 'total' => $total, + ]; + } + + public function widget($args, $instance): void { + if (!post_type_exists('aksrt_forum_topic')) { + return; + } + + $count = max(1, min(30, (int) ($instance['count'] ?? 8))); + $orderby = sanitize_key((string) ($instance['orderby'] ?? 'date')); + $style = sanitize_key((string) ($instance['style'] ?? 'mini')); + $plate_id = (int) ($instance['plate_id'] ?? 0); + $current_plate = !empty($instance['current_plate']); + $paginate = sanitize_key((string) ($instance['paginate'] ?? 'ajax')); + + if ($current_plate) { + $resolved = $this->resolve_current_plate_id(); + if ($resolved <= 0) { + return; + } + $plate_id = $resolved; + } + + $instance['count'] = $count; + $instance['orderby'] = $orderby; + $instance['style'] = $style; + $instance['plate_id'] = $plate_id; + $instance['paginate'] = $paginate; + + $payload = self::query_payload($instance, 1); + $rest = rest_url('aksrt/v1/forum/frontend/widgets/topics'); + + 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 '
'; + 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 } @@ -119,58 +207,74 @@ class WidgetForumTopicLists extends \WP_Widget { } public function form($instance): void { + $defaults = [ + 'title' => __('帖子列表', 'aksrt'), + 'count' => 8, + 'orderby' => 'date', + 'style' => 'mini', + 'plate_id' => 0, + 'current_plate' => 0, + 'paginate' => 'ajax', + ]; + $instance = wp_parse_args($instance, $defaults); ?>

-

- + +

+

+

sanitize_text_field((string) ($new_instance['title'] ?? '')), 'count' => max(1, min(30, (int) ($new_instance['count'] ?? 8))), - 'orderby' => $orderby, - 'style' => ($new_instance['style'] ?? '') === 'detail' ? 'detail' : 'mini', + 'orderby' => sanitize_key((string) ($new_instance['orderby'] ?? 'date')), + 'style' => sanitize_key((string) ($new_instance['style'] ?? 'mini')), 'plate_id' => max(0, (int) ($new_instance['plate_id'] ?? 0)), 'current_plate' => !empty($new_instance['current_plate']) ? 1 : 0, + 'paginate' => sanitize_key((string) ($new_instance['paginate'] ?? 'ajax')), ]; } }