ban_filter_string($input, 'type')); if (!in_array($type, ['report', 'appeal'], true)) { $type = ''; } $status = sanitize_key($this->ban_filter_string($input, 'status', 'pending')); if ($status !== '' && !in_array($status, ['pending', 'resolved', 'dismissed'], true)) { $status = 'pending'; } $orderby = sanitize_key($this->ban_filter_string($input, 'orderby', 'time')); if (!in_array($orderby, ['time', 'updated', 'status', 'type'], true)) { $orderby = 'time'; } $order = strtoupper($this->ban_filter_string($input, 'order', 'DESC')); if (!in_array($order, ['ASC', 'DESC'], true)) { $order = 'DESC'; } return [ 'type' => $type, 'status' => $status, 's' => sanitize_text_field($this->ban_filter_string($input, 's')), 'send_user' => absint($this->ban_filter_string($input, 'send_user')), 'orderby' => $orderby, 'order' => $order, ]; } /** * @return array{items:array>,total:int,page:int,counts:array{all:int,pending:int,resolved:int,dismissed:int}} */ private function query_ban_requests(array $filters, int $page): array { $rows = []; if ($filters['type'] !== 'appeal') { $rows = array_merge($rows, $this->query_ban_report_rows($filters)); } if ($filters['type'] !== 'report') { $rows = array_merge($rows, $this->query_ban_appeal_rows($filters)); } $counts = ['all' => 0, 'pending' => 0, 'resolved' => 0, 'dismissed' => 0]; $filtered = []; foreach ($rows as $row) { $counts['all']++; $counts[$row['status']]++; if ($filters['status'] === '' || $row['status'] === $filters['status']) { $filtered[] = $row; } } $this->sort_ban_requests($filtered, $filters['orderby'], $filters['order']); $total = count($filtered); $page = min(max(1, $page), max(1, (int) ceil($total / self::BAN_PER_PAGE))); $items = array_slice($filtered, ($page - 1) * self::BAN_PER_PAGE, self::BAN_PER_PAGE); $this->load_ban_report_notes($items); $this->cache_ban_request_users($items, $filters['send_user']); return [ 'items' => $items, 'total' => $total, 'page' => $page, 'counts' => $counts, ]; } private function ban_record_version(array $row): string { return hash('sha256', serialize([ 'kind' => (string) ($row['kind'] ?? ''), 'id' => (int) ($row['id'] ?? 0), 'user_id' => (int) ($row['user_id'] ?? 0), 'status' => (string) ($row['status'] ?? ''), 'raw_status' => (string) ($row['raw_status'] ?? ''), 'time' => (string) ($row['time'] ?? ''), 'updated' => (string) ($row['updated'] ?? ''), 'payload' => (array) ($row['payload'] ?? []), ])); } private function ban_filter_string(array $input, string $key, string $default = ''): string { if (!array_key_exists($key, $input) || !is_scalar($input[$key])) { return $default; } return (string) wp_unslash($input[$key]); } /** @return array> */ private function query_ban_report_rows(array $filters): array { global $wpdb; if (!class_exists('\\AKSRT\\Core\\DBMigrator')) { return []; } $table = \AKSRT\Core\DBMigrator::table('report'); if ($table === '') { return []; } $where = ['r.status IN (%s, %s, %s, %s)']; $params = ['pending', 'reviewing', 'resolved', 'dismissed']; if ($filters['send_user'] > 0) { $where[] = 'r.reporter_id = %d'; $params[] = $filters['send_user']; } if ($filters['s'] !== '') { $where[] = '(CONCAT_WS(\' \', r.reason, r.category, r.target_type, CONCAT(\'#\', r.target_id)) LIKE %s' . ' OR CAST(r.id AS CHAR) LIKE %s OR CAST(r.reporter_id AS CHAR) LIKE %s' . ' OR report_user.display_name LIKE %s OR report_user.user_login LIKE %s OR report_user.user_email LIKE %s)'; $like = '%' . $wpdb->esc_like($filters['s']) . '%'; array_push($params, $like, $like, $like, $like, $like, $like); } $sql = 'SELECT r.id, r.reporter_id, r.target_type, r.target_id, r.reason, r.category, r.status, r.created_at, r.updated_at' . " FROM {$table} AS r LEFT JOIN {$wpdb->users} AS report_user ON report_user.ID = r.reporter_id" . ' WHERE ' . implode(' AND ', $where); $sql = $wpdb->prepare($sql, $params); $reports = $wpdb->get_results($sql, ARRAY_A); $rows = []; foreach ((array) $reports as $report) { if (is_array($report)) { $row = $this->normalize_ban_report_row($report); if ($row !== null) { $rows[] = $row; } } } return $rows; } /** @return array> */ private function query_ban_appeal_rows(array $filters): array { if (!class_exists('\\AKSRT\\User\\BanAppeal')) { return []; } $args = [ 'meta_query' => [[ 'key' => \AKSRT\User\BanAppeal::META_STATUS, 'value' => ['pending', 'approved', 'rejected'], 'compare' => 'IN', ]], 'number' => -1, 'fields' => 'ID', 'orderby' => 'ID', 'order' => 'ASC', 'count_total' => false, 'update_meta_cache' => false, ]; if ($filters['send_user'] > 0) { $args['include'] = [$filters['send_user']]; } $user_ids = array_values(array_unique(array_map('absint', (array) get_users($args)))); if (!$user_ids) { return []; } if ($filters['s'] !== '') { cache_users($user_ids); } update_meta_cache('user', $user_ids); $rows = []; foreach ($user_ids as $user_id) { $row = $this->get_ban_appeal_row($user_id); if ($row === null) { continue; } if ($filters['s'] !== '') { $user = get_userdata($user_id); $terms = [ (string) $user_id, (string) ($row['payload']['reason'] ?? ''), (string) ($row['payload']['reason_key'] ?? ''), $user instanceof \WP_User ? $user->display_name : '', $user instanceof \WP_User ? $user->user_login : '', $user instanceof \WP_User ? $user->user_email : '', ]; foreach ((array) ($row['payload']['data'] ?? []) as $key => $value) { if (is_scalar($value)) { $terms[] = (string) $key; $terms[] = (string) $value; } } if (!$this->ban_search_matches(implode(' ', $terms), $filters['s'])) { continue; } } $rows[] = $row; } return $rows; } private function load_ban_report_notes(array &$items): void { $report_ids = []; foreach ($items as $row) { if (($row['kind'] ?? '') === 'report' && (int) ($row['id'] ?? 0) > 0) { $report_ids[] = (int) $row['id']; } } $report_ids = array_values(array_unique($report_ids)); if ($report_ids === [] || !class_exists('\\AKSRT\\Core\\DBMigrator')) { return; } global $wpdb; $table = \AKSRT\Core\DBMigrator::table('report'); if ($table === '') { return; } $placeholders = implode(',', array_fill(0, count($report_ids), '%d')); $report_notes = $wpdb->get_results($wpdb->prepare( "SELECT id, notes_json FROM {$table} WHERE id IN ({$placeholders})", $report_ids ), ARRAY_A); $notes_by_id = []; foreach ((array) $report_notes as $report) { $notes = json_decode((string) ($report['notes_json'] ?? ''), true); $notes_by_id[(int) ($report['id'] ?? 0)] = is_array($notes) ? $notes : []; } foreach ($items as &$row) { if (($row['kind'] ?? '') === 'report') { $row['payload']['notes'] = $notes_by_id[(int) $row['id']] ?? []; } } unset($row); } private function cache_ban_request_users(array $items, int $filter_user): void { $user_ids = $filter_user > 0 ? [$filter_user] : []; foreach ($items as $row) { $user_id = (int) ($row['user_id'] ?? 0); if ($user_id > 0) { $user_ids[] = $user_id; } $payload = (array) ($row['payload'] ?? []); if (($row['kind'] ?? '') === 'report') { if (($payload['target_type'] ?? '') === 'user' && (int) ($payload['target_id'] ?? 0) > 0) { $user_ids[] = (int) $payload['target_id']; } foreach ((array) ($payload['notes'] ?? []) as $note) { if (is_array($note) && isset($note['operator']) && is_scalar($note['operator'])) { $user_ids[] = absint($note['operator']); } } } elseif (isset($payload['handled_by']) && is_scalar($payload['handled_by'])) { $user_ids[] = absint($payload['handled_by']); } } $user_ids = array_values(array_unique(array_filter(array_map('absint', $user_ids)))); if ($user_ids === []) { return; } cache_users($user_ids); } private function find_ban_request(string $kind, int $id): ?array { if ($id <= 0) { return null; } if ($kind === 'appeal') { return $this->get_ban_appeal_row($id); } if ($kind !== 'report' || !class_exists('\\AKSRT\\Core\\DBMigrator')) { return null; } global $wpdb; $table = \AKSRT\Core\DBMigrator::table('report'); if ($table === '') { return null; } $report = $wpdb->get_row($wpdb->prepare( "SELECT id, reporter_id, target_type, target_id, reason, category, status, created_at, updated_at, notes_json FROM {$table} WHERE id = %d LIMIT 1", $id ), ARRAY_A); return is_array($report) ? $this->normalize_ban_report_row($report) : null; } private function normalize_ban_report_row(array $report): ?array { $raw_status = sanitize_key((string) ($report['status'] ?? '')); $status = $raw_status === 'reviewing' ? 'pending' : $raw_status; if (!in_array($status, ['pending', 'resolved', 'dismissed'], true)) { return null; } $id = (int) ($report['id'] ?? 0); $reporter_id = (int) ($report['reporter_id'] ?? 0); $notes = json_decode((string) ($report['notes_json'] ?? ''), true); $payload = [ 'id' => $id, 'reporter_id' => $reporter_id, 'target_type' => sanitize_key((string) ($report['target_type'] ?? '')), 'target_id' => (int) ($report['target_id'] ?? 0), 'reason' => (string) ($report['reason'] ?? ''), 'category' => (string) ($report['category'] ?? ''), 'status' => $raw_status, 'created_at' => (string) ($report['created_at'] ?? ''), 'updated_at' => (string) ($report['updated_at'] ?? ''), 'notes' => is_array($notes) ? $notes : [], ]; return [ 'kind' => 'report', 'id' => $id, 'user_id' => $reporter_id, 'status' => $status, 'raw_status' => $raw_status, 'time' => $payload['created_at'], 'updated' => $payload['updated_at'], 'payload' => $payload, ]; } private function get_ban_appeal_row(int $user_id): ?array { if ($user_id <= 0 || !class_exists('\\AKSRT\\User\\BanAppeal')) { return null; } $raw_status = \AKSRT\User\BanAppeal::get_status($user_id); $status = $raw_status === 'approved' ? 'resolved' : ($raw_status === 'rejected' ? 'dismissed' : $raw_status); if (!in_array($status, ['pending', 'resolved', 'dismissed'], true)) { return null; } $payload = \AKSRT\User\BanAppeal::get_payload($user_id); return [ 'kind' => 'appeal', 'id' => $user_id, 'user_id' => $user_id, 'status' => $status, 'raw_status' => $raw_status, 'time' => (string) ($payload['time'] ?? ''), 'updated' => (string) ($payload['handled_at'] ?? ''), 'payload' => $payload, ]; } private function ban_search_matches(string $haystack, string $needle): bool { if (function_exists('mb_stripos')) { return mb_stripos($haystack, $needle) !== false; } return stripos($haystack, $needle) !== false; } /** @param array> $rows */ private function sort_ban_requests(array &$rows, string $orderby, string $order): void { $status_weight = ['pending' => 0, 'resolved' => 1, 'dismissed' => 2]; usort($rows, static function (array $a, array $b) use ($orderby, $order, $status_weight): int { if ($orderby === 'status') { $comparison = ($status_weight[$a['status']] ?? 9) <=> ($status_weight[$b['status']] ?? 9); } elseif ($orderby === 'type') { $comparison = strcmp((string) $a['kind'], (string) $b['kind']); } else { $field = $orderby === 'updated' ? 'updated' : 'time'; $comparison = strcmp((string) $a[$field], (string) $b[$field]); } if ($comparison !== 0) { return $order === 'ASC' ? $comparison : -$comparison; } $kind_comparison = strcmp((string) $a['kind'], (string) $b['kind']); return $kind_comparison !== 0 ? $kind_comparison : ((int) $a['id'] <=> (int) $b['id']); }); } }