forum->get_user_managed_plates($user_id); } /** * Forum stats for a user (counts, cached per request). * * @return array{topic_count:int,reply_count:int,favorite_count:int,followed_plate_count:int,managed_plate_count:int} */ public function get_user_forum_stats(int $user_id): array { $topic_count = (int) count_user_posts($user_id, 'aksrt_forum_topic', true); global $wpdb; $reply_count = (int) $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->comments} c INNER JOIN {$wpdb->posts} p ON p.ID = c.comment_post_ID WHERE c.user_id = %d AND p.post_type = 'aksrt_forum_topic' AND c.comment_approved = '1'", $user_id )); $fav_ids = get_user_meta($user_id, 'aksrt_favorite_forum_posts', true); $favorite_count = is_array($fav_ids) ? count(array_filter($fav_ids)) : 0; $follow_ids = get_user_meta($user_id, 'aksrt_follow_plate', true); $followed_plate_count = is_array($follow_ids) ? count(array_filter($follow_ids)) : 0; $managed = $this->forum->get_user_managed_plates($user_id); $managed_count = count($managed['owned']) + count($managed['moderating']); return [ 'topic_count' => $topic_count, 'reply_count' => $reply_count, 'favorite_count' => $favorite_count, 'followed_plate_count' => $followed_plate_count, 'managed_plate_count' => $managed_count, ]; } public function get_topic_view(int $topic_id, int $user_id = 0): array { $post = get_post($topic_id); if (!$post || $post->post_type !== 'aksrt_forum_topic') { return [ 'topic' => null, 'board' => null, 'permissions' => [], 'board_options' => [], 'visibility_options' => $this->forum->get_visibility_options(), 'review_options' => $this->forum->get_review_options(), ]; } $topic = $this->forum->build_topic_payload($post, $user_id); $boards = !empty($topic['boards']) ? $topic['boards'] : []; $board = !empty($boards[0]) ? $boards[0] : null; return [ 'topic' => $topic, 'board' => $board, 'permissions' => [ 'can_edit' => $this->forum->can_edit_topic($user_id, $topic_id), 'can_moderate' => $this->forum->can_moderate_topic($user_id, $topic_id), // 加锁语义统一到原生 comment_status(§7.3):comment_status=closed 或 _aksrt_locked=1 均不可回复。 'can_reply' => comments_open($topic_id) && empty($topic['locked']), ], 'board_options' => $this->forum->get_boards(), 'visibility_options' => $this->forum->get_visibility_options(), 'review_options' => $this->forum->get_review_options(), ]; } /** * Get compose page view. */ public function get_compose_view(int $user_id, array $args = []): array { $board_id = (int) ($args['board_id'] ?? 0); return [ 'boards' => $this->forum->get_boards(), 'selected_board_id' => $board_id, 'visibility_options' => $this->forum->get_visibility_options(), 'publish_mode' => $this->forum->can_user_publish_directly($user_id, $board_id) ? 'publish' : 'pending', 'draft_count' => $this->forum->get_user_draft_count($user_id), 'drafts' => $this->forum->get_user_drafts($user_id, 10), 'capabilities' => $this->forum->get_compose_capabilities($user_id), ]; } /** * Get forum stats. */ public function get_stats(): array { $boards = $this->forum->get_boards(); $topic_count = post_type_exists('aksrt_forum_topic') ? (int) (wp_count_posts('aksrt_forum_topic')->publish ?? 0) : 0; $pending_topic_count = post_type_exists('aksrt_forum_topic') ? (int) (wp_count_posts('aksrt_forum_topic')->pending ?? 0) : 0; global $wpdb; $post_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->comments} c INNER JOIN {$wpdb->posts} p ON c.comment_post_ID = p.ID WHERE p.post_type = 'aksrt_forum_topic' AND c.comment_approved = '1'" ); $moderator_count = 0; $pending_application_count = 0; foreach ($boards as $board) { $moderator_count += (int) ($board['moderator_count'] ?? 0); $pending_application_count += (int) ($board['pending_application_count'] ?? 0); } return [ 'board_count' => count($boards), 'topic_count' => $topic_count, 'post_count' => $post_count, 'pending_topic_count' => $pending_topic_count, 'moderator_count' => $moderator_count, 'pending_application_count' => $pending_application_count, ]; } public function get_sticky_topics_for_board(int $board_id): array { $viewer_user_id = get_current_user_id(); // 先取本版块内的置顶帖(plate/super),再取全局置顶帖(跨版块)。 $query = new WP_Query([ 'post_type' => 'aksrt_forum_topic', 'post_status' => 'publish', 'posts_per_page' => 20, 'ignore_sticky_posts' => true, 'no_found_rows' => true, 'orderby' => 'meta_value_num date', 'order' => 'DESC DESC', 'meta_query' => [ 'relation' => 'OR', [ 'key' => '_aksrt_sticky', 'value' => [AKSRT_Forum::STICKY_PLATE, AKSRT_Forum::STICKY_SUPER], 'compare' => 'IN', 'type' => 'NUMERIC', ], [ 'key' => '_aksrt_sticky', 'value' => AKSRT_Forum::STICKY_GLOBAL, 'compare' => '=', 'type' => 'NUMERIC', ], ], 'tax_query' => [[ 'taxonomy' => 'aksrt_forum_board', 'field' => 'term_id', 'terms' => $board_id, ]], ]); $items = []; while ($query->have_posts()) { $query->the_post(); $post = get_post(get_the_ID()); if (!$post) { continue; } $topic = $this->forum->build_topic_payload($post, $viewer_user_id); if (!$topic['can_view']) { continue; } $items[] = $topic; } wp_reset_postdata(); // 全局置顶帖可能不归属本版块,单独查询后合并去重。 $global = new WP_Query([ 'post_type' => 'aksrt_forum_topic', 'post_status' => 'publish', 'posts_per_page' => 5, 'ignore_sticky_posts' => true, 'no_found_rows' => true, 'meta_query' => [[ 'key' => '_aksrt_sticky', 'value' => AKSRT_Forum::STICKY_GLOBAL, 'compare' => '=', 'type' => 'NUMERIC', ]], ]); $existing_ids = array_map(static fn($t) => (int) $t['id'], $items); while ($global->have_posts()) { $global->the_post(); $post = get_post(get_the_ID()); if (!$post || in_array((int) $post->ID, $existing_ids, true)) { continue; } $topic = $this->forum->build_topic_payload($post, $viewer_user_id); if (!$topic['can_view']) { continue; } $items[] = $topic; } wp_reset_postdata(); // 按 sticky_level DESC, date DESC 排序(全局 > 超级 > 版块)。 usort($items, function (array $a, array $b): int { $la = (int) ($a['sticky_level'] ?? 0); $lb = (int) ($b['sticky_level'] ?? 0); if ($la !== $lb) { return $lb <=> $la; } return strcmp((string) $b['date'], (string) $a['date']); }); return array_slice($items, 0, 5); } /** * Get topic detail payload for admin. */ public function get_topic_detail(int $topic_id, int $viewer_user_id = 0): array { $post = get_post($topic_id); if (!$post || $post->post_type !== 'aksrt_forum_topic') { return [ 'topic' => null, 'options' => [ 'boards' => $this->forum->get_boards(), 'visibility' => $this->forum->get_visibility_options(), 'review' => $this->forum->get_review_options(), ], ]; } return [ 'topic' => $this->forum->build_topic_payload($post, $viewer_user_id), 'options' => [ 'boards' => $this->forum->get_boards(), 'visibility' => $this->forum->get_visibility_options(), 'review' => $this->forum->get_review_options(), ], ]; } // ── Tags + Search (extracted from facade) ── public function get_tags(): array { $terms = get_terms([ 'taxonomy' => 'aksrt_forum_tag', 'hide_empty' => true, 'orderby' => 'count', 'order' => 'DESC', ]); if (is_wp_error($terms)) { return []; } return array_map(static function ($term): array { $url = get_term_link($term); return [ 'id' => (int) $term->term_id, 'name' => (string) $term->name, 'count' => (int) $term->count, 'url' => !is_wp_error($url) ? (string) $url : '', ]; }, $terms); } /** * Get tag archive page payload. */ public function get_tag_archive_view(array $args = []): array { $term_id = (int) ($args['term_id'] ?? 0); $paged = max(1, (int) ($args['paged'] ?? 1)); $per_page = 20; $term = get_term($term_id, 'aksrt_forum_tag'); if (is_wp_error($term) || !$term) { return ['topics' => [], 'term' => null, 'total' => 0, 'max_pages' => 0]; } $query = new WP_Query([ 'post_type' => 'aksrt_forum_topic', 'posts_per_page' => $per_page, 'paged' => $paged, 'post_status' => 'publish', 'tax_query' => [[ 'taxonomy' => 'aksrt_forum_tag', 'field' => 'term_id', 'terms' => $term_id, ]], ]); $topics = []; while ($query->have_posts()) { $query->the_post(); $post = get_post(get_the_ID()); if ($post) { $topics[] = $this->forum->build_topic_payload($post, get_current_user_id()); } } wp_reset_postdata(); return [ 'topics' => $topics, 'term' => [ 'id' => (int) $term->term_id, 'name' => (string) $term->name, 'description' => (string) $term->description, 'count' => (int) $term->count, ], 'total' => (int) $query->found_posts, 'max_pages' => (int) $query->max_num_pages, ]; } public function increment_view_count(int $topic_id): void { if ($topic_id <= 0) { return; } $views = (int) get_post_meta($topic_id, '_aksrt_views', true); update_post_meta($topic_id, '_aksrt_views', $views + 1); } public function ajax_search_boards(): void { $search = sanitize_text_field((string) ($_GET['q'] ?? $_POST['q'] ?? '')); $limit = min(50, max(5, (int) ($_GET['limit'] ?? 20))); if ($this->forum->is_plate_cpt_active()) { $args = [ 'post_type' => 'aksrt_forum_plate', 'post_status' => 'publish', 'posts_per_page' => $limit, 'no_found_rows' => true, 'orderby' => 'title', 'order' => 'ASC', ]; if ($search !== '') { $args['s'] = $search; } $query = new WP_Query($args); $results = []; while ($query->have_posts()) { $query->the_post(); $p = get_post(); if ($p) { $results[] = ['id' => $p->ID, 'name' => $p->post_title]; } } wp_reset_postdata(); } else { $args = [ 'taxonomy' => 'aksrt_forum_board', 'hide_empty' => false, 'number' => $limit, 'orderby' => 'name', 'order' => 'ASC', ]; if ($search !== '') { $args['search'] = $search; } $terms = get_terms($args); $results = []; if (!is_wp_error($terms)) { foreach ($terms as $term) { $results[] = ['id' => (int) $term->term_id, 'name' => (string) $term->name]; } } } wp_send_json_success($results); } public function ajax_search_tags(): void { $search = sanitize_text_field((string) ($_GET['q'] ?? $_POST['q'] ?? '')); $limit = min(50, max(5, (int) ($_GET['limit'] ?? 20))); $args = [ 'taxonomy' => 'aksrt_forum_tag', 'hide_empty' => false, 'number' => $limit, 'orderby' => 'count', 'order' => 'DESC', ]; if ($search !== '') { $args['search'] = $search; } $terms = get_terms($args); $results = []; if (!is_wp_error($terms)) { foreach ($terms as $term) { $results[] = [ 'id' => (int) $term->term_id, 'name' => (string) $term->name, 'count' => (int) $term->count, ]; } } wp_send_json_success($results); } }