Move 288 types from global AKSRT_* + inc/classes manual requires into
namespace AKSRT\{Domain} under src/AKSRT/, wire composer.json psr-4,
and slim bootstrap to helpers/modules/hooks only. Split multi-class
widget/gateway files, update admin contract audit paths, and document
the layout in ADR/README.
380 lines
14 KiB
PHP
380 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* @package AKSRT
|
|
*/
|
|
|
|
namespace AKSRT\Admin;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
trait UserControllerVerifyIntegrations {
|
|
|
|
public function get_verify_pending(): \WP_REST_Response {
|
|
$response = $this->get_verify_requests(new \WP_REST_Request('GET', '/'));
|
|
$data = $response->get_data();
|
|
$items = isset($data['data']['items']) && is_array($data['data']['items']) ? $data['data']['items'] : [];
|
|
$pending = array_values(array_filter($items, static function ($item) {
|
|
return ($item['status'] ?? '') === 'pending';
|
|
}));
|
|
|
|
return $this->success([
|
|
'data' => [
|
|
'requests' => array_slice($pending, 0, 50),
|
|
'total' => count($pending),
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function get_verify_requests(\WP_REST_Request $request): \WP_REST_Response {
|
|
global $wpdb;
|
|
|
|
$page = max(1, (int) ($request->get_param('page') ?: 1));
|
|
$per_page = max(1, min(100, (int) ($request->get_param('per_page') ?: 20)));
|
|
$keyword = trim(sanitize_text_field((string) $request->get_param('keyword')));
|
|
$status = sanitize_key((string) $request->get_param('status'));
|
|
$date_from = sanitize_text_field((string) $request->get_param('date_from'));
|
|
$date_to = sanitize_text_field((string) $request->get_param('date_to'));
|
|
|
|
$sql = "
|
|
SELECT u.ID, u.display_name, u.user_email, u.user_registered,
|
|
status.meta_value AS verify_status,
|
|
reason.meta_value AS verify_reason,
|
|
reviewed_at.meta_value AS reviewed_at,
|
|
reviewer.meta_value AS reviewer_id
|
|
FROM {$wpdb->users} u
|
|
INNER JOIN {$wpdb->usermeta} status
|
|
ON status.user_id = u.ID AND status.meta_key = 'aksrt_user_verify_status'
|
|
LEFT JOIN {$wpdb->usermeta} reason
|
|
ON reason.user_id = u.ID AND reason.meta_key = 'aksrt_user_verify_reason'
|
|
LEFT JOIN {$wpdb->usermeta} reviewed_at
|
|
ON reviewed_at.user_id = u.ID AND reviewed_at.meta_key = 'aksrt_user_verify_reviewed_at'
|
|
LEFT JOIN {$wpdb->usermeta} reviewer
|
|
ON reviewer.user_id = u.ID AND reviewer.meta_key = 'aksrt_user_verify_reviewer'
|
|
ORDER BY u.user_registered DESC
|
|
LIMIT 500
|
|
";
|
|
|
|
$rows = $wpdb->get_results($sql, ARRAY_A);
|
|
|
|
$items = [];
|
|
$counts = ['pending' => 0, 'verified' => 0, 'rejected' => 0, 'total' => 0];
|
|
|
|
foreach ((array) $rows as $row) {
|
|
$item = $this->build_verify_request_item($row);
|
|
$item_status = $item['status'];
|
|
if (isset($counts[$item_status])) {
|
|
$counts[$item_status]++;
|
|
}
|
|
$counts['total']++;
|
|
|
|
if ($status !== '' && $item_status !== $status) {
|
|
continue;
|
|
}
|
|
|
|
if ($date_from !== '' && strcmp((string) $item['requested_at'], $date_from . ' 00:00:00') < 0) {
|
|
continue;
|
|
}
|
|
if ($date_to !== '' && strcmp((string) $item['requested_at'], $date_to . ' 23:59:59') > 0) {
|
|
continue;
|
|
}
|
|
if ($keyword !== '' && mb_stripos(implode(' ', [
|
|
(string) ($item['display_name'] ?? ''),
|
|
(string) ($item['email'] ?? ''),
|
|
(string) ($item['reason'] ?? ''),
|
|
]), $keyword) === false) {
|
|
continue;
|
|
}
|
|
|
|
$items[] = $item;
|
|
}
|
|
|
|
$total = count($items);
|
|
$offset = ($page - 1) * $per_page;
|
|
|
|
return $this->success([
|
|
'data' => [
|
|
'items' => array_values(array_slice($items, $offset, $per_page)),
|
|
'total' => $total,
|
|
'page' => $page,
|
|
'per_page' => $per_page,
|
|
'counts' => $counts,
|
|
'options' => [
|
|
'statuses' => [
|
|
['label' => __('待审核', 'aksrt'), 'value' => 'pending'],
|
|
['label' => __('已通过', 'aksrt'), 'value' => 'verified'],
|
|
['label' => __('已驳回', 'aksrt'), 'value' => 'rejected'],
|
|
],
|
|
],
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function get_verify_request_detail(\WP_REST_Request $request): \WP_REST_Response {
|
|
$user_id = (int) $request->get_param('id');
|
|
if ($user_id <= 0) {
|
|
return $this->error(__('认证申请不存在', 'aksrt'), 404);
|
|
}
|
|
|
|
$user = get_userdata($user_id);
|
|
if (!$user) {
|
|
return $this->error(__('用户不存在', 'aksrt'), 404);
|
|
}
|
|
|
|
$detail = $this->build_verify_request_item([
|
|
'ID' => $user->ID,
|
|
'display_name' => $user->display_name,
|
|
'user_email' => $user->user_email,
|
|
'user_registered' => $user->user_registered,
|
|
'verify_status' => get_user_meta($user_id, 'aksrt_user_verify_status', true),
|
|
'verify_reason' => get_user_meta($user_id, 'aksrt_user_verify_reason', true),
|
|
'reviewed_at' => get_user_meta($user_id, 'aksrt_user_verify_reviewed_at', true),
|
|
'reviewer_id' => get_user_meta($user_id, 'aksrt_user_verify_reviewer', true),
|
|
]);
|
|
|
|
return $this->success([
|
|
'data' => [
|
|
'request' => $detail,
|
|
'history' => $this->get_verify_history_for_user($user_id),
|
|
'options' => [
|
|
'statuses' => [
|
|
['label' => __('通过认证', 'aksrt'), 'value' => 'verified'],
|
|
['label' => __('驳回申请', 'aksrt'), 'value' => 'rejected'],
|
|
],
|
|
],
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* POST /verify/review — approve or reject a verification request.
|
|
*/
|
|
public function review_verify_request(\WP_REST_Request $request): \WP_REST_Response {
|
|
$params = $this->get_json_params($request);
|
|
$user_id = (int) ($params['user_id'] ?? 0);
|
|
if ($user_id <= 0) {
|
|
return $this->error(__('参数无效', 'aksrt'));
|
|
}
|
|
|
|
return $this->perform_verify_review($user_id, $params);
|
|
}
|
|
|
|
public function review_verify_request_by_id(\WP_REST_Request $request): \WP_REST_Response {
|
|
$user_id = (int) $request->get_param('id');
|
|
if ($user_id <= 0) {
|
|
return $this->error(__('参数无效', 'aksrt'));
|
|
}
|
|
|
|
return $this->perform_verify_review($user_id, $this->get_json_params($request));
|
|
}
|
|
|
|
private function perform_verify_review(int $user_id, array $payload): \WP_REST_Response {
|
|
$status = sanitize_key((string) ($payload['status'] ?? ''));
|
|
$note = sanitize_textarea_field((string) ($payload['note'] ?? ($payload['reason'] ?? '')));
|
|
|
|
if (!in_array($status, ['verified', 'rejected'], true)) {
|
|
return $this->error(__('审核状态无效', 'aksrt'));
|
|
}
|
|
|
|
$user = get_userdata($user_id);
|
|
if (!$user) {
|
|
return $this->error(__('用户不存在', 'aksrt'), 404);
|
|
}
|
|
|
|
update_user_meta($user_id, 'aksrt_user_verify_status', $status);
|
|
update_user_meta($user_id, 'aksrt_user_verify_reviewed_at', current_time('mysql'));
|
|
update_user_meta($user_id, 'aksrt_user_verify_reviewer', get_current_user_id());
|
|
|
|
if ($note !== '') {
|
|
update_user_meta($user_id, 'aksrt_user_verify_reason', $note);
|
|
} elseif ($status === 'verified') {
|
|
delete_user_meta($user_id, 'aksrt_user_verify_reason');
|
|
}
|
|
|
|
$log = get_option('aksrt_verify_review_log', []);
|
|
if (!is_array($log)) {
|
|
$log = [];
|
|
}
|
|
$log[] = [
|
|
'user_id' => $user_id,
|
|
'reviewer' => get_current_user_id(),
|
|
'status' => $status,
|
|
'reason' => $note,
|
|
'time' => current_time('mysql'),
|
|
];
|
|
if (count($log) > 500) {
|
|
$log = array_slice($log, -500);
|
|
}
|
|
update_option('aksrt_verify_review_log', $log, false);
|
|
|
|
return $this->success([
|
|
'message' => $status === 'verified'
|
|
? __('已通过认证', 'aksrt')
|
|
: __('已驳回申请', 'aksrt'),
|
|
]);
|
|
}
|
|
|
|
private function build_verify_request_item(array $row): array {
|
|
$user_id = (int) ($row['ID'] ?? 0);
|
|
$reviewer_id = (int) ($row['reviewer_id'] ?? 0);
|
|
$reviewer = $reviewer_id > 0 ? get_userdata($reviewer_id) : null;
|
|
$status = sanitize_key((string) ($row['verify_status'] ?? 'pending'));
|
|
$email_verified = (bool) get_user_meta($user_id, 'aksrt_email_verified', true);
|
|
|
|
return [
|
|
'user_id' => $user_id,
|
|
'display_name' => (string) ($row['display_name'] ?? ''),
|
|
'email' => (string) ($row['user_email'] ?? ''),
|
|
'status' => $status,
|
|
'reason' => (string) ($row['verify_reason'] ?? ''),
|
|
'requested_at' => (string) ($row['user_registered'] ?? ''),
|
|
'reviewed_at' => (string) ($row['reviewed_at'] ?? ''),
|
|
'email_verified' => $email_verified,
|
|
'reviewer' => $this->build_admin_user_summary($reviewer_id, $reviewer),
|
|
'history_count' => count($this->get_verify_history_for_user($user_id)),
|
|
];
|
|
}
|
|
|
|
private function get_verify_history_for_user(int $user_id): array {
|
|
$log = get_option('aksrt_verify_review_log', []);
|
|
if (!is_array($log)) {
|
|
return [];
|
|
}
|
|
|
|
$history = [];
|
|
foreach ($log as $entry) {
|
|
if ((int) ($entry['user_id'] ?? 0) !== $user_id) {
|
|
continue;
|
|
}
|
|
|
|
$reviewer_id = (int) ($entry['reviewer'] ?? 0);
|
|
$history[] = [
|
|
'status' => sanitize_key((string) ($entry['status'] ?? '')),
|
|
'reason' => (string) ($entry['reason'] ?? ''),
|
|
'time' => (string) ($entry['time'] ?? ''),
|
|
'reviewer' => $reviewer_id,
|
|
'reviewer_name' => $reviewer_id > 0 && ($user = get_userdata($reviewer_id)) ? $user->display_name : '',
|
|
];
|
|
}
|
|
|
|
usort($history, static function ($left, $right) {
|
|
return strcmp((string) ($right['time'] ?? ''), (string) ($left['time'] ?? ''));
|
|
});
|
|
|
|
return $history;
|
|
}
|
|
|
|
private function build_admin_user_summary(int $user_id, $user = null): ?array {
|
|
if ($user_id <= 0 && !$user instanceof \WP_User) {
|
|
return null;
|
|
}
|
|
|
|
if (!$user instanceof \WP_User) {
|
|
$user = get_userdata($user_id);
|
|
}
|
|
if (!$user instanceof \WP_User) {
|
|
return null;
|
|
}
|
|
|
|
return [
|
|
'id' => (int) $user->ID,
|
|
'display_name' => (string) $user->display_name,
|
|
'email' => (string) $user->user_email,
|
|
'avatar_url' => get_avatar_url((int) $user->ID, ['size' => 64]),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* GET /sms/status — SMS provider connection status.
|
|
*/
|
|
public function get_sms_status(): \WP_REST_Response {
|
|
if (class_exists('\AKSRT\Sms\SMS')) {
|
|
return $this->success(['data' => \AKSRT\Sms\SMS::get_instance()->get_provider_status()]);
|
|
}
|
|
|
|
return $this->success([
|
|
'data' => [
|
|
'enabled' => false,
|
|
'provider' => 'none',
|
|
'configured' => false,
|
|
'available' => false,
|
|
'verified' => false,
|
|
'transport' => '',
|
|
'missing_fields' => [],
|
|
'code_length' => 6,
|
|
'code_expire' => 300,
|
|
'rate_limit' => 60,
|
|
'daily_limit' => 10,
|
|
'notify_bind' => true,
|
|
'notify_login' => false,
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* POST /sms/test — Send a test SMS to verify provider configuration.
|
|
*/
|
|
public function test_sms_send(\WP_REST_Request $request): \WP_REST_Response {
|
|
if (!class_exists('\AKSRT\Sms\SMS')) {
|
|
return $this->error('短信服务不可用');
|
|
}
|
|
|
|
$params = $this->get_json_params($request);
|
|
$phone = sanitize_text_field((string) ($params['phone'] ?? ''));
|
|
|
|
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
|
|
return $this->error('手机号格式不正确');
|
|
}
|
|
|
|
$result = \AKSRT\Sms\SMS::get_instance()->test_provider($phone);
|
|
|
|
if (!empty($result['success'])) {
|
|
return $this->success(['message' => $result['message'] ?? '发送成功']);
|
|
}
|
|
|
|
return $this->error($result['message'] ?? '发送失败');
|
|
}
|
|
|
|
/**
|
|
* GET /wechat/status — WeChat notification service status.
|
|
*/
|
|
public function get_wechat_status(): \WP_REST_Response {
|
|
if (class_exists('\AKSRT\Payment\WeChatNotify')) {
|
|
return $this->success(['data' => \AKSRT\Payment\WeChatNotify::get_instance()->get_status()]);
|
|
}
|
|
|
|
return $this->success([
|
|
'data' => [
|
|
'appid' => '',
|
|
'configured' => false,
|
|
'has_token' => false,
|
|
'available' => false,
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* POST /wechat/settings — 更新微信通知设置
|
|
*/
|
|
public function update_wechat_settings(\WP_REST_Request $request): \WP_REST_Response {
|
|
$params = $this->get_json_params($request);
|
|
|
|
$settings = [
|
|
'enabled' => !empty($params['enabled']),
|
|
'app_id' => sanitize_text_field((string) ($params['app_id'] ?? '')),
|
|
'app_secret' => sanitize_text_field((string) ($params['app_secret'] ?? '')),
|
|
'template_id' => sanitize_text_field((string) ($params['template_id'] ?? '')),
|
|
];
|
|
|
|
$existing = \AKSRT\Settings\Settings::get_wechat_settings();
|
|
if ($settings['app_secret'] === '***') {
|
|
$settings['app_secret'] = $existing['app_secret'] ?? '';
|
|
}
|
|
|
|
$merged = array_merge($existing, $settings);
|
|
\AKSRT\Settings\Settings::update_wechat_settings($merged);
|
|
delete_transient('aksrt_wechat_token');
|
|
|
|
return $this->success(['message' => __('微信设置已保存', 'aksrt')]);
|
|
}
|
|
}
|