388 lines
15 KiB
PHP
388 lines
15 KiB
PHP
<?php
|
|
/**
|
|
* @package AKSRT
|
|
*/
|
|
|
|
namespace AKSRT\Admin\Native;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
trait UsersMenusBanQuery {
|
|
|
|
private const BAN_PER_PAGE = 20;
|
|
|
|
private function get_ban_filters(array $input): array {
|
|
$type = sanitize_key($this->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<int,array<string,mixed>>,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<int,array<string,mixed>> */
|
|
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<int,array<string,mixed>> */
|
|
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<int,array<string,mixed>> $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']);
|
|
});
|
|
}
|
|
}
|