Files
wordpress-theme-aksrt/inc/classes/forum/class-aksrt-forum-board-service.php
T

400 lines
15 KiB
PHP

<?php
/**
* Forum board / plate service.
*
* Extracted from AKSRT_Forum to isolate board & plate CRUD, hierarchy,
* view assembly, access checks, and follow handling into a cohesive unit.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
class AKSRT_Forum_Board_Service {
use AKSRT_Forum_Board_Service_Part_1;
use AKSRT_Forum_Board_Service_Part_2;
use AKSRT_Forum_Board_Service_Part_3;
/** @var AKSRT_Forum */
private $forum;
public function __construct(AKSRT_Forum $forum) {
$this->forum = $forum;
}
/**
* Get board list (plate CPT).
*
* 读取 aksrt_forum_plate CPT,支持层级查询。
* Fallback:若 plate CPT 不存在或迁移未完成,降级读取 taxonomy。
*/
public function get_boards(array $args = []): array {
// 优先使用 plate CPT
if ($this->forum->is_plate_cpt_active()) {
return $this->get_boards_from_cpt($args);
}
// Fallback: 读取旧 taxonomy(向后兼容)
return $this->get_boards_from_taxonomy();
}
/**
* 从 plate CPT 读取板块列表(新实现)。
*/
private function get_boards_from_cpt(array $args = []): array {
$defaults = [
'post_type' => 'aksrt_forum_plate',
'post_status' => 'publish',
'posts_per_page' => -1,
'orderby' => 'menu_order',
'order' => 'ASC',
'hierarchical' => true,
];
$query_args = wp_parse_args($args, $defaults);
$plates = get_posts($query_args);
if (empty($plates)) {
return [];
}
$boards = [];
foreach ($plates as $plate) {
$plate_id = (int) $plate->ID;
$moderators = $this->forum->get_plate_moderators($plate_id);
$applications = $this->forum->get_plate_moderator_applications($plate_id);
$boards[] = [
'id' => $plate_id,
'name' => (string) $plate->post_title,
'description' => (string) $plate->post_excerpt,
'content' => (string) $plate->post_content,
'icon' => (string) get_post_meta($plate_id, '_aksrt_icon', true),
'sort_order' => (int) $plate->menu_order,
'post_count' => (int) get_post_meta($plate_id, '_aksrt_posts_count', true),
'reply_count' => (int) get_post_meta($plate_id, '_aksrt_reply_count', true),
'today_reply_count' => (int) get_post_meta($plate_id, '_aksrt_today_reply_count', true),
'views' => (int) get_post_meta($plate_id, '_aksrt_views', true),
'follow_count' => (int) get_post_meta($plate_id, '_aksrt_follow_count', true),
'last_reply' => (string) get_post_meta($plate_id, '_aksrt_last_reply', true),
'last_post' => (string) get_post_meta($plate_id, '_aksrt_last_post', true),
'moderator_count' => count($moderators),
'pending_application_count' => count(array_filter($applications, static function ($item): bool {
return (string) ($item['status'] ?? '') === AKSRT_Forum::APPLICATION_PENDING;
})),
'url' => (string) get_permalink($plate),
'author_id' => (int) $plate->post_author,
'parent_id' => (int) $plate->post_parent,
'created_at' => (string) $plate->post_date,
'thumbnail_url' => (string) get_the_post_thumbnail_url($plate, 'thumbnail'),
];
}
return $this->build_hierarchical_boards($boards);
}
/**
* 从 taxonomy 读取板块(向后兼容)。
*/
private function get_boards_from_taxonomy(): array {
$terms = get_terms([
'taxonomy' => 'aksrt_forum_board',
'hide_empty' => false,
'orderby' => 'meta_value_num',
'meta_key' => '_aksrt_sort_order',
'order' => 'ASC',
]);
if (is_wp_error($terms) || empty($terms)) {
return [];
}
$boards = [];
foreach ($terms as $term) {
$board_id = (int) $term->term_id;
$moderators = $this->forum->get_board_moderators($board_id);
$applications = $this->forum->get_board_moderator_applications($board_id);
$board_link = get_term_link($term);
$boards[] = [
'id' => $board_id,
'name' => (string) $term->name,
'description' => (string) $term->description,
'content' => '',
'icon' => (string) get_term_meta($board_id, '_aksrt_icon', true),
'sort_order' => (int) get_term_meta($board_id, '_aksrt_sort_order', true),
'post_count' => (int) $term->count,
'reply_count' => 0,
'today_reply_count' => 0,
'views' => 0,
'follow_count' => 0,
'last_reply' => '',
'last_post' => '',
'moderator_count' => count($moderators),
'pending_application_count' => count(array_filter($applications, static function ($item): bool {
return (string) ($item['status'] ?? '') === AKSRT_Forum::APPLICATION_PENDING;
})),
'url' => !is_wp_error($board_link) ? (string) $board_link : '',
'author_id' => 0,
'parent_id' => (int) $term->parent,
'created_at' => '',
'thumbnail_url' => '',
];
}
return $boards;
}
/**
* 构建层级板块树(子板块嵌套)。
*/
private function build_hierarchical_boards(array $boards): array {
$by_id = [];
foreach ($boards as $board) {
$by_id[$board['id']] = $board;
$by_id[$board['id']]['children'] = [];
}
$roots = [];
foreach ($boards as $board) {
if ($board['parent_id'] > 0 && isset($by_id[$board['parent_id']])) {
$by_id[$board['parent_id']]['children'][] = &$by_id[$board['id']];
} else {
$roots[] = &$by_id[$board['id']];
}
}
return $roots;
}
/**
* Create board (统一入口,自动选择 CPT/taxonomy).
*/
public function create_board(array $data): int {
if ($this->forum->is_plate_cpt_active()) {
return $this->create_plate($data);
}
// Fallback: taxonomy
$name = sanitize_text_field((string) ($data['name'] ?? ''));
if ($name === '') {
return 0;
}
$result = wp_insert_term($name, 'aksrt_forum_board', [
'description' => sanitize_textarea_field((string) ($data['description'] ?? '')),
]);
if (is_wp_error($result)) {
return 0;
}
$term_id = (int) $result['term_id'];
$icon = sanitize_text_field((string) ($data['icon'] ?? ''));
$sort_order = (int) ($data['sort_order'] ?? $term_id);
if ($icon !== '') {
update_term_meta($term_id, '_aksrt_icon', $icon);
}
update_term_meta($term_id, '_aksrt_sort_order', $sort_order);
return $term_id;
}
/**
* Update board (统一入口,自动选择 CPT/taxonomy).
*/
public function update_board(int $board_id, array $data): bool {
if ($this->forum->is_plate_cpt_active()) {
return $this->update_plate($board_id, $data);
}
// Fallback: taxonomy
$term = get_term($board_id, 'aksrt_forum_board');
if (!$term || is_wp_error($term)) {
return false;
}
$update = ['taxonomy' => 'aksrt_forum_board'];
if (isset($data['name'])) {
$name = sanitize_text_field((string) $data['name']);
if ($name !== '') {
$update['name'] = $name;
}
}
if (isset($data['description'])) {
$update['description'] = sanitize_textarea_field((string) $data['description']);
}
$result = wp_update_term($board_id, 'aksrt_forum_board', $update);
if (is_wp_error($result)) {
return false;
}
if (isset($data['icon'])) {
update_term_meta($board_id, '_aksrt_icon', sanitize_text_field((string) $data['icon']));
}
if (isset($data['sort_order'])) {
update_term_meta($board_id, '_aksrt_sort_order', (int) $data['sort_order']);
}
return true;
}
/**
* Delete board (统一入口,自动选择 CPT/taxonomy).
*/
public function delete_board(int $board_id): bool {
if ($this->forum->is_plate_cpt_active()) {
return $this->delete_plate($board_id);
}
// Fallback: taxonomy
$term = get_term($board_id, 'aksrt_forum_board');
if (!$term || is_wp_error($term)) {
return false;
}
$result = wp_delete_term($board_id, 'aksrt_forum_board');
return !is_wp_error($result) && !empty($result);
}
/**
* Reorder boards (统一入口).
*/
public function reorder_boards(array $order_map): bool {
if ($this->forum->is_plate_cpt_active()) {
return $this->reorder_plates($order_map);
}
foreach ($order_map as $term_id => $sort_order) {
$term_id = (int) $term_id;
if ($term_id <= 0) {
continue;
}
update_term_meta($term_id, '_aksrt_sort_order', (int) $sort_order);
}
return true;
}
// ================================================================
// §7.0 Plate CPT methods —板块 CPT 化 CRUD
// ================================================================
/**
* Get all plates (板块 CPT posts).
*
* @return array<int, array<string, mixed>>
*/
public function get_plates(array $args = []): array {
$query_args = [
'post_type' => 'aksrt_forum_plate',
'post_status' => 'publish',
'posts_per_page' => (int) ($args['limit'] ?? 100),
'orderby' => 'menu_order',
'order' => 'ASC',
];
if (!empty($args['parent'])) {
$query_args['post_parent'] = (int) $args['parent'];
}
if (!empty($args['plate_cat'])) {
$query_args['tax_query'] = [[
'taxonomy' => 'aksrt_plate_cat',
'field' => 'term_id',
'terms' => (int) $args['plate_cat'],
]];
}
$posts = get_posts($query_args);
$plates = [];
foreach ($posts as $post) {
$plates[] = $this->build_plate_payload($post);
}
return $plates;
}
/**
* Get single plate by ID.
*/
public function get_plate(int $plate_id): ?array {
$post = get_post($plate_id);
if (!$post || $post->post_type !== 'aksrt_forum_plate') {
return null;
}
return $this->build_plate_payload($post);
}
/**
* Build plate data payload from WP_Post.
*/
public function build_plate_payload(\WP_Post $post): array {
$plate_id = (int) $post->ID;
$moderators = get_post_meta($plate_id, '_aksrt_moderators', true);
if (!is_array($moderators)) {
$moderators = [];
}
$cover_url = (string) get_the_post_thumbnail_url($plate_id, 'medium');
if ($cover_url === '') {
$config = $this->forum->get_config();
$default_thumbs = $config['other']['bbs_term_thumb'] ?? [];
if (!empty($default_thumbs) && is_array($default_thumbs)) {
$cover_url = $default_thumbs[array_rand($default_thumbs)];
}
}
$plate_cats = wp_get_object_terms($plate_id, 'aksrt_plate_cat', ['fields' => 'all']);
$plate_cat = (!is_wp_error($plate_cats) && !empty($plate_cats)) ? [
'id' => (int) $plate_cats[0]->term_id,
'name' => (string) $plate_cats[0]->name,
'slug' => (string) $plate_cats[0]->slug,
] : null;
return [
'id' => $plate_id,
'name' => (string) $post->post_title,
'description' => (string) $post->post_excerpt,
'content' => (string) $post->post_content,
'icon' => (string) get_post_meta($plate_id, '_aksrt_icon', true),
'cover_url' => $cover_url,
'sort_order' => (int) $post->menu_order,
'parent_id' => (int) $post->post_parent,
'author_id' => (int) $post->post_author,
'post_count' => (int) get_post_meta($plate_id, '_aksrt_posts_count', true),
'reply_count' => (int) get_post_meta($plate_id, '_aksrt_reply_count', true),
'today_reply_count' => (int) get_post_meta($plate_id, '_aksrt_today_reply_count', true),
'views' => (int) get_post_meta($plate_id, '_aksrt_views', true),
'follow_count' => (int) get_post_meta($plate_id, '_aksrt_follow_count', true),
'last_reply' => (string) get_post_meta($plate_id, '_aksrt_last_reply', true),
'last_post' => (string) get_post_meta($plate_id, '_aksrt_last_post', true),
'moderator_count' => count($moderators),
'moderators' => $moderators,
'url' => (string) get_permalink($plate_id),
'created_at' => (string) $post->post_date,
'allow_view' => (string) (get_post_meta($plate_id, '_aksrt_allow_view', true) ?: 'public'),
'add_limit' => (string) (get_post_meta($plate_id, '_aksrt_add_limit', true) ?: 'all'),
'is_hot' => $this->forum->is_hot_plate($plate_id),
'plate_cat' => $plate_cat,
// Alias fields for backward compat with get_board_view consumer
'topic_count' => (int) get_post_meta($plate_id, '_aksrt_posts_count', true),
'thumbnail_url' => $cover_url,
];
}
/**
* Create plate (CPT post).
*/
}