get_var($wpdb->prepare("SELECT COUNT(*) FROM {$table} WHERE status = %s", $status)); } public function render_verify_page(): void { $this->ensure_cap(); $filters = $this->get_verify_filters(); $page_num = max(1, (int) ($_GET['paged'] ?? 1)); $result = $this->query_verify_requests($filters, $page_num, 20); $counts = $this->get_verify_status_counts(); echo '

' . esc_html__('身份认证', 'aksrt') . '

'; $this->flash(); echo '

' . esc_html(sprintf(__('人工待审 %1$d · 已通过 %2$d · 已驳回 %3$d · 已取消 %4$d', 'aksrt'), $counts['pending'], $counts['verified'], $counts['rejected'], $counts['cancelled'])) . '

'; $this->render_verify_filters($filters); $this->render_verify_table($result['items']); $this->pagination($result['pages'], $page_num); echo '
'; } /** @return array{status:string,keyword:string,date_from:string,date_to:string} */ private function get_verify_filters(): array { $status = isset($_GET['status']) ? sanitize_key((string) wp_unslash($_GET['status'])) : 'pending'; if (!in_array($status, ['pending', 'verified', 'rejected', 'cancelled', ''], true)) { $status = 'pending'; } return [ 'status' => $status, 'keyword' => isset($_GET['keyword']) ? sanitize_text_field((string) wp_unslash($_GET['keyword'])) : '', 'date_from' => $this->sanitize_verify_date($_GET['date_from'] ?? ''), 'date_to' => $this->sanitize_verify_date($_GET['date_to'] ?? ''), ]; } private function sanitize_verify_date($value): string { $value = sanitize_text_field((string) wp_unslash($value)); return preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) ? $value : ''; } /** @return array{pending:int,verified:int,rejected:int,cancelled:int,total:int} */ private function get_verify_status_counts(): array { global $wpdb; $table = \AKSRT\Core\DBMigrator::table('identity_application'); $counts = ['pending' => 0, 'verified' => 0, 'rejected' => 0, 'cancelled' => 0, 'total' => 0]; $rows = $wpdb->get_results("SELECT status, COUNT(*) total FROM {$table} GROUP BY status", ARRAY_A); foreach ((array) $rows as $row) { $status = (string) ($row['status'] ?? ''); $total = (int) ($row['total'] ?? 0); $public = \AKSRT\Identity\TransitionService::public_status($status); if (in_array($status, ['manual_pending', 'provider_failed', 'provider_unsupported', 'provider_expired'], true)) { $counts['pending'] += $total; } elseif (isset($counts[$public])) { $counts[$public] += $total; } $counts['total'] += $total; } return $counts; } /** @return array{items:array>,pages:int,total:int} */ private function query_verify_requests(array $filters, int $page_num, int $per_page): array { global $wpdb; $table = \AKSRT\Core\DBMigrator::table('identity_application'); $where = ['1=1']; $params = []; if ($filters['status'] === 'pending') { $where[] = "a.status IN ('manual_pending','provider_failed','provider_unsupported','provider_expired')"; } elseif ($filters['status'] !== '') { $where[] = 'a.status = %s'; $params[] = $filters['status']; } if ($filters['keyword'] !== '') { $like = '%' . $wpdb->esc_like($filters['keyword']) . '%'; $where[] = '(CAST(u.ID AS CHAR) LIKE %s OR u.display_name LIKE %s OR u.user_login LIKE %s OR u.user_email LIKE %s OR a.public_name LIKE %s OR a.application_uuid LIKE %s)'; array_push($params, $like, $like, $like, $like, $like, $like); } if ($filters['date_from'] !== '') { $where[] = 'a.created_at >= %s'; $params[] = $filters['date_from'] . ' 00:00:00'; } if ($filters['date_to'] !== '') { $where[] = 'a.created_at <= %s'; $params[] = $filters['date_to'] . ' 23:59:59'; } $where_sql = implode(' AND ', $where); $total_sql = "SELECT COUNT(*) FROM {$table} a INNER JOIN {$wpdb->users} u ON u.ID = a.user_id WHERE {$where_sql}"; $total = $params ? (int) $wpdb->get_var($wpdb->prepare($total_sql, $params)) : (int) $wpdb->get_var($total_sql); $query_params = array_merge($params, [$per_page, ($page_num - 1) * $per_page]); $sql = "SELECT a.*, u.display_name, u.user_login, u.user_email FROM {$table} a INNER JOIN {$wpdb->users} u ON u.ID = a.user_id WHERE {$where_sql} ORDER BY a.created_at DESC, a.id DESC LIMIT %d OFFSET %d"; $items = $wpdb->get_results($wpdb->prepare($sql, $query_params), ARRAY_A); return ['items' => is_array($items) ? $items : [], 'pages' => max(1, (int) ceil(max(1, $total) / $per_page)), 'total' => $total]; } private function render_verify_filters(array $filters): void { echo '
'; echo ''; echo ''; echo ''; echo ''; echo ''; submit_button(__('筛选', 'aksrt'), 'secondary', '', false); echo '
'; } private function render_verify_table(array $items): void { echo ''; if ($items === []) { echo ''; } foreach ($items as $row) { $this->render_verify_row($row); } echo '
用户状态申请资料审核记录操作
' . esc_html__('暂无认证申请', 'aksrt') . '
'; } private function render_verify_row(array $row): void { $user_id = (int) $row['user_id']; $status = (string) $row['status']; $application = (new \AKSRT\Identity\ApplicationRepository())->find_by_id((int) $row['id']) ?: $row; $history = (new \AKSRT\Identity\EventRepository())->for_application((int) $row['id']); echo '' . esc_html((string) $row['display_name']) . ''; echo '
@' . esc_html((string) $row['user_login']) . ' · UID ' . $user_id . '
' . esc_html((string) $row['user_email']) . '
'; echo '' . wp_kses_post($this->verify_status_badge($status)) . '
' . esc_html((string) $row['provider']) . ' / ' . esc_html((string) $row['channel']) . '
'; echo '' . esc_html((string) $row['public_name']) . '
' . esc_html((string) $row['type']) . '
'; echo '
UUID: ' . esc_html((string) $row['application_uuid']) . '
'; if (!empty($row['provider_result_code'])) echo '
' . esc_html__('云结果:', 'aksrt') . esc_html((string) $row['provider_result_code']) . '
'; foreach ((new \AKSRT\Identity\EvidenceService())->items($application, true) as $item) { echo ''; } echo ''; if (!empty($row['review_note'])) echo '
' . esc_html((string) $row['review_note']) . '
'; if ($history !== []) echo '
' . esc_html(sprintf(__('事件记录(%d)', 'aksrt'), count($history))) . '
    '; foreach ($history as $event) echo '
  1. ' . esc_html((string) $event['created_at'] . ' · ' . (string) $event['event_type'] . ' · ' . (string) $event['to_status']) . '
  2. '; if ($history !== []) echo '
'; echo ''; $this->render_verify_actions($row); echo ''; } private function verify_status_badge(string $status): string { $labels = ['manual_pending' => __('人工待审', 'aksrt'), 'verified' => __('已通过', 'aksrt'), 'rejected' => __('已驳回', 'aksrt'), 'cancelled' => __('已取消', 'aksrt'), 'provider_pending' => __('等待核身', 'aksrt'), 'provider_processing' => __('云端处理中', 'aksrt'), 'provider_failed' => __('云认证失败', 'aksrt'), 'provider_unsupported' => __('云端不支持', 'aksrt'), 'provider_expired' => __('会话已过期', 'aksrt')]; return '' . esc_html($labels[$status] ?? $status) . ''; } private function render_verify_actions(array $row): void { $status = (string) $row['status']; if (in_array($status, ['manual_pending', 'provider_failed', 'provider_unsupported', 'provider_expired'], true)) { foreach (['verified' => __('通过认证', 'aksrt'), 'rejected' => __('驳回申请', 'aksrt')] as $next => $label) { echo '
'; echo ''; wp_nonce_field('aksrt_users_review_verify_' . (int) $row['user_id']); echo '
'; } } elseif ($status === 'verified') { echo '
'; wp_nonce_field('aksrt_users_review_verify_' . (int) $row['user_id']); echo '
'; } else { echo '' . esc_html__('无需人工操作', 'aksrt') . ''; } echo '
'; echo ''; wp_nonce_field('aksrt_users_delete_verify_' . (string) $row['application_uuid']); echo '
'; } public function handle_review_verify(): void { if (!current_user_can('aksrt_review_identity') && !current_user_can(self::CAPABILITY)) { wp_die(esc_html__('权限不足', 'aksrt')); } $user_id = (int) ($_POST['user_id'] ?? 0); check_admin_referer('aksrt_users_review_verify_' . $user_id); $uuid = sanitize_text_field((string) wp_unslash($_POST['application_uuid'] ?? '')); $expected = sanitize_key((string) wp_unslash($_POST['expected_status'] ?? '')); $status = sanitize_key((string) wp_unslash($_POST['status'] ?? '')); $note = sanitize_textarea_field((string) wp_unslash($_POST['note'] ?? '')); $application = (new \AKSRT\Identity\ApplicationRepository())->find_by_uuid($uuid); $result = (!$application || (int) $application['user_id'] !== $user_id || (string) $application['status'] !== $expected) ? new \WP_Error('aksrt_verify_conflict', __('认证状态已变化,请刷新后重试', 'aksrt')) : \AKSRT\User\AuthApply::review($user_id, $status, $note, get_current_user_id()); $extra = ['status' => $status === 'cancelled' ? 'cancelled' : 'pending']; if (is_wp_error($result)) $extra['aksrt_verify_error'] = $result->get_error_message(); $this->redirect('aksrt_user_auth', is_wp_error($result) ? 'verify_failed' : 'verify_updated', $extra); } public function handle_delete_verify(): void { if (!current_user_can('aksrt_review_identity') && !current_user_can(self::CAPABILITY)) { wp_die(esc_html__('权限不足', 'aksrt')); } $uuid = sanitize_text_field((string) wp_unslash($_POST['application_uuid'] ?? '')); check_admin_referer('aksrt_users_delete_verify_' . $uuid); $repo = new \AKSRT\Identity\ApplicationRepository(); $application = $repo->find_by_uuid($uuid); if (!$application) { $this->redirect('aksrt_user_auth', 'verify_failed', ['aksrt_verify_error' => __('认证记录不存在', 'aksrt')]); } (new \AKSRT\Identity\EvidenceService())->delete((array) ($application['evidence_ids'] ?? [])); (new \AKSRT\Identity\EventRepository())->delete_for_application((int) $application['id']); $deleted = $repo->delete((int) $application['id']); $this->redirect('aksrt_user_auth', $deleted ? 'verify_deleted' : 'verify_failed', $deleted ? [] : ['aksrt_verify_error' => __('认证记录删除失败', 'aksrt')]); } private function get_verify_history(int $user_id): array { return \AKSRT\User\AuthApply::get_review_history($user_id); } }