400 lines
15 KiB
PHP
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).
|
|
*/
|
|
}
|