*/ public static function settings(): array { $identity = class_exists('\\AKSRT\\Settings\\Settings') ? \AKSRT\Settings\Settings::get_user_identity_settings() : []; return is_array($identity['verify'] ?? null) ? $identity['verify'] : []; } /** @return array */ public static function type_settings(string $type): array { $types = self::settings()['types'] ?? []; $type = sanitize_key($type); return is_array($types[$type] ?? null) ? $types[$type] : []; } /** * Read the current user's identity state from one repository context. * * The repository memoizes the latest/active/verified records for the * request, while this typed facade keeps all presentation callers on the * same application selection and next-action contract. * * @return array */ public static function snapshot(int $user_id): array { $user_id = absint($user_id); $settings = self::settings(); $records = (new ApplicationRepository())->snapshot_for_user($user_id); $verified = is_array($records['verified'] ?? null) ? $records['verified'] : null; $latest = is_array($records['latest'] ?? null) ? $records['latest'] : null; $active = is_array($records['active'] ?? null) ? $records['active'] : null; $status_record = $verified ?: $latest; $status = $status_record ? TransitionService::public_status((string) ($status_record['status'] ?? '')) : ''; $current = $active ?: $latest; $stage = $active ? (string) ($active['status'] ?? '') : (string) ($latest['status'] ?? ''); $next_action = self::next_action($stage); $runtime_settings = class_exists('\\AKSRT\\Settings\\Settings') ? SettingsRuntime::get() : []; $poll_interval = max(5, (int) ($runtime_settings['poll_interval_seconds'] ?? 5)); $can_apply = !empty($settings['enabled']) && !empty($settings['allow_apply']) && $status !== 'verified'; return [ 'user_id' => $user_id, 'verified' => $verified !== null, 'status' => $status, 'stage' => $stage, 'next_action' => $next_action, 'poll_interval' => $poll_interval, 'can_apply' => $can_apply, 'latest' => $latest, 'active' => $active, 'current' => $current, 'info' => $verified ? self::info_from_application($verified) : [], 'apply' => self::payload_from_application($latest), ]; } /** * Safe browser-facing snapshot; raw application rows never cross this * boundary. * * @return array */ public static function snapshot_view(int $user_id): array { $snapshot = self::snapshot($user_id); $latest = is_array($snapshot['latest'] ?? null) ? $snapshot['latest'] : null; $active = is_array($snapshot['active'] ?? null) ? $snapshot['active'] : null; $current = is_array($snapshot['current'] ?? null) ? $snapshot['current'] : null; return [ 'user_id' => (int) ($snapshot['user_id'] ?? 0), 'verified' => !empty($snapshot['verified']), 'status' => (string) ($snapshot['status'] ?? ''), 'stage' => (string) ($snapshot['stage'] ?? ''), 'next_action' => (string) ($snapshot['next_action'] ?? 'wait'), 'poll_interval' => (int) ($snapshot['poll_interval'] ?? 5), 'can_apply' => !empty($snapshot['can_apply']), 'info' => is_array($snapshot['info'] ?? null) ? $snapshot['info'] : [], 'current' => $current ? self::application_view_model($current, (int) ($snapshot['poll_interval'] ?? 5)) : null, 'active' => $active ? self::application_view_model($active, (int) ($snapshot['poll_interval'] ?? 5)) : null, 'latest' => $latest ? self::application_view_model($latest, (int) ($snapshot['poll_interval'] ?? 5)) : null, ]; } private static function next_action(string $stage): string { if (in_array($stage, ['provider_pending', 'provider_processing'], true)) { return 'poll'; } if (in_array($stage, ['provider_failed', 'provider_unsupported', 'provider_expired'], true)) { return 'manual_or_retry'; } if ($stage === 'verified') { return 'complete'; } return 'wait'; } /** @return array */ private static function info_from_application(array $application): array { return [ 'name' => (string) ($application['public_name'] ?? ''), 'type' => (string) ($application['type'] ?? ''), 'time' => (string) (($application['completed_at'] ?? '') ?: ($application['updated_at'] ?? '')), ]; } /** @return array */ private static function payload_from_application(?array $application): array { if (!$application) { return []; } return [ 'application_uuid' => (string) ($application['application_uuid'] ?? ''), 'user_id' => (int) ($application['user_id'] ?? 0), 'name' => (string) ($application['public_name'] ?? ''), 'real_name' => (string) ($application['person_name_masked'] ?? ''), 'msg' => (string) ($application['manual_reason'] ?? ''), 'type' => (string) ($application['type'] ?? ''), 'time' => (string) ($application['created_at'] ?? ''), 'fee' => (string) ($application['fee_amount'] ?? '0.00'), '_application' => $application, ]; } /** @return array */ private static function application_view_model(array $application, int $poll_interval): array { $stage = (string) ($application['status'] ?? ''); return [ 'uuid' => (string) ($application['application_uuid'] ?? ''), 'type' => (string) ($application['type'] ?? ''), 'status' => TransitionService::public_status($stage), 'stage' => $stage, 'next_action' => self::next_action($stage), 'public_name' => (string) ($application['public_name'] ?? ''), 'result_code' => (string) ($application['provider_result_code'] ?? ''), 'message' => (string) ($application['provider_result_summary'] ?? ''), 'redirect_url' => esc_url_raw((string) ($application['redirect_url'] ?? '')), 'expires_at' => (string) ($application['expires_at'] ?? ''), 'created_at' => (string) ($application['created_at'] ?? ''), 'poll_interval' => $poll_interval, ]; } public static function application_count(int $user_id, string $type): int { return (new ApplicationRepository())->count_user_type($user_id, sanitize_key($type)); } public static function get_status(int $user_id): string { return (string) (self::snapshot($user_id)['status'] ?? ''); } public static function is_verified(int $user_id): bool { return !empty(self::snapshot($user_id)['verified']); } /** @return array */ public static function get_auth_info(int $user_id): array { $info = self::snapshot($user_id)['info'] ?? []; return is_array($info) ? $info : []; } /** @return array */ public static function get_apply_payload(int $user_id): array { $payload = self::snapshot($user_id)['apply'] ?? []; return is_array($payload) ? $payload : []; } /** @return array */ public static function get_evidence_items(array $application, int $owner_id = 0, bool $admin_context = false): array { $record = is_array($application['_application'] ?? null) ? $application['_application'] : null; if (!$record && !empty($application['application_uuid'])) { $record = (new ApplicationRepository())->find_by_uuid((string) $application['application_uuid']); } return $record ? (new EvidenceService())->items($record, $admin_context) : []; } /** @return array */ public static function application_view(array $application, int $owner_id = 0, bool $admin_context = false): array { return [ 'name' => (string) ($application['name'] ?? ''), 'real_name' => (string) ($application['real_name'] ?? ''), 'desc' => (string) ($application['desc'] ?? ''), 'msg' => (string) ($application['msg'] ?? ''), 'type' => sanitize_key((string) ($application['type'] ?? '')), 'time' => (string) ($application['time'] ?? ''), 'evidence' => self::get_evidence_items($application, $owner_id, $admin_context), ]; } /** @return array> */ public static function get_review_history(int $user_id): array { $application = (new ApplicationRepository())->latest_for_user($user_id); if (!$application) { return []; } $history = []; foreach ((new EventRepository())->for_application((int) $application['id']) as $event) { $history[] = [ 'status' => TransitionService::public_status((string) ($event['to_status'] ?? '')), 'reason' => (string) ($event['result_code'] ?? ''), 'reviewer' => (int) ($event['actor_user_id'] ?? 0), 'time' => (string) ($event['created_at'] ?? ''), ]; } return $history; } /** @param array $profile_fields */ public static function review(int $user_id, string $application_uuid, string $status, string $note, int $reviewer_id, array $profile_fields) { $repo = new ApplicationRepository(); $application_uuid = sanitize_text_field($application_uuid); $application = wp_is_uuid($application_uuid) ? $repo->find_by_uuid($application_uuid) : null; if (!$application || (int) $application['user_id'] !== $user_id) { return new \WP_Error('aksrt_verify_missing', __('认证申请不存在', 'aksrt')); } $status = sanitize_key($status); $note = sanitize_textarea_field($note); if (!in_array($status, ['manual_pending', 'verified', 'rejected', 'cancelled'], true)) { return new \WP_Error('aksrt_verify_status', __('审核状态无效', 'aksrt')); } if (in_array($status, ['rejected', 'cancelled'], true) && $note === '') { return new \WP_Error('aksrt_verify_note', __('请填写处理原因', 'aksrt')); } $fields = []; foreach (['type', 'public_name'] as $key) { if (array_key_exists($key, $profile_fields)) { $fields[$key] = (string) $profile_fields[$key]; } } $fields['review_note'] = $note; $fields['reviewer_id'] = $reviewer_id; $result = (new TransitionService($repo))->admin_transition($application, $status, $fields, [ 'source' => 'admin', 'event_type' => 'manual_review', 'actor_user_id' => $reviewer_id, ]); return is_wp_error($result) ? $result : ['status' => $status, 'reviewed_at' => (string) $result['completed_at']]; } public function add_admin_user_column(array $columns): array { $columns['aksrt_identity_status'] = __('身份认证', 'aksrt'); return $columns; } public function render_admin_user_column(string $output, string $column_name, int $user_id): string { if ($column_name !== 'aksrt_identity_status') { return $output; } $status = self::get_status($user_id); $labels = ['pending' => __('进行中', 'aksrt'), 'verified' => __('已认证', 'aksrt'), 'rejected' => __('未通过', 'aksrt'), 'cancelled' => __('已取消', 'aksrt')]; if ($status === '') { return esc_html__('未认证', 'aksrt'); } $label = (string) ($labels[$status] ?? $status); if ($status === 'verified') { $info = self::get_auth_info($user_id); $label .= ' · ' . self::admin_type_label((string) ($info['type'] ?? 'personal')); } return esc_html($label); } public function render_admin_profile_status(\WP_User $user): void { $user_id = (int) $user->ID; $snapshot = self::snapshot($user_id); $application = is_array($snapshot['verified'] ?? null) ? $snapshot['verified'] : (is_array($snapshot['latest'] ?? null) ? $snapshot['latest'] : null); $raw_status = (string) ($application['status'] ?? ''); $can_manage = current_user_can('aksrt_review_identity') && current_user_can('edit_user', $user_id); $public_name = (string) ($application['public_name'] ?? ''); $identity_type = in_array((string) ($application['type'] ?? ''), ['personal', 'enterprise'], true) ? (string) $application['type'] : 'personal'; $selected_status = $raw_status === 'cancelled' ? '' : $raw_status; $status_options = [ '' => __('未认证', 'aksrt'), 'manual_pending' => __('待审核', 'aksrt'), 'verified' => __('已认证', 'aksrt'), 'rejected' => __('已驳回', 'aksrt'), ]; if ($raw_status !== '' && $raw_status !== 'cancelled' && !isset($status_options[$raw_status])) { $status_options = [$raw_status => self::admin_status_label($raw_status)] + $status_options; } ?>

snapshot_for_user($user_id); $application = is_array($snapshot['verified'] ?? null) ? $snapshot['verified'] : (is_array($snapshot['latest'] ?? null) ? $snapshot['latest'] : null); $application_uuid = sanitize_text_field((string) wp_unslash($_POST['aksrt_identity_application_uuid'] ?? '')); $current_uuid = (string) ($application['application_uuid'] ?? ''); if (!hash_equals($current_uuid, $application_uuid)) { $this->set_admin_profile_notice('error', __('身份认证记录已变化,请刷新后重试。', 'aksrt')); return; } $public_name = wp_html_excerpt(sanitize_text_field((string) wp_unslash($_POST['aksrt_identity_public_name'] ?? '')), 64, ''); $identity_type = sanitize_key((string) wp_unslash($_POST['aksrt_identity_type'] ?? '')); $status = sanitize_key((string) wp_unslash($_POST['aksrt_identity_status'] ?? '')); $note = sanitize_textarea_field((string) wp_unslash($_POST['aksrt_identity_review_note'] ?? '')); if (!in_array($identity_type, ['personal', 'enterprise'], true)) { $this->set_admin_profile_notice('error', __('认证类型无效。', 'aksrt')); return; } $fields = [ 'type' => $identity_type, 'public_name' => $public_name, ]; $current_status = (string) ($application['status'] ?? ''); if (!in_array($status, ['', 'manual_pending', 'verified', 'rejected'], true) && $status !== $current_status) { $this->set_admin_profile_notice('error', __('认证状态无效。', 'aksrt')); return; } $target_status = $status === '' ? 'cancelled' : $status; if ($target_status === 'verified' && $public_name === '') { $this->set_admin_profile_notice('error', __('设为已认证时请填写姓名 / 主体全称。', 'aksrt')); return; } if (in_array($target_status, ['rejected', 'cancelled'], true) && $target_status !== $current_status && $note === '') { $this->set_admin_profile_notice('error', __('改为已驳回或未认证时请填写处理说明。', 'aksrt')); return; } if ($application === null && $status === '') { return; } if ($application === null) { $application = $this->create_admin_application($repo, $user_id, $fields); if (is_wp_error($application)) { $this->set_admin_profile_notice('error', $application->get_error_message()); return; } $application_uuid = (string) $application['application_uuid']; $current_status = (string) $application['status']; } if ($target_status === $current_status) { $updated = $repo->update((int) $application['id'], $fields); $this->set_admin_profile_notice($updated ? 'success' : 'error', $updated ? __('身份认证资料已更新。', 'aksrt') : __('身份认证资料更新失败。', 'aksrt')); return; } $result = self::review($user_id, $application_uuid, $target_status, $note, get_current_user_id(), $fields); $this->set_admin_profile_notice( is_wp_error($result) ? 'error' : 'success', is_wp_error($result) ? $result->get_error_message() : __('身份认证已更新。', 'aksrt') ); } /** @param array $fields */ private function create_admin_application(ApplicationRepository $repo, int $user_id, array $fields) { $application_uuid = wp_generate_uuid4(); $client_request_id = wp_generate_uuid4(); return $repo->create([ 'application_uuid' => $application_uuid, 'client_request_id' => $client_request_id, 'request_payload_hash' => hash('sha256', 'admin-profile|' . $user_id . '|' . $client_request_id), 'user_id' => $user_id, 'type' => $fields['type'], 'channel' => 'manual', 'provider' => '', 'status' => 'created', 'public_name' => $fields['public_name'], 'fee_amount' => '0.00', 'fee_status' => 'not_required', 'attempt_ordinal' => $repo->count_user_type($user_id, $fields['type']) + 1, ]); } public function render_admin_profile_notice(): void { $key = 'aksrt_identity_profile_notice_' . get_current_user_id(); $notice = get_transient($key); if (!is_array($notice) || empty($notice['message'])) { return; } delete_transient($key); $type = ($notice['type'] ?? '') === 'success' ? 'success' : 'error'; echo '

' . esc_html((string) $notice['message']) . '

'; } private function set_admin_profile_notice(string $type, string $message): void { set_transient('aksrt_identity_profile_notice_' . get_current_user_id(), [ 'type' => $type, 'message' => $message, ], 60); } private static function admin_status_label(string $status): string { $labels = [ 'created' => __('已创建', 'aksrt'), 'provider_pending' => __('等待核身', 'aksrt'), 'provider_processing' => __('云端处理中', 'aksrt'), 'provider_failed' => __('云认证失败', 'aksrt'), 'provider_unsupported' => __('云端不支持', 'aksrt'), 'provider_expired' => __('会话已过期', 'aksrt'), 'manual_pending' => __('人工待审', 'aksrt'), 'verified' => __('已认证', 'aksrt'), 'rejected' => __('已驳回', 'aksrt'), 'cancelled' => __('已取消', 'aksrt'), ]; return $labels[$status] ?? $status; } private static function admin_type_label(string $type): string { return $type === 'enterprise' ? __('企业认证', 'aksrt') : __('个人认证', 'aksrt'); } }