Files
wordpress-theme-aksrt/inc/classes/forum/class-aksrt-forum-view-builder.php
T

420 lines
16 KiB
PHP

<?php
/**
* Forum view builder service.
*
* Extracted from AKSRT_Forum to isolate topic listing, home/stream views,
* user-dimension queries, topic/compose views, stats, and sticky topics
* into a cohesive read-path unit.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
class AKSRT_Forum_View_Builder {
use AKSRT_Forum_View_Builder_Part_1;
use AKSRT_Forum_View_Builder_Part_2;
private AKSRT_Forum $forum;
public function __construct(AKSRT_Forum $forum) {
$this->forum = $forum;
}
public function get_topic_list(array $args = []): array {
$board_id = (int) ($args['board_id'] ?? 0);
$limit = max(1, min(100, (int) ($args['limit'] ?? ($args['per_page'] ?? 20))));
$page = max(1, (int) ($args['page'] ?? $args['paged'] ?? 1));
$offset = array_key_exists('offset', $args)
? max(0, (int) $args['offset'])
: ($page - 1) * $limit;
$status = sanitize_key((string) ($args['status'] ?? 'publish'));
$keyword = sanitize_text_field((string) ($args['keyword'] ?? ''));
$visibility = sanitize_key((string) ($args['visibility'] ?? ''));
$review_status = sanitize_key((string) ($args['review_status'] ?? ''));
$author_id = (int) ($args['author_id'] ?? 0);
$orderby = sanitize_key((string) ($args['orderby'] ?? 'date'));
$viewer_user_id = (int) ($args['viewer_user_id'] ?? get_current_user_id());
$include_hidden = !empty($args['include_hidden']);
$query_args = [
'post_type' => 'aksrt_forum_topic',
'posts_per_page' => $limit,
'offset' => $offset,
'post_status' => $this->forum->normalize_post_status_query($status),
'order' => 'DESC',
'ignore_sticky_posts' => true,
];
if ($orderby === 'views') {
$query_args['orderby'] = 'meta_value_num';
$query_args['meta_key'] = '_aksrt_views';
} elseif ($orderby === 'recommend') {
$query_args['orderby'] = 'meta_value_num';
$query_args['meta_key'] = '_aksrt_recommend_score';
} elseif ($orderby === 'comment_count') {
$query_args['orderby'] = 'comment_count';
} elseif ($orderby === 'score') {
$query_args['orderby'] = 'meta_value_num';
$query_args['meta_key'] = '_aksrt_score';
} elseif ($orderby === 'favorites') {
$query_args['orderby'] = 'meta_value_num';
$query_args['meta_key'] = '_aksrt_favorite_count';
} else {
$query_args['orderby'] = 'date';
}
if ($keyword !== '') {
$query_args['s'] = $keyword;
}
if ($author_id > 0) {
$query_args['author'] = $author_id;
}
$plate_id = (int) ($args['plate_id'] ?? 0);
$plate_ids = $args['plate_ids'] ?? [];
if (!empty($plate_ids) && is_array($plate_ids)) {
$query_args['meta_query'][] = [
'key' => '_aksrt_plate_id',
'value' => array_map('intval', $plate_ids),
'type' => 'NUMERIC',
'compare' => 'IN',
];
} elseif ($plate_id > 0) {
$query_args['meta_query'][] = [
'key' => '_aksrt_plate_id',
'value' => $plate_id,
'type' => 'NUMERIC',
'compare' => '=',
];
} elseif ($board_id > 0) {
$query_args['tax_query'] = [[
'taxonomy' => 'aksrt_forum_board',
'field' => 'term_id',
'terms' => $board_id,
]];
}
$meta_query = [];
if ($visibility !== '') {
$meta_query[] = [
'key' => '_aksrt_visibility',
'value' => $visibility,
'compare' => '=',
];
}
if ($review_status !== '') {
$meta_query[] = [
'key' => '_aksrt_review_status',
'value' => $review_status,
'compare' => '=',
];
}
if (!empty($meta_query)) {
$query_args['meta_query'] = $meta_query;
}
$query = new WP_Query($query_args);
$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 (!$include_hidden && !$topic['can_view']) {
continue;
}
$items[] = $topic;
}
wp_reset_postdata();
return [
'items' => $items,
'total' => (int) $query->found_posts,
'max_pages' => (int) $query->max_num_pages,
'page' => $page,
];
}
/**
* Get forum homepage view.
*/
public function get_home_view(int $user_id = 0): array {
$boards = $this->forum->get_boards();
$stats = $this->get_stats();
$recent = $this->get_topic_list([
'limit' => 10,
'status' => 'publish',
'viewer_user_id' => $user_id,
]);
$hot = $this->get_topic_list([
'limit' => 8,
'status' => 'publish',
'viewer_user_id' => $user_id,
'orderby' => 'views',
]);
$applications = [];
if ($user_id > 0) {
foreach ($boards as $board) {
$applications[(int) $board['id']] = $this->forum->get_board_application_for_user($user_id, (int) $board['id']);
}
}
$config = $this->forum->get_config();
$home_tabs = $config['homepage']['tabs'] ?? [
['type' => 'follow', 'label' => '关注'],
['type' => 'synthesis', 'label' => '综合'],
['type' => 'plate', 'label' => '版块'],
];
$active_index = (int) ($config['homepage']['tab_active_index'] ?? 1);
$quick_posts = !empty($config['homepage']['quick_posts']);
return [
'boards' => $boards,
'recent_topics' => $recent['items'],
'hot_topics' => $hot['items'],
'stats' => $stats,
'permissions' => [
'can_compose' => $user_id > 0 && $this->forum->forum_user_can($user_id, 'compose_post'),
'can_manage' => $this->forum->is_forum_admin($user_id),
'can_create_plate' => $this->forum->forum_user_can($user_id, 'plate_create'),
'user_tier' => $user_id > 0 ? $this->forum->get_user_plate_tier($user_id, 0) : AKSRT_Forum::TIER_NONE,
],
'applications' => $applications,
'home_tabs' => $home_tabs,
'active_index' => $active_index,
'quick_posts' => $quick_posts,
];
}
// ── §7.10 Tab data streams ──────────────────────────────────────────
/**
* Get topics from plates the user follows.
*
* @return array{items:array,total:int,max_pages:int}
*/
public function get_follow_stream(int $user_id, int $page = 1, int $limit = 15): array {
if ($user_id <= 0) {
return ['items' => [], 'total' => 0, 'max_pages' => 1];
}
$follow_ids = get_user_meta($user_id, 'aksrt_follow_plate', true);
$follow_ids = is_array($follow_ids) ? array_filter(array_map('intval', $follow_ids)) : [];
if (empty($follow_ids)) {
return ['items' => [], 'total' => 0, 'max_pages' => 1];
}
return $this->get_topic_list([
'plate_ids' => $follow_ids,
'page' => $page,
'limit' => $limit,
'status' => 'publish',
'viewer_user_id' => $user_id,
]);
}
/**
* Get synthesis (all topics) stream sorted by date.
*
* @return array{items:array,total:int,max_pages:int}
*/
public function get_synthesis_stream(int $page = 1, int $limit = 15, int $viewer = 0): array {
return $this->get_topic_list([
'page' => $page,
'limit' => $limit,
'status' => 'publish',
'viewer_user_id' => $viewer,
]);
}
/**
* Get pending-review topics for moderators.
*
* @return array{items:array,total:int,max_pages:int}
*/
public function get_pending_stream(int $user_id, int $page = 1, int $limit = 15): array {
if ($user_id <= 0 || !$this->forum->is_forum_admin($user_id)) {
return ['items' => [], 'total' => 0, 'max_pages' => 1];
}
return $this->get_topic_list([
'page' => $page,
'limit' => $limit,
'status' => 'pending',
'viewer_user_id' => $user_id,
'include_hidden' => true,
]);
}
/**
* AJAX: Load a forum home tab stream.
*/
public function ajax_home_tab(): void {
check_ajax_referer('aksrt_public_nonce', 'nonce');
$tab_type = sanitize_key((string) ($_POST['tab_type'] ?? 'synthesis'));
$page = max(1, (int) ($_POST['page'] ?? 1));
$user_id = get_current_user_id();
switch ($tab_type) {
case 'follow':
$result = $this->get_follow_stream($user_id, $page);
break;
case 'pending':
$result = $this->get_pending_stream($user_id, $page);
break;
case 'plate':
wp_send_json_success([
'html' => '',
'type' => 'plate',
'boards' => $this->forum->get_boards(),
'total' => 0,
'max_pages' => 1,
'page' => 1,
]);
return;
default:
$result = $this->get_synthesis_stream($page, 15, $user_id);
}
ob_start();
if (!empty($result['items'])) {
foreach ($result['items'] as $topic) {
$this->render_topic_row($topic);
}
} else {
echo '<div class="aksrt-forum__empty"><i class="far fa-comment-dots"></i><p>' . esc_html__('暂无内容', 'aksrt') . '</p></div>';
}
$html = ob_get_clean();
wp_send_json_success([
'html' => $html,
'type' => $tab_type,
'total' => $result['total'],
'max_pages' => $result['max_pages'],
'page' => $page,
'has_more' => $page < $result['max_pages'],
]);
}
/**
* Render a single topic row (reusable for AJAX and initial render).
*/
public function render_topic_row(array $topic): void {
$board = !empty($topic['boards'][0]) ? $topic['boards'][0] : null;
$names = $this->forum->get_config()['names'] ?? [];
?>
<article class="aksrt-forum-topic-row">
<div class="aksrt-forum-topic-row__avatar">
<?php if (!empty($topic['author']['avatar_url'])) : ?>
<img src="<?php echo esc_url((string) $topic['author']['avatar_url']); ?>" alt="" width="40" height="40" loading="lazy">
<?php endif; ?>
</div>
<div class="aksrt-forum-topic-row__body">
<div class="aksrt-forum-topic-row__title-wrap">
<?php if (!empty($topic['sticky'])) : ?>
<span class="aksrt-forum-topic-row__badge aksrt-forum-topic-row__badge--sticky"><?php esc_html_e('置顶', 'aksrt'); ?></span>
<?php endif; ?>
<?php if (!empty($topic['featured'])) : ?>
<span class="aksrt-forum-topic-row__badge aksrt-forum-topic-row__badge--featured"><?php esc_html_e('精华', 'aksrt'); ?></span>
<?php endif; ?>
<?php if (($topic['bbs_type'] ?? '') === 'question') : ?>
<span class="aksrt-forum-topic-row__badge aksrt-forum-topic-row__badge--question"><?php echo esc_html($names['question_badge'] ?? __('提问', 'aksrt')); ?></span>
<?php endif; ?>
<h3 class="aksrt-forum-topic-row__title">
<a href="<?php echo esc_url((string) ($topic['permalink'] ?? '#')); ?>"><?php echo esc_html((string) ($topic['title'] ?? '')); ?></a>
</h3>
</div>
<div class="aksrt-forum-topic-row__meta">
<?php if ($board) : ?>
<a class="aksrt-forum-topic-row__board" href="<?php echo esc_url((string) ($board['url'] ?? '#')); ?>"><?php echo esc_html((string) ($board['name'] ?? '')); ?></a>
<?php endif; ?>
<span class="aksrt-forum-topic-row__author"><?php echo esc_html((string) ($topic['author']['display_name'] ?? '')); ?></span>
<span class="aksrt-forum-topic-row__time"><?php echo esc_html((string) ($topic['date'] ?? '')); ?></span>
<span><i class="fas fa-eye"></i> <?php echo esc_html(number_format_i18n((int) ($topic['views'] ?? 0))); ?></span>
<span><i class="fas fa-comment"></i> <?php echo esc_html(number_format_i18n((int) ($topic['comment_count'] ?? 0))); ?></span>
</div>
</div>
</article>
<?php
}
public function get_user_forum_topics(int $user_id, array $args = []): array {
$status = $args['status'] ?? 'publish';
$page = max(1, (int) ($args['page'] ?? 1));
$limit = max(1, min(50, (int) ($args['limit'] ?? 10)));
$query_args = [
'post_type' => 'aksrt_forum_topic',
'author' => $user_id,
'post_status' => $status,
'posts_per_page' => $limit,
'paged' => $page,
'orderby' => 'date',
'order' => 'DESC',
];
$query = new \WP_Query($query_args);
$items = [];
foreach ($query->posts as $post) {
$items[] = $this->forum->build_topic_payload($post, $user_id);
}
return [
'items' => $items,
'total' => (int) $query->found_posts,
'max_pages' => (int) $query->max_num_pages,
];
}
/**
* Get a user's favorited forum topics.
*
* @return array{items:array,total:int,max_pages:int}
*/
public function get_user_favorite_topics(int $user_id, int $page = 1, int $limit = 10): array {
$fav_ids = get_user_meta($user_id, 'aksrt_favorite_forum_posts', true);
$fav_ids = is_array($fav_ids) ? array_map('intval', array_filter($fav_ids)) : [];
if (empty($fav_ids)) {
return ['items' => [], 'total' => 0, 'max_pages' => 1];
}
$query = new \WP_Query([
'post_type' => 'aksrt_forum_topic',
'post__in' => $fav_ids,
'post_status' => 'publish',
'posts_per_page' => $limit,
'paged' => $page,
'orderby' => 'post__in',
]);
$items = [];
foreach ($query->posts as $post) {
$items[] = $this->forum->build_topic_payload($post, $user_id);
}
return [
'items' => $items,
'total' => (int) $query->found_posts,
'max_pages' => (int) $query->max_num_pages,
];
}
/**
* Get a user's managed plates (plates where user is moderator/author).
*
* @return array{owned:array,moderating:array}
*/
}