184 lines
11 KiB
PHP
184 lines
11 KiB
PHP
<?php
|
|
/** Native WordPress identity application review. @package AKSRT */
|
|
|
|
namespace AKSRT\Admin\Native;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
trait UsersMenusVerify {
|
|
|
|
private function count_verify_status(string $status): int {
|
|
global $wpdb;
|
|
$table = \AKSRT\Core\DBMigrator::table('identity_application');
|
|
$stored = $status === 'pending' ? 'manual_pending' : $status;
|
|
return (int) $wpdb->get_var($wpdb->prepare(
|
|
"SELECT COUNT(*) FROM {$table} WHERE type = %s AND channel = %s AND status = %s",
|
|
'general',
|
|
'manual',
|
|
$stored
|
|
));
|
|
}
|
|
|
|
public function render_verify_page(): void {
|
|
$this->ensure_cap();
|
|
$filters = $this->get_verify_filters();
|
|
$page = max(1, (int) ($_GET['paged'] ?? 1));
|
|
$data = $this->query_verify_requests($filters, $page, 20);
|
|
$counts = $this->get_verify_status_counts();
|
|
echo '<div class="wrap"><h1>' . esc_html__('身份认证', 'aksrt') . '</h1>';
|
|
$this->flash();
|
|
printf('<p>%s</p>', esc_html(sprintf(__('待审核 %1$d · 已通过 %2$d · 已拒绝 %3$d', 'aksrt'), $counts['pending'], $counts['approved'], $counts['rejected'])));
|
|
$this->render_verify_filters($filters);
|
|
$this->render_verify_table($data['items']);
|
|
$this->pagination($data['pages'], $page);
|
|
echo '</div>';
|
|
}
|
|
|
|
/** @return array{status:string,keyword:string} */
|
|
private function get_verify_filters(): array {
|
|
$status = isset($_GET['status']) ? sanitize_key((string) wp_unslash($_GET['status'])) : 'pending';
|
|
if (!in_array($status, ['pending', 'approved', 'rejected', ''], true)) $status = 'pending';
|
|
return [
|
|
'status' => $status,
|
|
'keyword' => isset($_GET['keyword']) ? sanitize_text_field((string) wp_unslash($_GET['keyword'])) : '',
|
|
];
|
|
}
|
|
|
|
/** @return array{pending:int,approved:int,rejected:int} */
|
|
private function get_verify_status_counts(): array {
|
|
return [
|
|
'pending' => $this->count_verify_status('pending'),
|
|
'approved' => $this->count_verify_status('verified'),
|
|
'rejected' => $this->count_verify_status('rejected'),
|
|
];
|
|
}
|
|
|
|
/** @return array{items:array<int,array<string,mixed>>,pages:int} */
|
|
private function query_verify_requests(array $filters, int $page, int $per_page): array {
|
|
global $wpdb;
|
|
$table = \AKSRT\Core\DBMigrator::table('identity_application');
|
|
$where = ["a.type = 'general'", "a.channel = 'manual'"];
|
|
$params = [];
|
|
if ($filters['status'] !== '') {
|
|
$where[] = 'a.status = %s';
|
|
$params[] = $filters['status'] === 'pending' ? 'manual_pending' : ($filters['status'] === 'approved' ? 'verified' : $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.provider_result_summary LIKE %s)';
|
|
array_push($params, $like, $like, $like, $like, $like, $like);
|
|
}
|
|
$condition = implode(' AND ', $where);
|
|
$base = "FROM {$table} a INNER JOIN {$wpdb->users} u ON u.ID = a.user_id WHERE {$condition}";
|
|
$count_sql = "SELECT COUNT(*) {$base}";
|
|
$total = $params ? (int) $wpdb->get_var($wpdb->prepare($count_sql, $params)) : (int) $wpdb->get_var($count_sql);
|
|
$limit = max(1, min(100, $per_page));
|
|
$query_params = array_merge($params, [$limit, (max(1, $page) - 1) * $limit]);
|
|
$sql = "SELECT a.id, a.application_uuid, a.user_id, a.type, a.channel, a.status, a.public_name, a.provider_result_summary AS description, a.manual_reason, a.review_note, a.evidence_ids, a.created_at, u.display_name, u.user_login, u.user_email {$base} ORDER BY a.created_at DESC, a.id DESC LIMIT %d OFFSET %d";
|
|
$rows = $wpdb->get_results($wpdb->prepare($sql, $query_params), ARRAY_A);
|
|
return ['items' => is_array($rows) ? $rows : [], 'pages' => max(1, (int) ceil(max(1, $total) / $limit))];
|
|
}
|
|
|
|
private function render_verify_filters(array $filters): void {
|
|
echo '<form method="get" style="margin:12px 0;display:flex;gap:8px;align-items:end;flex-wrap:wrap;">';
|
|
echo '<input type="hidden" name="page" value="aksrt_user_auth" />';
|
|
echo '<label>' . esc_html__('状态', 'aksrt') . '<br><select name="status">';
|
|
foreach (['pending' => __('待审核', 'aksrt'), 'approved' => __('已通过', 'aksrt'), 'rejected' => __('已拒绝', 'aksrt'), '' => __('全部', 'aksrt')] as $key => $label) {
|
|
printf('<option value="%s"%s>%s</option>', esc_attr($key), selected($filters['status'], $key, false), esc_html($label));
|
|
}
|
|
echo '</select></label><label>' . esc_html__('搜索', 'aksrt') . '<br><input type="search" name="keyword" value="' . esc_attr($filters['keyword']) . '" /></label>';
|
|
submit_button(__('筛选', 'aksrt'), 'secondary', '', false);
|
|
echo '</form>';
|
|
}
|
|
|
|
private function render_verify_table(array $items): void {
|
|
echo '<table class="wp-list-table widefat fixed striped"><thead><tr><th>用户</th><th>状态</th><th>认证名称</th><th>认证简介 / 申请说明</th><th>证明材料</th><th>审核</th></tr></thead><tbody>';
|
|
if ($items === []) echo '<tr><td colspan="6">' . esc_html__('暂无认证申请', 'aksrt') . '</td></tr>';
|
|
foreach ($items as $row) $this->render_verify_row($row);
|
|
echo '</tbody></table>';
|
|
}
|
|
|
|
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;
|
|
echo '<tr><td><strong><a href="' . esc_url(get_edit_user_link($user_id)) . '">' . esc_html((string) $row['display_name']) . '</a></strong><div>@' . esc_html((string) $row['user_login']) . ' · UID ' . $user_id . '</div><div>' . esc_html((string) $row['user_email']) . '</div></td>';
|
|
echo '<td>' . esc_html($this->verify_status_label($status)) . '<div class="description">' . esc_html((string) $row['created_at']) . '</div></td>';
|
|
echo '<td><strong>' . esc_html((string) $row['public_name']) . '</strong></td>';
|
|
echo '<td>' . esc_html((string) $row['description']) . '<p class="description">' . esc_html((string) $row['manual_reason']) . '</p>';
|
|
if (!empty($row['review_note'])) echo '<p class="description">' . esc_html__('处理留言:', 'aksrt') . esc_html((string) $row['review_note']) . '</p>';
|
|
echo '</td><td>';
|
|
foreach ((new \AKSRT\Identity\EvidenceService())->items($application, true) as $item) {
|
|
echo '<a href="' . esc_url($item['url']) . '" target="_blank" rel="noopener noreferrer"><img src="' . esc_url($item['thumbnail_url']) . '" alt="" style="width:64px;height:64px;object-fit:cover;margin:4px;border-radius:4px;" /></a>';
|
|
}
|
|
echo '</td><td>';
|
|
$this->render_verify_actions($row);
|
|
echo '</td></tr>';
|
|
}
|
|
|
|
private function verify_status_label(string $status): string {
|
|
$labels = ['manual_pending' => __('待审核', 'aksrt'), 'verified' => __('已通过', 'aksrt'), 'rejected' => __('已拒绝', 'aksrt')];
|
|
return (string) ($labels[$status] ?? $status);
|
|
}
|
|
|
|
private function render_verify_actions(array $row): void {
|
|
$status = (string) $row['status'];
|
|
if ($status === 'manual_pending') {
|
|
foreach (['verified' => __('通过申请', 'aksrt'), 'rejected' => __('拒绝申请', 'aksrt')] as $next => $label) {
|
|
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="display:grid;gap:5px;margin-bottom:10px;">';
|
|
echo '<input type="hidden" name="action" value="aksrt_users_review_verify"><input type="hidden" name="user_id" value="' . (int) $row['user_id'] . '"><input type="hidden" name="application_uuid" value="' . esc_attr((string) $row['application_uuid']) . '"><input type="hidden" name="status" value="' . esc_attr($next) . '">';
|
|
wp_nonce_field('aksrt_users_review_verify_' . (int) $row['user_id']);
|
|
echo '<input name="name" type="text" maxlength="64" value="' . esc_attr((string) $row['public_name']) . '" placeholder="' . esc_attr__('认证名称', 'aksrt') . '" required>';
|
|
echo '<textarea name="description" rows="2" placeholder="' . esc_attr__('认证简介', 'aksrt') . '">' . esc_textarea((string) $row['description']) . '</textarea>';
|
|
echo '<textarea name="note" rows="2" ' . ($next === 'rejected' ? 'required' : '') . ' placeholder="' . esc_attr__('处理留言;拒绝时请填写原因', 'aksrt') . '"></textarea>';
|
|
echo '<button class="button ' . ($next === 'verified' ? 'button-primary' : '') . '">' . esc_html($label) . '</button></form>';
|
|
}
|
|
} else {
|
|
echo '<span class="description">' . esc_html__('已处理', 'aksrt') . '</span>';
|
|
}
|
|
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" onsubmit="return confirm(\'' . esc_js(__('确定删除此认证申请记录及其证明材料?', 'aksrt')) . '\');">';
|
|
echo '<input type="hidden" name="action" value="aksrt_users_delete_verify"><input type="hidden" name="application_uuid" value="' . esc_attr((string) $row['application_uuid']) . '">';
|
|
wp_nonce_field('aksrt_users_delete_verify_' . (string) $row['application_uuid']);
|
|
echo '<button class="button button-link-delete">' . esc_html__('删除记录', 'aksrt') . '</button></form>';
|
|
}
|
|
|
|
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 = absint($_POST['user_id'] ?? 0);
|
|
check_admin_referer('aksrt_users_review_verify_' . $user_id);
|
|
$uuid = sanitize_text_field((string) wp_unslash($_POST['application_uuid'] ?? ''));
|
|
$status = sanitize_key((string) wp_unslash($_POST['status'] ?? ''));
|
|
$note = sanitize_textarea_field((string) wp_unslash($_POST['note'] ?? ''));
|
|
$fields = [
|
|
'name' => sanitize_text_field((string) wp_unslash($_POST['name'] ?? '')),
|
|
'description' => sanitize_textarea_field((string) wp_unslash($_POST['description'] ?? '')),
|
|
];
|
|
$result = \AKSRT\User\AuthApply::review($user_id, $uuid, $status, $note, get_current_user_id(), $fields);
|
|
$extra = ['status' => '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
|
|
|| (string) ($application['type'] ?? '') !== 'general'
|
|
|| (string) ($application['channel'] ?? '') !== 'manual'
|
|
) {
|
|
$this->redirect('aksrt_user_auth', 'verify_failed', ['aksrt_verify_error' => __('认证记录不存在', 'aksrt')]);
|
|
}
|
|
$deleted = $repo->delete((int) $application['id']);
|
|
if (!$deleted) {
|
|
$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']);
|
|
$this->redirect('aksrt_user_auth', 'verify_deleted');
|
|
}
|
|
}
|