table(); if ($table === '') return 0; global $wpdb; $wpdb->insert($table, [ 'reporter_id' => $reporter_id, 'target_type' => sanitize_key($target_type), 'target_id' => $target_id, 'reason' => sanitize_textarea_field($reason), 'category' => sanitize_key($category), 'status' => 'pending', 'created_at' => current_time('mysql'), ], ['%d', '%s', '%d', '%s', '%s', '%s', '%s']); $id = $wpdb->insert_id ? (int) $wpdb->insert_id : 0; if ($id > 0) { do_action('aksrt_report_submitted', $id, [ 'reporter_id' => $reporter_id, 'target_type' => $target_type, 'target_id' => $target_id, 'reason' => $reason, 'category' => $category, ]); } return $id; } /** * 获取待处理举报数 */ public function get_pending_count(): int { $table = $this->table(); if ($table === '') return 0; global $wpdb; return (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table} WHERE status = 'pending'" ); } /** * 获取举报列表 */ public function get_reports(array $args = []): array { $table = $this->table(); if ($table === '') return []; global $wpdb; $where = []; $params = []; $status = sanitize_key((string) ($args['status'] ?? '')); if ($status !== '') { $where[] = 'status = %s'; $params[] = $status; } $limit = max(1, min(100, (int) ($args['limit'] ?? 20))); $offset = max(0, (int) ($args['offset'] ?? 0)); $where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : ''; $sql = "SELECT * FROM {$table} {$where_sql} ORDER BY created_at DESC LIMIT %d OFFSET %d"; $params[] = $limit; $params[] = $offset; $rows = $wpdb->get_results($wpdb->prepare($sql, $params), ARRAY_A); // 附加审核备注 foreach ($rows as &$row) { $row['id'] = (int) $row['id']; $row['reporter_id'] = (int) $row['reporter_id']; $row['target_id'] = (int) $row['target_id']; $row['notes'] = isset($row['notes_json']) ? json_decode($row['notes_json'], true) ?? [] : []; } return $rows; } /** * 更新举报状态 */ public function update_status(int $report_id, string $new_status, string $note = ''): bool { $valid_statuses = ['pending', 'reviewing', 'resolved', 'dismissed']; if (!in_array($new_status, $valid_statuses, true)) return false; $table = $this->table(); if ($table === '') return false; global $wpdb; $existing = $wpdb->get_row($wpdb->prepare( "SELECT id, reporter_id, notes_json FROM {$table} WHERE id = %d", $report_id )); if (!$existing) return false; $update_data = [ 'status' => $new_status, 'updated_at' => current_time('mysql'), ]; $format = ['%s', '%s']; if ($note !== '') { $notes = json_decode($existing->notes_json ?? '[]', true) ?? []; $notes[] = [ 'note' => sanitize_textarea_field($note), 'status' => $new_status, 'operator' => get_current_user_id(), 'timestamp' => current_time('mysql'), ]; $update_data['notes_json'] = wp_json_encode($notes); $format[] = '%s'; } $result = $wpdb->update($table, $update_data, ['id' => $report_id], $format, ['%d']); if ($result !== false) { do_action('aksrt_report_status_changed', $report_id, (int) ($existing->reporter_id ?? 0), $new_status); } return $result !== false; } /** * Get status counts for the admin dashboard. * * @return array{pending: int, reviewing: int, resolved: int, dismissed: int, total: int} */ public function get_status_counts(): array { $table = $this->table(); if ($table === '') { return ['pending' => 0, 'reviewing' => 0, 'resolved' => 0, 'dismissed' => 0, 'total' => 0]; } global $wpdb; $rows = $wpdb->get_results( "SELECT status, COUNT(*) as cnt FROM {$table} GROUP BY status", ARRAY_A ); $counts = ['pending' => 0, 'reviewing' => 0, 'resolved' => 0, 'dismissed' => 0]; $total = 0; foreach ((array) $rows as $row) { $s = $row['status'] ?? ''; if (isset($counts[$s])) { $counts[$s] = (int) $row['cnt']; } $total += (int) $row['cnt']; } $counts['total'] = $total; return $counts; } /** * 一次性迁移:把 option 中的举报数据搬到表中。 * 幂等 — 已存在的 ID 不会重复插入。 */ public function migrate_from_option(): int { $table = $this->table(); if ($table === '') return 0; $reports = get_option('aksrt_reports', []); if (!is_array($reports) || empty($reports)) return 0; global $wpdb; $migrated = 0; foreach ($reports as $old_id => $report) { // 检查是否已迁移(通过 reporter_id + target_type + target_id + created_at 去重) $existing = $wpdb->get_var($wpdb->prepare( "SELECT id FROM {$table} WHERE reporter_id = %d AND target_type = %s AND target_id = %d AND created_at = %s", (int) ($report['reporter_id'] ?? 0), sanitize_key((string) ($report['target_type'] ?? '')), (int) ($report['target_id'] ?? 0), (string) ($report['created_at'] ?? '') )); if ($existing) continue; $notes_json = ''; if (!empty($report['notes']) && is_array($report['notes'])) { $notes_json = wp_json_encode($report['notes']); } $wpdb->insert($table, [ 'reporter_id' => (int) ($report['reporter_id'] ?? 0), 'target_type' => sanitize_key((string) ($report['target_type'] ?? '')), 'target_id' => (int) ($report['target_id'] ?? 0), 'reason' => sanitize_textarea_field((string) ($report['reason'] ?? '')), 'category' => sanitize_key((string) ($report['category'] ?? '')), 'status' => sanitize_key((string) ($report['status'] ?? 'pending')), 'created_at' => (string) ($report['created_at'] ?? current_time('mysql')), 'updated_at' => (string) ($report['updated_at'] ?? ''), 'notes_json' => $notes_json, ], ['%d', '%s', '%d', '%s', '%s', '%s', '%s', '%s', '%s']); if ($wpdb->insert_id) $migrated++; } if ($migrated > 0) { update_option('aksrt_report_migrated_to_table', time()); } return $migrated; } /** * Initialize AJAX hooks for admin operations. */ public function init(): void { add_action('wp_ajax_aksrt_report_update_status', [$this, 'ajax_update_status']); add_action('wp_ajax_aksrt_report_list', [$this, 'ajax_list']); add_action('wp_ajax_aksrt_report_counts', [$this, 'ajax_counts']); // 一次性迁移钩子 add_action('admin_init', [$this, 'maybe_migrate'], 20); } /** * 自动迁移旧 option 数据(幂等) */ public function maybe_migrate(): void { if (get_option('aksrt_report_migrated_to_table')) return; $this->migrate_from_option(); } /** * AJAX: Update report status (admin only). */ public function ajax_update_status(): void { if (!current_user_can('manage_options')) { wp_send_json_error(['message' => __('权限不足', 'aksrt')]); } check_ajax_referer('aksrt_report', '_nonce'); $report_id = (int) ($_POST['report_id'] ?? 0); $new_status = sanitize_key((string) ($_POST['status'] ?? '')); $note = sanitize_textarea_field((string) ($_POST['note'] ?? '')); if ($report_id <= 0 || $new_status === '') { wp_send_json_error(['message' => __('参数无效', 'aksrt')]); } $result = $this->update_status($report_id, $new_status, $note); if ($result) { wp_send_json_success(['message' => __('状态已更新', 'aksrt')]); } wp_send_json_error(['message' => __('更新失败', 'aksrt')]); } /** * AJAX: Get report list (admin only). */ public function ajax_list(): void { if (!current_user_can('manage_options')) { wp_send_json_error(['message' => __('权限不足', 'aksrt')]); } $status = sanitize_key((string) ($_GET['status'] ?? '')); $limit = max(1, min(100, (int) ($_GET['limit'] ?? 20))); $offset = max(0, (int) ($_GET['offset'] ?? 0)); $reports = $this->get_reports(['status' => $status, 'limit' => $limit, 'offset' => $offset]); $counts = $this->get_status_counts(); wp_send_json_success(['reports' => $reports, 'counts' => $counts]); } /** * AJAX: Get status counts (admin only). */ public function ajax_counts(): void { if (!current_user_can('manage_options')) { wp_send_json_error(['message' => __('权限不足', 'aksrt')]); } wp_send_json_success($this->get_status_counts()); } }