Files
wordpress-theme-aksrt/inc/classes/admin/class-aksrt-admin-user-controller-report-operations.php
T

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'));
}
}