refactor(autoload): migrate theme classes to Composer PSR-4
Move 288 types from global AKSRT_* + inc/classes manual requires into
namespace AKSRT\{Domain} under src/AKSRT/, wire composer.json psr-4,
and slim bootstrap to helpers/modules/hooks only. Split multi-class
widget/gateway files, update admin contract audit paths, and document
the layout in ADR/README.
This commit is contained in:
1 parent
8f349b40fb
commit
aaf458af1c
404 files changed
+13602
-7705
No files matched your search
@@ -0,0 +1,717 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Forum;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class ModerationService {
|
||||
|
||||
private Forum $forum;
|
||||
|
||||
public function __construct(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'] ?? '') === 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' => 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, [Forum::APPLICATION_APPROVED, 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 === 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 === 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\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' => Forum::TIER_MODERATOR, // backward-compat alias
|
||||
'moderator' => Forum::TIER_MODERATOR,
|
||||
'plate_author' => Forum::TIER_PLATE_AUTHOR,
|
||||
'super_moderator' => Forum::TIER_PLATE_AUTHOR, // backward-compat alias
|
||||
'cat_moderator' => Forum::TIER_CAT_MOD,
|
||||
'forum_admin' => Forum::TIER_FORUM_ADMIN,
|
||||
];
|
||||
$required = $tier_map[$min_role] ?? 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'] ?? 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'] ?? 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 = [
|
||||
Forum::APPLICATION_PENDING => __('待处理', 'aksrt'),
|
||||
Forum::APPLICATION_APPROVED => __('已通过', 'aksrt'),
|
||||
Forum::APPLICATION_REJECTED => __('已拒绝', 'aksrt'),
|
||||
];
|
||||
|
||||
return $map[$status] ?? $map[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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user