472 lines
18 KiB
PHP
472 lines
18 KiB
PHP
<?php
|
|
/**
|
|
* AKSRT admin report operations support.
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
trait AKSRT_Admin_User_Controller_Report_Operations {
|
|
|
|
public function get_report_list(WP_REST_Request $request): WP_REST_Response {
|
|
if (!class_exists('AKSRT_Report_Center')) {
|
|
return $this->success([
|
|
'data' => [
|
|
'items' => [],
|
|
'reports' => [],
|
|
'counts' => ['pending' => 0, 'reviewing' => 0, 'resolved' => 0, 'dismissed' => 0, 'total' => 0],
|
|
'total' => 0,
|
|
'page' => 1,
|
|
'options' => [
|
|
'statuses' => $this->get_report_status_options(),
|
|
],
|
|
],
|
|
]);
|
|
}
|
|
|
|
global $wpdb;
|
|
|
|
$table = AKSRT_DB_Migrator::table('report');
|
|
if (!$table) {
|
|
return $this->success([
|
|
'data' => [
|
|
'items' => [],
|
|
'reports' => [],
|
|
'counts' => ['pending' => 0, 'reviewing' => 0, 'resolved' => 0, 'dismissed' => 0, 'total' => 0],
|
|
'total' => 0,
|
|
'page' => 1,
|
|
'options' => [
|
|
'statuses' => $this->get_report_status_options(),
|
|
],
|
|
],
|
|
]);
|
|
}
|
|
|
|
$page = max(1, (int) ($request->get_param('page') ?: 1));
|
|
$per_page = max(1, min(100, (int) ($request->get_param('per_page') ?: $request->get_param('limit') ?: 20)));
|
|
$status = sanitize_key((string) $request->get_param('status'));
|
|
$category = sanitize_key((string) $request->get_param('category'));
|
|
$target_type = sanitize_key((string) $request->get_param('target_type'));
|
|
$keyword = trim(sanitize_text_field((string) $request->get_param('keyword')));
|
|
$date_from = sanitize_text_field((string) $request->get_param('date_from'));
|
|
$date_to = sanitize_text_field((string) $request->get_param('date_to'));
|
|
|
|
$where = [];
|
|
$params = [];
|
|
|
|
if ($status !== '') {
|
|
$where[] = 'status = %s';
|
|
$params[] = $status;
|
|
}
|
|
if ($category !== '') {
|
|
$where[] = 'category = %s';
|
|
$params[] = $category;
|
|
}
|
|
if ($target_type !== '') {
|
|
$where[] = 'target_type = %s';
|
|
$params[] = $target_type;
|
|
}
|
|
if ($date_from !== '') {
|
|
$where[] = 'created_at >= %s';
|
|
$params[] = $date_from . ' 00:00:00';
|
|
}
|
|
if ($date_to !== '') {
|
|
$where[] = 'created_at <= %s';
|
|
$params[] = $date_to . ' 23:59:59';
|
|
}
|
|
|
|
$where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
|
$sql = "SELECT * FROM {$table} {$where_sql} ORDER BY created_at DESC LIMIT 500";
|
|
$rows = $params ? $wpdb->get_results($wpdb->prepare($sql, $params), ARRAY_A) : $wpdb->get_results($sql, ARRAY_A);
|
|
|
|
$items = [];
|
|
foreach ((array) $rows as $row) {
|
|
$item = $this->build_report_admin_item($row);
|
|
if ($keyword !== '' && !$this->report_matches_keyword($item, $keyword)) {
|
|
continue;
|
|
}
|
|
$items[] = $item;
|
|
}
|
|
|
|
$total = count($items);
|
|
$offset = ($page - 1) * $per_page;
|
|
$paged = array_slice($items, $offset, $per_page);
|
|
$counts = AKSRT_Report_Center::get_instance()->get_status_counts();
|
|
$categories = [];
|
|
foreach ((array) $rows as $row) {
|
|
$cat = sanitize_key((string) ($row['category'] ?? ''));
|
|
if ($cat !== '') {
|
|
$categories[$cat] = $cat;
|
|
}
|
|
}
|
|
|
|
return $this->success([
|
|
'data' => [
|
|
'items' => array_values($paged),
|
|
'reports' => array_values($paged),
|
|
'total' => $total,
|
|
'page' => $page,
|
|
'per_page' => $per_page,
|
|
'counts' => $counts,
|
|
'options' => [
|
|
'statuses' => $this->get_report_status_options(),
|
|
'actions' => $this->get_report_action_options(),
|
|
'targets' => $this->get_report_target_type_options(),
|
|
'categories' => array_map(static function ($item) {
|
|
return ['label' => $item, 'value' => $item];
|
|
}, array_values($categories)),
|
|
],
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function update_report_status(WP_REST_Request $request): WP_REST_Response {
|
|
if (!class_exists('AKSRT_Report_Center')) {
|
|
return $this->error(__('举报系统未启用', 'aksrt'));
|
|
}
|
|
|
|
$params = $this->get_json_params($request);
|
|
$report_id = (int) ($params['report_id'] ?? 0);
|
|
if ($report_id <= 0) {
|
|
return $this->error(__('参数无效', 'aksrt'));
|
|
}
|
|
|
|
return $this->handle_report_process($report_id, $params);
|
|
}
|
|
|
|
public function get_report_counts(): WP_REST_Response {
|
|
if (!class_exists('AKSRT_Report_Center')) {
|
|
return $this->success(['pending' => 0, 'reviewing' => 0, 'resolved' => 0, 'dismissed' => 0, 'total' => 0]);
|
|
}
|
|
return $this->success(AKSRT_Report_Center::get_instance()->get_status_counts());
|
|
}
|
|
|
|
public function get_report_detail(WP_REST_Request $request): WP_REST_Response {
|
|
global $wpdb;
|
|
|
|
$report_id = (int) $request->get_param('id');
|
|
$table = AKSRT_DB_Migrator::table('report');
|
|
|
|
if ($report_id <= 0 || !$table) {
|
|
return $this->error(__('举报工单不存在', 'aksrt'), 404);
|
|
}
|
|
|
|
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE id = %d", $report_id), ARRAY_A);
|
|
if (!$row) {
|
|
return $this->error(__('举报工单不存在', 'aksrt'), 404);
|
|
}
|
|
|
|
$related = (int) $wpdb->get_var($wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$table} WHERE target_type = %s AND target_id = %d",
|
|
sanitize_key((string) $row['target_type']),
|
|
(int) $row['target_id']
|
|
));
|
|
|
|
return $this->success([
|
|
'data' => [
|
|
'report' => $this->build_report_admin_item($row),
|
|
'related' => ['same_target_count' => $related],
|
|
'options' => [
|
|
'statuses' => $this->get_report_status_options(),
|
|
'actions' => $this->get_report_action_options(sanitize_key((string) $row['target_type'])),
|
|
],
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function process_report(WP_REST_Request $request): WP_REST_Response {
|
|
$report_id = (int) $request->get_param('id');
|
|
if ($report_id <= 0) {
|
|
return $this->error(__('参数无效', 'aksrt'));
|
|
}
|
|
|
|
return $this->handle_report_process($report_id, $this->get_json_params($request));
|
|
}
|
|
|
|
private function handle_report_process(int $report_id, array $payload): WP_REST_Response {
|
|
global $wpdb;
|
|
|
|
$table = AKSRT_DB_Migrator::table('report');
|
|
if (!$table) {
|
|
return $this->error(__('举报系统未启用', 'aksrt'));
|
|
}
|
|
|
|
$status = sanitize_key((string) ($payload['status'] ?? ''));
|
|
$action_taken = sanitize_key((string) ($payload['action_taken'] ?? 'none'));
|
|
$note = sanitize_textarea_field((string) ($payload['note'] ?? ''));
|
|
|
|
$valid_statuses = array_column($this->get_report_status_options(), 'value');
|
|
$valid_actions = array_column($this->get_report_action_options(), 'value');
|
|
|
|
if ($status === '' || !in_array($status, $valid_statuses, true)) {
|
|
return $this->error(__('处理状态无效', 'aksrt'));
|
|
}
|
|
if (!in_array($action_taken, $valid_actions, true)) {
|
|
$action_taken = 'none';
|
|
}
|
|
|
|
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE id = %d", $report_id), ARRAY_A);
|
|
if (!$row) {
|
|
return $this->error(__('举报工单不存在', 'aksrt'), 404);
|
|
}
|
|
|
|
$effect = $this->apply_report_action(
|
|
sanitize_key((string) $row['target_type']),
|
|
(int) $row['target_id'],
|
|
$action_taken
|
|
);
|
|
|
|
$notes = json_decode((string) ($row['notes_json'] ?? '[]'), true);
|
|
if (!is_array($notes)) {
|
|
$notes = [];
|
|
}
|
|
$notes[] = [
|
|
'note' => $note,
|
|
'status' => $status,
|
|
'action_taken' => $action_taken,
|
|
'operator' => get_current_user_id(),
|
|
'timestamp' => current_time('mysql'),
|
|
'effect' => $effect,
|
|
];
|
|
|
|
$updated = $wpdb->update(
|
|
$table,
|
|
[
|
|
'status' => $status,
|
|
'updated_at' => current_time('mysql'),
|
|
'notes_json' => wp_json_encode($notes),
|
|
],
|
|
['id' => $report_id],
|
|
['%s', '%s', '%s'],
|
|
['%d']
|
|
);
|
|
|
|
if ($updated === false) {
|
|
return $this->error(__('处理失败', 'aksrt'));
|
|
}
|
|
|
|
return $this->success([
|
|
'message' => __('举报工单已处理', 'aksrt'),
|
|
]);
|
|
}
|
|
|
|
private function build_report_admin_item(array $row): array {
|
|
$reporter = get_userdata((int) ($row['reporter_id'] ?? 0));
|
|
$notes = json_decode((string) ($row['notes_json'] ?? '[]'), true);
|
|
if (!is_array($notes)) {
|
|
$notes = [];
|
|
}
|
|
|
|
$normalized_notes = [];
|
|
foreach ($notes as $entry) {
|
|
$operator_id = (int) ($entry['operator'] ?? 0);
|
|
$operator_user = $operator_id > 0 ? get_userdata($operator_id) : null;
|
|
$normalized_notes[] = [
|
|
'note' => (string) ($entry['note'] ?? ''),
|
|
'status' => sanitize_key((string) ($entry['status'] ?? '')),
|
|
'action_taken' => sanitize_key((string) ($entry['action_taken'] ?? 'none')),
|
|
'operator' => $operator_id,
|
|
'operator_name' => $operator_user ? $operator_user->display_name : '',
|
|
'timestamp' => (string) ($entry['timestamp'] ?? ''),
|
|
'effect' => (string) ($entry['effect'] ?? ''),
|
|
];
|
|
}
|
|
|
|
$last_note = !empty($normalized_notes) ? $normalized_notes[count($normalized_notes) - 1] : null;
|
|
|
|
return [
|
|
'id' => (int) ($row['id'] ?? 0),
|
|
'target_type' => sanitize_key((string) ($row['target_type'] ?? '')),
|
|
'target_id' => (int) ($row['target_id'] ?? 0),
|
|
'reason' => (string) ($row['reason'] ?? ''),
|
|
'category' => sanitize_key((string) ($row['category'] ?? '')),
|
|
'status' => sanitize_key((string) ($row['status'] ?? 'pending')),
|
|
'created_at' => (string) ($row['created_at'] ?? ''),
|
|
'updated_at' => (string) ($row['updated_at'] ?? ''),
|
|
'reporter' => $this->build_admin_user_summary($reporter ? (int) $reporter->ID : 0, $reporter),
|
|
'target' => $this->build_report_target_summary(
|
|
sanitize_key((string) ($row['target_type'] ?? '')),
|
|
(int) ($row['target_id'] ?? 0)
|
|
),
|
|
'notes' => $normalized_notes,
|
|
'note_count' => count($normalized_notes),
|
|
'last_note' => $last_note,
|
|
];
|
|
}
|
|
|
|
private function report_matches_keyword(array $item, string $keyword): bool {
|
|
$haystack = implode(' ', array_filter([
|
|
(string) ($item['reason'] ?? ''),
|
|
(string) ($item['category'] ?? ''),
|
|
(string) ($item['reporter']['display_name'] ?? ''),
|
|
(string) ($item['reporter']['email'] ?? ''),
|
|
(string) ($item['target']['title'] ?? ''),
|
|
(string) ($item['target']['display_name'] ?? ''),
|
|
(string) ($item['target']['excerpt'] ?? ''),
|
|
(string) ($item['target_id'] ?? ''),
|
|
]));
|
|
|
|
return mb_stripos($haystack, $keyword) !== false;
|
|
}
|
|
|
|
private function build_report_target_summary(string $target_type, int $target_id): array {
|
|
$summary = [
|
|
'type' => $target_type,
|
|
'id' => $target_id,
|
|
'title' => '',
|
|
'excerpt' => '',
|
|
'display_name' => '',
|
|
'status' => '',
|
|
'url' => '',
|
|
'ban_state' => '',
|
|
];
|
|
|
|
if ($target_type === 'post') {
|
|
$post = get_post($target_id);
|
|
if ($post instanceof WP_Post) {
|
|
$summary['title'] = get_the_title($post);
|
|
$summary['status'] = (string) $post->post_status;
|
|
$summary['url'] = get_permalink($post);
|
|
}
|
|
} elseif ($target_type === 'comment') {
|
|
$comment = get_comment($target_id);
|
|
if ($comment instanceof WP_Comment) {
|
|
$summary['display_name'] = (string) $comment->comment_author;
|
|
$summary['excerpt'] = wp_trim_words(wp_strip_all_tags((string) $comment->comment_content), 24);
|
|
$summary['status'] = $comment->comment_approved === '1' ? 'approved' : (string) $comment->comment_approved;
|
|
$summary['url'] = get_comment_link($comment);
|
|
if (!empty($comment->comment_post_ID)) {
|
|
$summary['title'] = get_the_title((int) $comment->comment_post_ID);
|
|
}
|
|
}
|
|
} elseif ($target_type === 'user') {
|
|
$user = get_userdata($target_id);
|
|
if ($user instanceof WP_User) {
|
|
$summary['display_name'] = $user->display_name;
|
|
$summary['title'] = $user->display_name;
|
|
$summary['url'] = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($target_id) : '';
|
|
$summary['ban_state'] = (string) get_user_meta($target_id, 'aksrt_user_ban_state', true);
|
|
}
|
|
}
|
|
|
|
return $summary;
|
|
}
|
|
|
|
private function apply_report_action(string $target_type, int $target_id, string $action_taken): string {
|
|
if ($target_id <= 0 || $action_taken === 'none') {
|
|
return '';
|
|
}
|
|
|
|
if ($target_type === 'user') {
|
|
if (in_array($action_taken, ['muted', 'restricted', 'banned'], true)) {
|
|
update_user_meta($target_id, 'aksrt_user_ban_state', $action_taken);
|
|
return sprintf(__('已同步用户限制状态:%s', 'aksrt'), $action_taken);
|
|
}
|
|
|
|
if ($action_taken === 'clear') {
|
|
delete_user_meta($target_id, 'aksrt_user_ban_state');
|
|
return __('已清除用户限制状态', 'aksrt');
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
if ($action_taken === 'content_removed' && $target_type === 'post' && get_post($target_id)) {
|
|
wp_update_post([
|
|
'ID' => $target_id,
|
|
'post_status' => 'pending',
|
|
]);
|
|
return __('已将内容改为待审状态', 'aksrt');
|
|
}
|
|
|
|
if ($action_taken === 'content_removed' && $target_type === 'comment' && get_comment($target_id)) {
|
|
wp_set_comment_status($target_id, 'hold', true);
|
|
return __('已将评论改为待审状态', 'aksrt');
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
private function get_report_status_options(): array {
|
|
return [
|
|
['label' => __('待处理', 'aksrt'), 'value' => 'pending'],
|
|
['label' => __('处理中', 'aksrt'), 'value' => 'reviewing'],
|
|
['label' => __('已解决', 'aksrt'), 'value' => 'resolved'],
|
|
['label' => __('已驳回', 'aksrt'), 'value' => 'dismissed'],
|
|
];
|
|
}
|
|
|
|
private function get_report_target_type_options(): array {
|
|
return [
|
|
['label' => __('文章', 'aksrt'), 'value' => 'post'],
|
|
['label' => __('评论', 'aksrt'), 'value' => 'comment'],
|
|
['label' => __('用户', 'aksrt'), 'value' => 'user'],
|
|
];
|
|
}
|
|
|
|
private function get_report_action_options(string $target_type = ''): array {
|
|
$common = [
|
|
['label' => __('仅更新状态', 'aksrt'), 'value' => 'none'],
|
|
['label' => __('升级人工处理', 'aksrt'), 'value' => 'escalated'],
|
|
];
|
|
|
|
if ($target_type === 'user' || $target_type === '') {
|
|
$common = array_merge($common, [
|
|
['label' => __('发送警告', 'aksrt'), 'value' => 'warned'],
|
|
['label' => __('禁言', 'aksrt'), 'value' => 'muted'],
|
|
['label' => __('限制功能', 'aksrt'), 'value' => 'restricted'],
|
|
['label' => __('封禁账号', 'aksrt'), 'value' => 'banned'],
|
|
['label' => __('解除限制', 'aksrt'), 'value' => 'clear'],
|
|
]);
|
|
}
|
|
|
|
if ($target_type === 'post' || $target_type === 'comment' || $target_type === '') {
|
|
$common[] = ['label' => __('转为待审内容', 'aksrt'), 'value' => 'content_removed'];
|
|
}
|
|
|
|
return $common;
|
|
}
|
|
|
|
/**
|
|
* POST /report/submit — 前台举报提交(登录用户)
|
|
*/
|
|
public function submit_report(WP_REST_Request $request): WP_REST_Response {
|
|
if (!class_exists('AKSRT_Report_Center')) {
|
|
return $this->error('举报系统未启用', 500);
|
|
}
|
|
|
|
$params = $this->get_json_params($request);
|
|
$target_type = sanitize_key((string) ($params['target_type'] ?? ''));
|
|
$target_id = (int) ($params['target_id'] ?? 0);
|
|
$reason = sanitize_textarea_field((string) ($params['reason'] ?? ''));
|
|
$category = sanitize_key((string) ($params['category'] ?? ''));
|
|
|
|
if ($target_type === '' || $target_id <= 0 || $reason === '') {
|
|
return $this->error('请填写完整的举报信息');
|
|
}
|
|
|
|
$valid_types = ['post', 'comment', 'user'];
|
|
if (!in_array($target_type, $valid_types, true)) {
|
|
return $this->error('无效的举报类型');
|
|
}
|
|
|
|
$report_id = AKSRT_Report_Center::get_instance()->submit(
|
|
get_current_user_id(),
|
|
$target_type,
|
|
$target_id,
|
|
$reason,
|
|
$category
|
|
);
|
|
|
|
if ($report_id > 0) {
|
|
return $this->success([
|
|
'message' => __('举报已提交,我们会尽快处理', 'aksrt'),
|
|
'data' => ['report_id' => $report_id],
|
|
]);
|
|
}
|
|
|
|
return $this->error(__('举报提交失败', 'aksrt'));
|
|
}
|
|
}
|