Wire Zibll-style board sort tabs, shared topic rows, page-template URLs, forum asset enqueue on home/plate, home synthesis load-more, and topic last_reply meta for last-reply ordering. No dual-read.
721 lines
27 KiB
PHP
721 lines
27 KiB
PHP
<?php
|
|
/**
|
|
* Forum moderation service.
|
|
*
|
|
* Extracted from AKSRT_Forum to isolate moderator management, applications,
|
|
* board/plate counting, and moderator role CRUD into a cohesive unit.
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
class AKSRT_Forum_Moderation_Service {
|
|
|
|
private AKSRT_Forum $forum;
|
|
|
|
public function __construct(AKSRT_Forum $forum) {
|
|
$this->forum = $forum;
|
|
}
|
|
|
|
public function apply_moderator(int $user_id, int $board_id, string $reason) {
|
|
if ($user_id <= 0) {
|
|
return new WP_Error('not_logged_in', __('请先登录', 'aksrt'));
|
|
}
|
|
|
|
if ($this->forum->is_plate_cpt_active()) {
|
|
$post = get_post($board_id);
|
|
if (!$post || $post->post_type !== 'aksrt_forum_plate') {
|
|
return new WP_Error('invalid_board', __('版块不存在', 'aksrt'));
|
|
}
|
|
} else {
|
|
$board = get_term($board_id, 'aksrt_forum_board');
|
|
if (!$board || is_wp_error($board)) {
|
|
return new WP_Error('invalid_board', __('版块不存在', 'aksrt'));
|
|
}
|
|
}
|
|
|
|
if ($this->is_board_moderator($user_id, $board_id)) {
|
|
return new WP_Error('already_moderator', __('你已经是该版块版主', 'aksrt'));
|
|
}
|
|
|
|
$applications = $this->get_board_moderator_applications($board_id);
|
|
foreach ($applications as $application) {
|
|
if ((int) ($application['user_id'] ?? 0) === $user_id && (string) ($application['status'] ?? '') === AKSRT_Forum::APPLICATION_PENDING) {
|
|
return new WP_Error('application_exists', __('你已经提交过申请,等待处理即可', 'aksrt'));
|
|
}
|
|
}
|
|
|
|
$reason = trim($reason);
|
|
if ($reason === '') {
|
|
return new WP_Error('empty_reason', __('请填写申请理由', 'aksrt'));
|
|
}
|
|
|
|
$application = [
|
|
'id' => wp_generate_uuid4(),
|
|
'board_id' => $board_id,
|
|
'user_id' => $user_id,
|
|
'reason' => sanitize_textarea_field($reason),
|
|
'status' => AKSRT_Forum::APPLICATION_PENDING,
|
|
'created_at' => current_time('mysql'),
|
|
'reviewed_at' => '',
|
|
'review_note' => '',
|
|
'reviewer_id' => 0,
|
|
];
|
|
|
|
$applications[] = $application;
|
|
if ($this->forum->is_plate_cpt_active()) {
|
|
update_post_meta($board_id, '_aksrt_moderator_applications', $applications);
|
|
} else {
|
|
update_term_meta($board_id, '_aksrt_moderator_applications', $applications);
|
|
}
|
|
|
|
return [
|
|
'message' => __('版主申请已提交', 'aksrt'),
|
|
'application' => $application,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Review a moderator application.
|
|
*
|
|
* @return array<string,mixed>|WP_Error
|
|
*/
|
|
public function review_moderator_application(string $application_id, string $status, string $note, int $reviewer_id) {
|
|
if ($reviewer_id <= 0 || !$this->forum->forum_user_can($reviewer_id, 'moderator_review')) {
|
|
return new WP_Error('forbidden', __('权限不足', 'aksrt'));
|
|
}
|
|
|
|
if (!in_array($status, [AKSRT_Forum::APPLICATION_APPROVED, AKSRT_Forum::APPLICATION_REJECTED], true)) {
|
|
return new WP_Error('invalid_status', __('申请处理状态无效', 'aksrt'));
|
|
}
|
|
|
|
$use_cpt = $this->forum->is_plate_cpt_active();
|
|
$boards = $this->forum->get_boards();
|
|
$flat_boards = $this->flatten_boards($boards);
|
|
|
|
foreach ($flat_boards as $board) {
|
|
$board_id = (int) ($board['id'] ?? 0);
|
|
$applications = $this->get_board_moderator_applications($board_id);
|
|
$updated = false;
|
|
|
|
foreach ($applications as &$application) {
|
|
if ((string) ($application['id'] ?? '') !== $application_id) {
|
|
continue;
|
|
}
|
|
|
|
$application['status'] = $status;
|
|
$application['review_note'] = sanitize_textarea_field($note);
|
|
$application['reviewed_at'] = current_time('mysql');
|
|
$application['reviewer_id'] = $reviewer_id;
|
|
$updated = true;
|
|
|
|
if ($status === AKSRT_Forum::APPLICATION_APPROVED) {
|
|
$this->add_moderator((int) ($application['user_id'] ?? 0), $board_id);
|
|
}
|
|
|
|
break;
|
|
}
|
|
unset($application);
|
|
|
|
if ($updated) {
|
|
if ($use_cpt) {
|
|
update_post_meta($board_id, '_aksrt_moderator_applications', $applications);
|
|
} else {
|
|
update_term_meta($board_id, '_aksrt_moderator_applications', $applications);
|
|
}
|
|
|
|
$reviewed_application = $this->find_application_by_id($application_id);
|
|
if ($reviewed_application) {
|
|
$reviewed_user_id = (int) ($reviewed_application['user_id'] ?? 0);
|
|
if ($reviewed_user_id > 0 && $reviewed_user_id !== $reviewer_id) {
|
|
$board_name = (string) ($board['name'] ?? __('当前版块', 'aksrt'));
|
|
$status_label = $status === AKSRT_Forum::APPLICATION_APPROVED
|
|
? __('已通过', 'aksrt')
|
|
: __('已拒绝', 'aksrt');
|
|
$payload = [
|
|
'title' => __('版主申请结果', 'aksrt'),
|
|
'target_url' => isset($board['url']) ? (string) $board['url'] : aksrt_get_forum_home_url(),
|
|
];
|
|
$clean_note = sanitize_textarea_field($note);
|
|
if ($clean_note !== '') {
|
|
$payload['reason'] = $clean_note;
|
|
}
|
|
$this->forum->notify(
|
|
$reviewed_user_id,
|
|
'system',
|
|
sprintf(__('你在「%1$s」提交的版主申请%2$s', 'aksrt'), $board_name, $status_label),
|
|
$payload
|
|
);
|
|
}
|
|
}
|
|
|
|
return [
|
|
'message' => __('申请状态已更新', 'aksrt'),
|
|
'application' => $reviewed_application,
|
|
];
|
|
}
|
|
}
|
|
|
|
return new WP_Error('application_not_found', __('申请记录不存在', 'aksrt'));
|
|
}
|
|
|
|
/**
|
|
* Flatten hierarchical boards array (collapse children into flat list).
|
|
*/
|
|
private function flatten_boards(array $boards): array {
|
|
$flat = [];
|
|
foreach ($boards as $board) {
|
|
$children = $board['children'] ?? [];
|
|
unset($board['children']);
|
|
$flat[] = $board;
|
|
if (!empty($children)) {
|
|
$flat = array_merge($flat, $this->flatten_boards($children));
|
|
}
|
|
}
|
|
return $flat;
|
|
}
|
|
|
|
/**
|
|
* Get flattened moderator application list.
|
|
*/
|
|
public function get_moderator_application_list(array $args = []): array {
|
|
$status = sanitize_key((string) ($args['status'] ?? ''));
|
|
$board_id = (int) ($args['board_id'] ?? 0);
|
|
$keyword = sanitize_text_field((string) ($args['keyword'] ?? ''));
|
|
$items = [];
|
|
|
|
foreach ($this->forum->get_boards() as $board) {
|
|
$current_board_id = (int) ($board['id'] ?? 0);
|
|
if ($board_id > 0 && $current_board_id !== $board_id) {
|
|
continue;
|
|
}
|
|
|
|
foreach ($this->get_board_moderator_applications($current_board_id) as $application) {
|
|
if ($status !== '' && (string) ($application['status'] ?? '') !== $status) {
|
|
continue;
|
|
}
|
|
|
|
$user = get_userdata((int) ($application['user_id'] ?? 0));
|
|
$row = [
|
|
'id' => (string) ($application['id'] ?? ''),
|
|
'board_id' => $current_board_id,
|
|
'board_name' => (string) ($board['name'] ?? ''),
|
|
'user_id' => (int) ($application['user_id'] ?? 0),
|
|
'display_name' => $user ? $user->display_name : '',
|
|
'user_email' => $user ? $user->user_email : '',
|
|
'avatar_url' => $user ? $this->forum->get_user_avatar_url((int) $user->ID) : '',
|
|
'reason' => (string) ($application['reason'] ?? ''),
|
|
'status' => (string) ($application['status'] ?? ''),
|
|
'status_label' => $this->get_application_status_label((string) ($application['status'] ?? '')),
|
|
'created_at' => (string) ($application['created_at'] ?? ''),
|
|
'reviewed_at' => (string) ($application['reviewed_at'] ?? ''),
|
|
'review_note' => (string) ($application['review_note'] ?? ''),
|
|
'reviewer' => $this->forum->build_user_summary((int) ($application['reviewer_id'] ?? 0)),
|
|
];
|
|
|
|
if ($keyword !== '') {
|
|
$haystack = strtolower(implode(' ', [
|
|
(string) $row['board_name'],
|
|
(string) $row['display_name'],
|
|
(string) $row['user_email'],
|
|
(string) $row['reason'],
|
|
]));
|
|
if (strpos($haystack, strtolower($keyword)) === false) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
$items[] = $row;
|
|
}
|
|
}
|
|
|
|
usort($items, static function ($left, $right): int {
|
|
return strcmp((string) ($right['created_at'] ?? ''), (string) ($left['created_at'] ?? ''));
|
|
});
|
|
|
|
return $items;
|
|
}
|
|
|
|
/**
|
|
* Sync board/plate topic count + reply count.
|
|
*/
|
|
public function sync_board_count(int $board_id): void {
|
|
if ($board_id <= 0) {
|
|
return;
|
|
}
|
|
|
|
if ($this->forum->is_plate_cpt_active()) {
|
|
$this->sync_plate_counts($board_id);
|
|
return;
|
|
}
|
|
|
|
$count = new WP_Query([
|
|
'post_type' => 'aksrt_forum_topic',
|
|
'posts_per_page' => 1,
|
|
'fields' => 'ids',
|
|
'tax_query' => [[
|
|
'taxonomy' => 'aksrt_forum_board',
|
|
'field' => 'term_id',
|
|
'terms' => $board_id,
|
|
]],
|
|
'post_status' => 'publish',
|
|
]);
|
|
|
|
update_term_meta($board_id, '_aksrt_topic_count', (int) $count->found_posts);
|
|
}
|
|
|
|
/**
|
|
* Sync plate multi-dimensional counters (§7.2 去规范化统计).
|
|
*/
|
|
public function sync_plate_counts(int $plate_id): void {
|
|
global $wpdb;
|
|
|
|
$posts_count = (int) $wpdb->get_var($wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$wpdb->postmeta} pm
|
|
INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
|
|
WHERE pm.meta_key = '_aksrt_plate_id' AND pm.meta_value = %s
|
|
AND p.post_type = 'aksrt_forum_topic' AND p.post_status = 'publish'",
|
|
(string) $plate_id
|
|
));
|
|
|
|
$reply_count = (int) $wpdb->get_var($wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$wpdb->comments} c
|
|
INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = c.comment_post_ID
|
|
WHERE pm.meta_key = '_aksrt_plate_id' AND pm.meta_value = %s
|
|
AND c.comment_approved = '1'",
|
|
(string) $plate_id
|
|
));
|
|
|
|
$today_start = current_time('Y-m-d') . ' 00:00:00';
|
|
$today_reply_count = (int) $wpdb->get_var($wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$wpdb->comments} c
|
|
INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = c.comment_post_ID
|
|
WHERE pm.meta_key = '_aksrt_plate_id' AND pm.meta_value = %s
|
|
AND c.comment_approved = '1' AND c.comment_date >= %s",
|
|
(string) $plate_id,
|
|
$today_start
|
|
));
|
|
|
|
$last_post = $wpdb->get_var($wpdb->prepare(
|
|
"SELECT p.post_date FROM {$wpdb->posts} p
|
|
INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID
|
|
WHERE pm.meta_key = '_aksrt_plate_id' AND pm.meta_value = %s
|
|
AND p.post_type = 'aksrt_forum_topic' AND p.post_status = 'publish'
|
|
ORDER BY p.post_date DESC LIMIT 1",
|
|
(string) $plate_id
|
|
));
|
|
|
|
$last_reply = $wpdb->get_var($wpdb->prepare(
|
|
"SELECT c.comment_date FROM {$wpdb->comments} c
|
|
INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = c.comment_post_ID
|
|
WHERE pm.meta_key = '_aksrt_plate_id' AND pm.meta_value = %s
|
|
AND c.comment_approved = '1'
|
|
ORDER BY c.comment_date DESC LIMIT 1",
|
|
(string) $plate_id
|
|
));
|
|
|
|
update_post_meta($plate_id, '_aksrt_posts_count', $posts_count);
|
|
update_post_meta($plate_id, '_aksrt_reply_count', $reply_count);
|
|
update_post_meta($plate_id, '_aksrt_today_reply_count', $today_reply_count);
|
|
update_post_meta($plate_id, '_aksrt_last_post', (string) ($last_post ?: ''));
|
|
update_post_meta($plate_id, '_aksrt_last_reply', (string) ($last_reply ?: ''));
|
|
}
|
|
|
|
/**
|
|
* Get board moderator IDs (统一入口,自动检测 CPT/taxonomy).
|
|
*/
|
|
public function get_board_moderators(int $board_id): array {
|
|
if ($this->forum->is_plate_cpt_active()) {
|
|
return $this->get_plate_moderators($board_id);
|
|
}
|
|
|
|
// Fallback: taxonomy
|
|
$moderators = get_term_meta($board_id, '_aksrt_moderators', true);
|
|
if (!is_array($moderators)) {
|
|
return [];
|
|
}
|
|
|
|
return array_values(array_unique(array_filter(array_map('intval', $moderators))));
|
|
}
|
|
|
|
/**
|
|
* Get plate moderators from CPT meta.
|
|
*/
|
|
public function get_plate_moderators(int $plate_id): array {
|
|
if (!post_type_exists('aksrt_forum_plate')) {
|
|
return [];
|
|
}
|
|
|
|
$moderators = get_post_meta($plate_id, '_aksrt_moderators', true);
|
|
if (!is_array($moderators)) {
|
|
return [];
|
|
}
|
|
|
|
return array_values(array_unique(array_filter(array_map('intval', $moderators))));
|
|
}
|
|
|
|
/**
|
|
* Check if a user is a forum-level admin (bbs_admin_users setting).
|
|
*
|
|
* Forum admins have cross-board governance privileges and bypass bans.
|
|
*/
|
|
public function is_forum_admin(int $user_id): bool {
|
|
if ($user_id <= 0) {
|
|
return false;
|
|
}
|
|
|
|
if (user_can($user_id, 'manage_options')) {
|
|
return true;
|
|
}
|
|
|
|
$forum = AKSRT_Settings::get_forum_settings();
|
|
$admin_users = $forum['admin_users'] ?? [];
|
|
|
|
return in_array($user_id, $admin_users, true);
|
|
}
|
|
|
|
/**
|
|
* Check if a user is a board moderator.
|
|
*/
|
|
public function is_board_moderator(int $user_id, int $board_id): bool {
|
|
if ($user_id <= 0) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->is_forum_admin($user_id)) {
|
|
return true;
|
|
}
|
|
|
|
return in_array($user_id, $this->get_board_moderators($board_id), true);
|
|
}
|
|
|
|
/**
|
|
* Get board moderator profile rows.
|
|
*/
|
|
public function get_board_moderator_profiles(int $board_id): array {
|
|
$profiles = [];
|
|
foreach ($this->get_board_moderators($board_id) as $user_id) {
|
|
$user = get_userdata($user_id);
|
|
if (!$user) {
|
|
continue;
|
|
}
|
|
|
|
$profiles[] = [
|
|
'id' => $user_id,
|
|
'display_name' => $user->display_name,
|
|
'user_email' => $user->user_email,
|
|
'avatar_url' => $this->forum->get_user_avatar_url($user_id),
|
|
];
|
|
}
|
|
|
|
return $profiles;
|
|
}
|
|
|
|
/**
|
|
* Get board moderator profiles with role information.
|
|
*/
|
|
public function get_board_moderator_profiles_with_roles(int $board_id): array {
|
|
$roles_map = $this->get_moderator_roles($board_id);
|
|
$profiles = [];
|
|
foreach ($this->get_board_moderators($board_id) as $user_id) {
|
|
$user = get_userdata($user_id);
|
|
if (!$user) continue;
|
|
$profiles[] = [
|
|
'id' => $user_id,
|
|
'display_name' => $user->display_name,
|
|
'user_email' => $user->user_email,
|
|
'avatar_url' => $this->forum->get_user_avatar_url($user_id),
|
|
'role' => $roles_map[$user_id] ?? 'moderator',
|
|
];
|
|
}
|
|
return $profiles;
|
|
}
|
|
|
|
/**
|
|
* Get moderator role map for a board: [user_id => role].
|
|
*
|
|
* In CPT mode, also maps plate author → 'plate_author' (超级版主).
|
|
*/
|
|
public function get_moderator_roles(int $board_id): array {
|
|
if ($this->forum->is_plate_cpt_active()) {
|
|
$roles = get_post_meta($board_id, '_aksrt_moderator_roles', true);
|
|
$roles = is_array($roles) ? $roles : [];
|
|
$post = get_post($board_id);
|
|
if ($post && $post->post_author > 0) {
|
|
$roles[(int) $post->post_author] = 'plate_author';
|
|
}
|
|
return $roles;
|
|
}
|
|
$roles = get_term_meta($board_id, '_aksrt_moderator_roles', true);
|
|
return is_array($roles) ? $roles : [];
|
|
}
|
|
|
|
/**
|
|
* Set a moderator's role on a board.
|
|
*/
|
|
public function set_moderator_role(int $user_id, int $board_id, string $role): bool {
|
|
$allowed = ['super_moderator', 'moderator', 'junior_moderator', 'plate_author', 'cat_moderator'];
|
|
if (!in_array($role, $allowed, true)) {
|
|
return false;
|
|
}
|
|
if (!in_array($user_id, $this->get_board_moderators($board_id), true)) {
|
|
return false;
|
|
}
|
|
$roles = $this->get_moderator_roles($board_id);
|
|
$roles[$user_id] = $role;
|
|
if ($this->forum->is_plate_cpt_active()) {
|
|
return (bool) update_post_meta($board_id, '_aksrt_moderator_roles', $roles);
|
|
}
|
|
return (bool) update_term_meta($board_id, '_aksrt_moderator_roles', $roles);
|
|
}
|
|
|
|
/**
|
|
* Add a moderator to a board with optional role.
|
|
*/
|
|
public function add_moderator(int $user_id, int $board_id, string $role = 'moderator'): bool {
|
|
$current = $this->get_board_moderators($board_id);
|
|
if (in_array($user_id, $current, true)) {
|
|
$this->set_moderator_role($user_id, $board_id, $role);
|
|
return true;
|
|
}
|
|
$current[] = $user_id;
|
|
$new_list = array_values(array_unique(array_map('intval', $current)));
|
|
if ($this->forum->is_plate_cpt_active()) {
|
|
$result = update_post_meta($board_id, '_aksrt_moderators', $new_list);
|
|
} else {
|
|
$result = update_term_meta($board_id, '_aksrt_moderators', $new_list);
|
|
}
|
|
if ($result !== false) {
|
|
$this->set_moderator_role($user_id, $board_id, $role);
|
|
}
|
|
return $result !== false;
|
|
}
|
|
|
|
/**
|
|
* Remove a moderator from a board.
|
|
*/
|
|
public function remove_moderator(int $user_id, int $board_id): bool {
|
|
$current = $this->get_board_moderators($board_id);
|
|
$current = array_values(array_diff($current, [$user_id]));
|
|
$roles = $this->get_moderator_roles($board_id);
|
|
unset($roles[$user_id]);
|
|
if ($this->forum->is_plate_cpt_active()) {
|
|
$result = update_post_meta($board_id, '_aksrt_moderators', $current);
|
|
update_post_meta($board_id, '_aksrt_moderator_roles', $roles);
|
|
} else {
|
|
$result = update_term_meta($board_id, '_aksrt_moderators', $current);
|
|
update_term_meta($board_id, '_aksrt_moderator_roles', $roles);
|
|
}
|
|
return $result !== false;
|
|
}
|
|
|
|
/**
|
|
* Get all boards a user moderates (with roles).
|
|
*/
|
|
public function get_user_moderator_boards(int $user_id): array {
|
|
$boards = [];
|
|
foreach ($this->forum->get_boards() as $board) {
|
|
$board_id = (int) ($board['id'] ?? 0);
|
|
if ($board_id <= 0) continue;
|
|
$moderators = $this->get_board_moderators($board_id);
|
|
if (in_array($user_id, $moderators, true)) {
|
|
$roles = $this->get_moderator_roles($board_id);
|
|
$boards[] = [
|
|
'board_id' => $board_id,
|
|
'board_name' => (string) ($board['name'] ?? ''),
|
|
'role' => $roles[$user_id] ?? 'moderator',
|
|
];
|
|
}
|
|
}
|
|
return $boards;
|
|
}
|
|
|
|
/**
|
|
* Check if a user has a specific minimum tier on a board/plate.
|
|
*
|
|
* Tier hierarchy (aligned with zibll 4-level):
|
|
* forum_admin(4) > cat_moderator(3) > plate_author(2) > moderator(1)
|
|
*
|
|
* @param string $min_role 'moderator'|'plate_author'|'cat_moderator'|'forum_admin'
|
|
*/
|
|
public function has_moderator_role(int $user_id, int $board_id, string $min_role = 'moderator'): bool {
|
|
$tier_map = [
|
|
'junior_moderator' => AKSRT_Forum::TIER_MODERATOR, // backward-compat alias
|
|
'moderator' => AKSRT_Forum::TIER_MODERATOR,
|
|
'plate_author' => AKSRT_Forum::TIER_PLATE_AUTHOR,
|
|
'super_moderator' => AKSRT_Forum::TIER_PLATE_AUTHOR, // backward-compat alias
|
|
'cat_moderator' => AKSRT_Forum::TIER_CAT_MOD,
|
|
'forum_admin' => AKSRT_Forum::TIER_FORUM_ADMIN,
|
|
];
|
|
$required = $tier_map[$min_role] ?? AKSRT_Forum::TIER_MODERATOR;
|
|
return $this->forum->get_user_plate_tier($user_id, $board_id) >= $required;
|
|
}
|
|
|
|
/**
|
|
* Get board moderator applications (统一入口,自动检测 CPT/taxonomy).
|
|
*/
|
|
public function get_board_moderator_applications(int $board_id): array {
|
|
if ($this->forum->is_plate_cpt_active()) {
|
|
return $this->get_plate_moderator_applications($board_id);
|
|
}
|
|
|
|
// Fallback: taxonomy
|
|
$applications = get_term_meta($board_id, '_aksrt_moderator_applications', true);
|
|
if (!is_array($applications)) {
|
|
return [];
|
|
}
|
|
|
|
return array_values(array_filter(array_map(function ($application) use ($board_id): array {
|
|
if (!is_array($application)) {
|
|
$application = [];
|
|
}
|
|
|
|
return [
|
|
'id' => (string) ($application['id'] ?? wp_generate_uuid4()),
|
|
'board_id' => $board_id,
|
|
'user_id' => (int) ($application['user_id'] ?? 0),
|
|
'reason' => (string) ($application['reason'] ?? ''),
|
|
'status' => (string) ($application['status'] ?? AKSRT_Forum::APPLICATION_PENDING),
|
|
'created_at' => (string) ($application['created_at'] ?? ''),
|
|
'reviewed_at' => (string) ($application['reviewed_at'] ?? ''),
|
|
'review_note' => (string) ($application['review_note'] ?? ''),
|
|
'reviewer_id' => (int) ($application['reviewer_id'] ?? 0),
|
|
];
|
|
}, $applications)));
|
|
}
|
|
|
|
/**
|
|
* Get plate moderator applications from CPT meta.
|
|
*/
|
|
public function get_plate_moderator_applications(int $plate_id): array {
|
|
if (!post_type_exists('aksrt_forum_plate')) {
|
|
return [];
|
|
}
|
|
|
|
$applications = get_post_meta($plate_id, '_aksrt_moderator_applications', true);
|
|
if (!is_array($applications)) {
|
|
return [];
|
|
}
|
|
|
|
return array_values(array_filter(array_map(function ($application) use ($plate_id): array {
|
|
if (!is_array($application)) {
|
|
$application = [];
|
|
}
|
|
|
|
return [
|
|
'id' => (string) ($application['id'] ?? wp_generate_uuid4()),
|
|
'board_id' => $plate_id,
|
|
'user_id' => (int) ($application['user_id'] ?? 0),
|
|
'reason' => (string) ($application['reason'] ?? ''),
|
|
'status' => (string) ($application['status'] ?? AKSRT_Forum::APPLICATION_PENDING),
|
|
'created_at' => (string) ($application['created_at'] ?? ''),
|
|
'reviewed_at' => (string) ($application['reviewed_at'] ?? ''),
|
|
'review_note' => (string) ($application['review_note'] ?? ''),
|
|
'reviewer_id' => (int) ($application['reviewer_id'] ?? 0),
|
|
];
|
|
}, $applications)));
|
|
}
|
|
|
|
/**
|
|
* Get current user's board application.
|
|
*/
|
|
public function get_board_application_for_user(int $user_id, int $board_id): ?array {
|
|
$applications = $this->get_board_moderator_applications($board_id);
|
|
$matched = array_values(array_filter($applications, static function ($application) use ($user_id): bool {
|
|
return (int) ($application['user_id'] ?? 0) === $user_id;
|
|
}));
|
|
|
|
if (empty($matched)) {
|
|
return null;
|
|
}
|
|
|
|
usort($matched, static function ($left, $right): int {
|
|
return strcmp((string) ($right['created_at'] ?? ''), (string) ($left['created_at'] ?? ''));
|
|
});
|
|
|
|
$application = $matched[0];
|
|
$application['status_label'] = $this->get_application_status_label((string) ($application['status'] ?? ''));
|
|
|
|
return $application;
|
|
}
|
|
|
|
public function find_application_by_id(string $application_id): ?array {
|
|
foreach ($this->get_moderator_application_list() as $application) {
|
|
if ((string) ($application['id'] ?? '') === $application_id) {
|
|
return $application;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function get_application_status_label(string $status): string {
|
|
$map = [
|
|
AKSRT_Forum::APPLICATION_PENDING => __('待处理', 'aksrt'),
|
|
AKSRT_Forum::APPLICATION_APPROVED => __('已通过', 'aksrt'),
|
|
AKSRT_Forum::APPLICATION_REJECTED => __('已拒绝', 'aksrt'),
|
|
];
|
|
|
|
return $map[$status] ?? $map[AKSRT_Forum::APPLICATION_PENDING];
|
|
}
|
|
|
|
public function on_topic_saved_sync_plate(int $post_id, \WP_Post $post): void {
|
|
if (!$this->forum->is_plate_cpt_active()) {
|
|
return;
|
|
}
|
|
$plate_id = (int) get_post_meta($post_id, '_aksrt_plate_id', true);
|
|
if ($plate_id > 0) {
|
|
$this->sync_plate_counts($plate_id);
|
|
}
|
|
}
|
|
|
|
public function on_comment_sync_plate_reply(int $comment_id, $approved, array $commentdata): void {
|
|
if (!$this->forum->is_plate_cpt_active()) {
|
|
return;
|
|
}
|
|
$post_id = (int) ($commentdata['comment_post_ID'] ?? 0);
|
|
if ($post_id <= 0) {
|
|
return;
|
|
}
|
|
$post = get_post($post_id);
|
|
if (!$post || $post->post_type !== 'aksrt_forum_topic') {
|
|
return;
|
|
}
|
|
$plate_id = (int) get_post_meta($post_id, '_aksrt_plate_id', true);
|
|
if ($plate_id > 0) {
|
|
$this->sync_plate_counts($plate_id);
|
|
}
|
|
}
|
|
|
|
public function is_moderator(int $user_id, int $topic_id): bool {
|
|
if ($user_id <= 0) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->is_forum_admin($user_id)) {
|
|
return true;
|
|
}
|
|
|
|
// CPT mode: resolve plate from topic meta
|
|
if ($this->forum->is_plate_cpt_active()) {
|
|
$plate_id = $this->forum->get_topic_plate_id($topic_id);
|
|
if ($plate_id > 0) {
|
|
return $this->forum->get_user_plate_tier($user_id, $plate_id) >= self::TIER_MODERATOR;
|
|
}
|
|
}
|
|
|
|
// Fallback: taxonomy-based boards
|
|
$boards = get_the_terms($topic_id, 'aksrt_forum_board');
|
|
if ($boards && !is_wp_error($boards)) {
|
|
foreach ($boards as $board) {
|
|
if ($this->is_board_moderator($user_id, (int) $board->term_id)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|