feat(pages): 1:1 用户身份认证页 user-auth

Public apply/status shell on aksrt_user_verify_* meta, AJAX submit with
materials upload, admin approve promotes apply payload to auth info.
This commit is contained in:
2026-07-18 21:15:15 +08:00
parent 1cb534e4fe
commit cd04a03329
8 changed files with 602 additions and 0 deletions
+12
View File
@@ -550,6 +550,18 @@ class UsersMenus {
} elseif ($status === 'verified') {
delete_user_meta($user_id, 'aksrt_user_verify_reason');
}
// Promote apply payload to public auth info when approved.
if ($status === 'verified') {
$apply = get_user_meta($user_id, 'aksrt_user_verify_apply', true);
if (is_array($apply)) {
update_user_meta($user_id, 'aksrt_user_verify_info', [
'name' => (string) ($apply['name'] ?? ''),
'desc' => (string) ($apply['desc'] ?? ''),
'type' => (string) ($apply['type'] ?? ''),
'time' => current_time('Y-m-d H:i'),
]);
}
}
$log = get_option('aksrt_verify_review_log', []);
$log = is_array($log) ? $log : [];
$log[] = [
+23
View File
@@ -437,6 +437,29 @@ trait ScriptsPart1b {
}
}
// 用户身份认证页(zibll user-auth.php)
if (is_page_template('templates/pages/page-user-auth.php')) {
$ua_css = get_template_directory() . '/assets/css/routes/user-auth.css';
if (file_exists($ua_css)) {
wp_enqueue_style(
'aksrt-route-user-auth',
$base_uri . '/assets/css/routes/user-auth.css',
['aksrt-tokens'],
(string) filemtime($ua_css)
);
}
$ua_js = get_template_directory() . '/assets/js/user-auth-page.js';
if (file_exists($ua_js)) {
wp_enqueue_script(
'aksrt-user-auth-page',
$base_uri . '/assets/js/user-auth-page.js',
[],
(string) filemtime($ua_js),
true
);
}
}
// 交易中心共享壳层(购物车 / 订单 / 收益)
$is_commerce_center = is_page_template('templates/pages/page-orders.php')
|| is_page_template('templates/pages/page-cart.php')
+1
View File
@@ -47,6 +47,7 @@ class Templates {
'templates/pages/page-posts-navs.php' => __('AKSRT - 文章导航', 'aksrt'),
'templates/pages/page-friends.php' => __('AKSRT - 友情链接', 'aksrt'),
'templates/pages/page-links.php' => __('AKSRT - 网址导航', 'aksrt'),
'templates/pages/page-user-auth.php' => __('AKSRT - 用户身份认证', 'aksrt'),
'templates/pages/page-orders.php' => __('AKSRT - 订单中心', 'aksrt'),
'templates/pages/page-revenue.php' => __('AKSRT - 收益中心', 'aksrt'),
'templates/pages/page-cart.php' => __('AKSRT - 购物车', 'aksrt'),
+207
View File
@@ -0,0 +1,207 @@
<?php
/**
* Frontend identity verification apply (zibll user-auth page parity on aksrt_user_verify_* meta).
*
* @package AKSRT
*/
namespace AKSRT\User;
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';
private static $instance = null;
public static function get_instance(): self {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function init(): void {
add_action('wp_ajax_aksrt_user_auth_apply', [$this, 'ajax_apply']);
}
/**
* @return array{
* enabled:bool,allow_apply:bool,public_display:bool,badge_text:string,
* limit_level:int,desc:string,disable_desc:string,
* types:array<int,array{slug:string,label:string}>
* }
*/
public static function settings(): array {
$all = class_exists('\AKSRT\Settings\Settings')
? (array) \AKSRT\Settings\Settings::get_all()
: [];
$enabled = !isset($all['user_verify_enabled']) || !empty($all['user_verify_enabled']);
$types_raw = (string) ($all['user_verify_apply_option'] ?? "real_name|实名认证\norganization|企业认证");
$types = [];
foreach (preg_split('/\r\n|\r|\n/', $types_raw) as $line) {
$line = trim($line);
if ($line === '') {
continue;
}
$parts = array_map('trim', explode('|', $line, 2));
$slug = sanitize_key($parts[0] ?? '');
$label = $parts[1] ?? $parts[0];
if ($slug === '') {
continue;
}
$types[] = ['slug' => $slug, 'label' => (string) $label];
}
return [
'enabled' => $enabled,
'allow_apply' => !isset($all['user_verify_allow_apply']) || !empty($all['user_verify_allow_apply']),
'public_display' => !isset($all['user_verify_public_display']) || !empty($all['user_verify_public_display']),
'badge_text' => (string) ($all['user_verify_badge_text'] ?? __('已认证', 'aksrt')),
'limit_level' => max(0, (int) ($all['user_verify_limit_level'] ?? 0)),
'desc' => (string) ($all['user_verify_desc'] ?? ''),
'disable_desc' => (string) ($all['user_verify_disable_desc'] ?? ''),
'types' => $types,
];
}
public static function get_status(int $user_id): string {
if ($user_id <= 0) {
return '';
}
return sanitize_key((string) get_user_meta($user_id, self::STATUS_META, true));
}
public static function is_verified(int $user_id): bool {
return self::get_status($user_id) === 'verified';
}
/**
* @return array<string,mixed>
*/
public static function get_auth_info(int $user_id): array {
$info = get_user_meta($user_id, self::INFO_META, true);
return is_array($info) ? $info : [];
}
/**
* @return array<string,mixed>
*/
public static function get_apply_payload(int $user_id): array {
$apply = get_user_meta($user_id, self::APPLY_META, true);
return is_array($apply) ? $apply : [];
}
public function ajax_apply(): void {
if (!is_user_logged_in()) {
wp_send_json(['error' => 1, 'msg' => __('请先登录', 'aksrt')]);
}
if (!check_ajax_referer('aksrt_user_auth_apply', '_wpnonce', false)) {
wp_send_json(['error' => 1, 'msg' => __('安全校验失败', 'aksrt')]);
}
$user_id = get_current_user_id();
$s = self::settings();
if (!$s['enabled'] || !$s['allow_apply']) {
wp_send_json(['error' => 1, 'msg' => __('暂不能申请身份认证', 'aksrt')]);
}
if (self::is_verified($user_id)) {
wp_send_json(['error' => 1, 'msg' => __('您已是认证用户', 'aksrt')]);
}
if (self::get_status($user_id) === 'pending') {
wp_send_json(['error' => 1, 'msg' => __('您的申请正在审核中', 'aksrt')]);
}
if ($s['limit_level'] > 0 && class_exists('\AKSRT\User\Level')) {
$lv = (int) Level::get_level($user_id);
if ($lv < $s['limit_level']) {
wp_send_json([
'error' => 1,
'msg' => sprintf(
/* translators: %d: required level */
__('等级不足,需达到 LV.%d 后申请', 'aksrt'),
$s['limit_level']
),
]);
}
}
$name = isset($_POST['name']) ? sanitize_text_field(wp_unslash((string) $_POST['name'])) : '';
$desc = isset($_POST['desc']) ? sanitize_textarea_field(wp_unslash((string) $_POST['desc'])) : '';
$msg = isset($_POST['msg']) ? sanitize_textarea_field(wp_unslash((string) $_POST['msg'])) : '';
$type = isset($_POST['type']) ? sanitize_key((string) wp_unslash($_POST['type'])) : '';
if ($name === '') {
wp_send_json(['error' => 1, 'msg' => __('请填写认证身份名称', 'aksrt')]);
}
$images = [];
if (!empty($_FILES['image']['name'])) {
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/media.php';
require_once ABSPATH . 'wp-admin/includes/image.php';
$files = $_FILES['image'];
// Normalize multi-file upload.
$names = $files['name'];
if (!is_array($names)) {
$names = [$names];
$tmp = [
'name' => $names,
'type' => [$files['type']],
'tmp_name' => [$files['tmp_name']],
'error' => [$files['error']],
'size' => [$files['size']],
];
$files = $tmp;
}
$count = min(3, count($files['name']));
for ($i = 0; $i < $count; $i++) {
if (empty($files['name'][$i]) || (int) $files['error'][$i] !== UPLOAD_ERR_OK) {
continue;
}
$file = [
'name' => $files['name'][$i],
'type' => $files['type'][$i],
'tmp_name' => $files['tmp_name'][$i],
'error' => $files['error'][$i],
'size' => $files['size'][$i],
];
$_FILES['aksrt_auth_img'] = $file;
$aid = media_handle_upload('aksrt_auth_img', 0);
if (!is_wp_error($aid)) {
$url = wp_get_attachment_url((int) $aid);
if ($url) {
$images[] = $url;
}
}
}
unset($_FILES['aksrt_auth_img']);
}
$payload = [
'name' => $name,
'desc' => $desc,
'msg' => $msg,
'type' => $type,
'img' => $images,
'time' => current_time('Y-m-d H:i'),
];
update_user_meta($user_id, self::APPLY_META, $payload);
update_user_meta($user_id, self::STATUS_META, 'pending');
delete_user_meta($user_id, self::REASON_META);
/**
* @param int $user_id
* @param array $payload
*/
do_action('aksrt_user_auth_apply_submitted', $user_id, $payload);
wp_send_json([
'error' => 0,
'msg' => __('提交成功,请等待管理员审核', 'aksrt'),
'reload' => true,
]);
}
}