fix(identity): complete native certification review flow
This commit is contained in:
1 parent
32658be87d
commit
2dfaa10798
16 files changed
+571
-1167
No files matched your search
+197
-19
@@ -11,10 +11,14 @@ defined('ABSPATH') || exit;
|
||||
|
||||
class AuthApply {
|
||||
|
||||
public const STATUS_META = 'aksrt_user_verify_status';
|
||||
public const REASON_META = 'aksrt_user_verify_reason';
|
||||
public const APPLY_META = 'aksrt_user_verify_apply';
|
||||
public const INFO_META = 'aksrt_user_verify_info';
|
||||
public const STATUS_META = 'aksrt_user_verify_status';
|
||||
public const REASON_META = 'aksrt_user_verify_reason';
|
||||
public const APPLY_META = 'aksrt_user_verify_apply';
|
||||
public const INFO_META = 'aksrt_user_verify_info';
|
||||
public const REQUESTED_AT_META = 'aksrt_user_verify_requested_at';
|
||||
public const REVIEWED_AT_META = 'aksrt_user_verify_reviewed_at';
|
||||
public const REVIEWER_META = 'aksrt_user_verify_reviewer';
|
||||
public const HISTORY_META = 'aksrt_user_verify_history';
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
@@ -27,6 +31,7 @@ class AuthApply {
|
||||
|
||||
public function init(): void {
|
||||
add_action('wp_ajax_aksrt_user_auth_apply', [$this, 'ajax_apply']);
|
||||
add_action('admin_post_aksrt_verify_evidence', [$this, 'serve_evidence']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,44 +103,209 @@ class AuthApply {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the only public representation of certification evidence.
|
||||
* Return certification evidence only to its owner or an administrator.
|
||||
*
|
||||
* @param array<string,mixed> $application
|
||||
* @return array<int,array{id:int,url:string,thumbnail_url:string}>
|
||||
*/
|
||||
public static function get_evidence_items(array $application): array {
|
||||
public static function get_evidence_items(array $application, int $owner_id = 0, bool $admin_context = false): array {
|
||||
if ($owner_id <= 0) {
|
||||
return [];
|
||||
}
|
||||
$can_view = $admin_context
|
||||
? current_user_can('manage_options')
|
||||
: get_current_user_id() === $owner_id;
|
||||
if (!$can_view) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$items = [];
|
||||
foreach ((array) ($application['evidence_attachment_ids'] ?? []) as $attachment_id) {
|
||||
$attachment_id = (int) $attachment_id;
|
||||
if ($attachment_id <= 0 || get_post_type($attachment_id) !== 'attachment') {
|
||||
if (
|
||||
$attachment_id <= 0
|
||||
|| get_post_type($attachment_id) !== 'attachment'
|
||||
|| (int) get_post_meta($attachment_id, '_aksrt_verify_evidence_owner', true) !== $owner_id
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
$url = wp_get_attachment_url($attachment_id);
|
||||
if (!is_string($url) || $url === '') {
|
||||
continue;
|
||||
}
|
||||
$thumb = wp_get_attachment_image_url($attachment_id, 'thumbnail');
|
||||
$url = add_query_arg([
|
||||
'action' => 'aksrt_verify_evidence',
|
||||
'attachment' => $attachment_id,
|
||||
'_wpnonce' => wp_create_nonce('aksrt_verify_evidence_' . $attachment_id),
|
||||
], admin_url('admin-post.php'));
|
||||
$items[] = [
|
||||
'id' => $attachment_id,
|
||||
'url' => esc_url_raw($url),
|
||||
'thumbnail_url' => esc_url_raw(is_string($thumb) && $thumb !== '' ? $thumb : $url),
|
||||
'thumbnail_url' => esc_url_raw($url),
|
||||
];
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function serve_evidence(): void {
|
||||
if (!is_user_logged_in()) {
|
||||
wp_die(esc_html__('请先登录', 'aksrt'), '', ['response' => 401]);
|
||||
}
|
||||
$attachment_id = isset($_GET['attachment']) ? (int) $_GET['attachment'] : 0;
|
||||
if ($attachment_id <= 0 || !check_admin_referer('aksrt_verify_evidence_' . $attachment_id)) {
|
||||
wp_die(esc_html__('安全校验失败', 'aksrt'), '', ['response' => 403]);
|
||||
}
|
||||
$owner_id = (int) get_post_meta($attachment_id, '_aksrt_verify_evidence_owner', true);
|
||||
if ($owner_id <= 0 || (get_current_user_id() !== $owner_id && !current_user_can('manage_options'))) {
|
||||
wp_die(esc_html__('您没有权限查看该证明材料', 'aksrt'), '', ['response' => 403]);
|
||||
}
|
||||
$file_path = get_attached_file($attachment_id);
|
||||
if (!is_string($file_path) || $file_path === '' || !is_file($file_path) || !is_readable($file_path)) {
|
||||
wp_die(esc_html__('证明材料不存在', 'aksrt'), '', ['response' => 404]);
|
||||
}
|
||||
$mime = (string) get_post_mime_type($attachment_id);
|
||||
if (!in_array($mime, ['image/jpeg', 'image/png', 'image/gif', 'image/webp'], true)) {
|
||||
wp_die(esc_html__('证明材料类型无效', 'aksrt'), '', ['response' => 415]);
|
||||
}
|
||||
|
||||
nocache_headers();
|
||||
header('Content-Type: ' . $mime);
|
||||
header('Content-Disposition: inline; filename="' . rawurlencode(basename($file_path)) . '"');
|
||||
header('Content-Length: ' . (string) filesize($file_path));
|
||||
readfile($file_path);
|
||||
exit;
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $application */
|
||||
public static function application_view(array $application): array {
|
||||
public static function application_view(array $application, int $owner_id = 0, bool $admin_context = false): array {
|
||||
return [
|
||||
'name' => (string) ($application['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),
|
||||
'evidence' => self::get_evidence_items($application, $owner_id, $admin_context),
|
||||
];
|
||||
}
|
||||
|
||||
/** @return array<int,array<string,mixed>> */
|
||||
public static function get_review_history(int $user_id): array {
|
||||
$history = get_user_meta($user_id, self::HISTORY_META, true);
|
||||
$history = is_array($history) ? array_values($history) : [];
|
||||
usort($history, static fn(array $left, array $right): int => strcmp(
|
||||
(string) ($right['time'] ?? ''),
|
||||
(string) ($left['time'] ?? '')
|
||||
));
|
||||
return $history;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply one authoritative certification status transition.
|
||||
*
|
||||
* @return array<string,mixed>|\WP_Error
|
||||
*/
|
||||
public static function review(int $user_id, string $status, string $note, int $reviewer_id) {
|
||||
$status = sanitize_key($status);
|
||||
$note = sanitize_textarea_field($note);
|
||||
if (!in_array($status, ['verified', 'rejected', 'cancelled'], true)) {
|
||||
return new \WP_Error('aksrt_verify_status', __('审核状态无效', 'aksrt'));
|
||||
}
|
||||
if ($user_id <= 0 || !get_userdata($user_id) || $reviewer_id <= 0) {
|
||||
return new \WP_Error('aksrt_verify_user', __('用户不存在', 'aksrt'));
|
||||
}
|
||||
if ($status === 'cancelled' && $note === '') {
|
||||
return new \WP_Error('aksrt_verify_note', __('取消认证时必须填写原因', 'aksrt'));
|
||||
}
|
||||
if ($status === 'rejected' && $note === '') {
|
||||
return new \WP_Error('aksrt_verify_note', __('驳回申请时必须填写原因', 'aksrt'));
|
||||
}
|
||||
|
||||
$current = self::get_status($user_id);
|
||||
$allowed = $status === 'cancelled'
|
||||
? $current === 'verified'
|
||||
: $current === 'pending';
|
||||
$application = self::get_apply_payload($user_id);
|
||||
if (!$allowed || $application === [] || empty($application['name']) || empty($application['type'])) {
|
||||
return new \WP_Error('aksrt_verify_transition', __('该认证记录当前不可执行此操作', 'aksrt'));
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
$rows = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT umeta_id, meta_value
|
||||
FROM {$wpdb->usermeta}
|
||||
WHERE user_id = %d AND meta_key = %s
|
||||
ORDER BY umeta_id ASC",
|
||||
$user_id,
|
||||
self::STATUS_META
|
||||
), ARRAY_A);
|
||||
if (count((array) $rows) !== 1 || sanitize_key((string) ($rows[0]['meta_value'] ?? '')) !== $current) {
|
||||
return new \WP_Error('aksrt_verify_state', __('认证状态数据异常,请修复后重试', 'aksrt'));
|
||||
}
|
||||
$updated = $wpdb->query($wpdb->prepare(
|
||||
"UPDATE {$wpdb->usermeta}
|
||||
SET meta_value = %s
|
||||
WHERE umeta_id = %d AND meta_value = %s",
|
||||
$status,
|
||||
(int) $rows[0]['umeta_id'],
|
||||
$current
|
||||
));
|
||||
if ($updated !== 1) {
|
||||
return new \WP_Error('aksrt_verify_conflict', __('认证状态已变化,请刷新后重试', 'aksrt'));
|
||||
}
|
||||
|
||||
$reviewed_at = current_time('mysql');
|
||||
update_user_meta($user_id, self::REVIEWED_AT_META, $reviewed_at);
|
||||
update_user_meta($user_id, self::REVIEWER_META, $reviewer_id);
|
||||
if ($note !== '') {
|
||||
update_user_meta($user_id, self::REASON_META, $note);
|
||||
} else {
|
||||
delete_user_meta($user_id, self::REASON_META);
|
||||
}
|
||||
|
||||
if ($status === 'verified') {
|
||||
update_user_meta($user_id, self::INFO_META, [
|
||||
'name' => (string) ($application['name'] ?? ''),
|
||||
'desc' => (string) ($application['desc'] ?? ''),
|
||||
'type' => sanitize_key((string) ($application['type'] ?? '')),
|
||||
'time' => current_time('Y-m-d H:i'),
|
||||
]);
|
||||
} elseif ($status === 'cancelled') {
|
||||
delete_user_meta($user_id, self::INFO_META);
|
||||
}
|
||||
|
||||
$entry = [
|
||||
'status' => $status,
|
||||
'reason' => $note,
|
||||
'reviewer' => $reviewer_id,
|
||||
'time' => $reviewed_at,
|
||||
'application' => self::application_view($application),
|
||||
];
|
||||
$history = self::get_review_history($user_id);
|
||||
array_unshift($history, $entry);
|
||||
update_user_meta($user_id, self::HISTORY_META, array_slice($history, 0, 50));
|
||||
|
||||
self::send_review_notification($user_id, $status, $note);
|
||||
do_action('aksrt_user_auth_reviewed', $user_id, $status, $note, $reviewer_id);
|
||||
|
||||
return ['status' => $status, 'reviewed_at' => $reviewed_at];
|
||||
}
|
||||
|
||||
private static function send_review_notification(int $user_id, string $status, string $note): void {
|
||||
if (!class_exists('\\AKSRT\\Messages\\Message')) {
|
||||
return;
|
||||
}
|
||||
if ($status === 'verified') {
|
||||
$message = __('您的官方认证申请已通过审核', 'aksrt');
|
||||
$title = __('官方认证审核结果', 'aksrt');
|
||||
} elseif ($status === 'cancelled') {
|
||||
$message = sprintf(__('您的官方认证已被取消:%s', 'aksrt'), $note);
|
||||
$title = __('官方认证已取消', 'aksrt');
|
||||
} else {
|
||||
$message = sprintf(__('您的官方认证申请未通过:%s', 'aksrt'), $note);
|
||||
$title = __('官方认证审核结果', 'aksrt');
|
||||
}
|
||||
\AKSRT\Messages\Message::get_instance()->send($user_id, 'auth_apply_reply', $message, [
|
||||
'title' => $title,
|
||||
'target_url' => aksrt_get_user_center_tab_url('auth'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function ajax_apply(): void {
|
||||
if (strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? '')) !== 'POST') {
|
||||
wp_send_json_error(['message' => __('请求方式无效', 'aksrt')], 405);
|
||||
@@ -222,13 +392,14 @@ class AuthApply {
|
||||
$this->renew_submission_lock($lock_key, $lock_token);
|
||||
|
||||
$previous_payload = self::get_apply_payload($user_id);
|
||||
$requested_at = current_time('Y-m-d H:i');
|
||||
$payload = [
|
||||
'name' => $name,
|
||||
'desc' => $desc,
|
||||
'msg' => $msg,
|
||||
'type' => $type,
|
||||
'evidence_attachment_ids' => $uploaded,
|
||||
'time' => current_time('Y-m-d H:i'),
|
||||
'time' => $requested_at,
|
||||
];
|
||||
|
||||
$payload_saved = update_user_meta($user_id, self::APPLY_META, $payload);
|
||||
@@ -246,14 +417,17 @@ class AuthApply {
|
||||
$this->release_submission_lock($lock_key, $lock_token);
|
||||
wp_send_json_error(['message' => __('认证申请保存失败,请重试', 'aksrt')], 500);
|
||||
}
|
||||
update_user_meta($user_id, self::REQUESTED_AT_META, $requested_at);
|
||||
delete_user_meta($user_id, self::REVIEWED_AT_META);
|
||||
delete_user_meta($user_id, self::REVIEWER_META);
|
||||
delete_user_meta($user_id, self::REASON_META);
|
||||
|
||||
if (!$this->owns_submission_lock($lock_key, $lock_token)) {
|
||||
$this->release_submission_lock($lock_key, $lock_token);
|
||||
wp_send_json_error(['message' => __('认证申请已提交,请刷新查看最新状态', 'aksrt')], 409);
|
||||
}
|
||||
$this->renew_submission_lock($lock_key, $lock_token);
|
||||
delete_user_meta($user_id, self::REASON_META);
|
||||
$this->delete_attachments(array_column(self::get_evidence_items($previous_payload), 'id'));
|
||||
$this->delete_attachments(array_column(self::get_evidence_items($previous_payload, $user_id), 'id'));
|
||||
|
||||
/** @param int $user_id @param array $payload */
|
||||
do_action('aksrt_user_auth_apply_submitted', $user_id, $payload);
|
||||
@@ -314,7 +488,11 @@ class AuthApply {
|
||||
$this->delete_attachments($attachment_ids);
|
||||
return new \WP_Error('aksrt_auth_upload_type', __('证明材料类型校验失败', 'aksrt'));
|
||||
}
|
||||
wp_update_post(['ID' => $attachment_id, 'post_author' => $user_id]);
|
||||
wp_update_post([
|
||||
'ID' => $attachment_id,
|
||||
'post_author' => $user_id,
|
||||
'post_status' => 'private',
|
||||
]);
|
||||
update_post_meta($attachment_id, '_aksrt_verify_evidence_owner', $user_id);
|
||||
$attachment_ids[] = $attachment_id;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user