Files
kate522andClaude Opus 5 35bb96e82f refactor: rename the theme brand and every identifier from AKSRT to Jio
Full-project rename. `git grep -i aksrt` is now empty apart from the
DBMigrator 3.3.0 changelog note that documents the rename itself.

Code identifiers
- PHP namespace AKSRT\ -> Jio\ (composer PSR-4 + 378 declarations)
- global constants AKSRT_* -> JIO_* (VERSION/DIR/URI, PAY_TYPE_*, CAPTCHA_*)
- procedural functions, hooks, options, meta keys, table names,
  cookies, query vars, ajax actions and nonces: aksrt_ -> jio_
- text domain aksrt -> jio; Theme Name -> Jio
- JS globals: aksrtData/aksrtAdminData/... -> jioData/jioAdminData/...,
  window.AKSRTCaptcha & friends -> window.JioCaptcha, Aksrt* -> Jio*
- Vue components Aks*.vue -> Jio*.vue (15 files renamed + imports)
- CSS classes/data-attrs/custom properties: aksrt- -> jio-
- REST namespace aksrt/v1 -> jio/v1 (incl. payment gateway callbacks)

Storage layer
- DB tables {prefix}aksrt_* -> {prefix}jio_*; SCHEMA_VERSION 3.2.0 -> 3.3.0
  so dbDelta creates the new names on install
- widget id_base aksrt_widget_* -> jio_widget_*
- uninstall.php drop list rebuilt from the actual migrator tables
  (dropped 3 stale entries, added the 17 missing ones)

Removed
- hardcoded https://api.aksrt.com/version.json default update endpoint;
  get_endpoint() now reads the 「更新端点」 setting and do_check() no-ops
  when it is empty

Files renamed: inc/helpers/aksrt-*.php -> jio-*.php (54),
assets/admin/src/components/form/Aks*.vue -> Jio*.vue (15).
Build artifacts regenerated: assets/css/build, assets/js/runtime,
assets/admin/build.

Verified: audit-psr4-paths errors=0, audit-admin-contracts errors=0
(warnings=56, unchanged baseline), 813/813 widget-system tests,
44/44 phpunit, php -l clean across all PHP.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-01 20:05:04 +08:00

519 lines
22 KiB
PHP

<?php
/** WordPress-free harness for the native identity admin render and POST handlers. */
namespace Jio\Core {
final class DBMigrator {
public static function table($key) { return 'wp_jio_' . $key; }
}
}
namespace Jio\User {
final class AuthApply {
public static $next_result = null;
public static function review($user_id, $application_uuid, $status, $note, $reviewer_id, $profile_fields) {
$GLOBALS['review_calls'][] = compact('user_id', 'application_uuid', 'status', 'note', 'reviewer_id', 'profile_fields');
return self::$next_result;
}
}
}
namespace {
define('ABSPATH', __DIR__);
define('ARRAY_A', 'ARRAY_A');
$root = dirname(__DIR__, 2);
define('JIO_DIR', $root);
define('JIO_URI', 'https://example.test/theme');
class WP_Error {
public function __construct(public $code, public $message) {}
public function get_error_message() { return $this->message; }
}
class HarnessWpDie extends \RuntimeException {}
class HarnessRedirect extends \RuntimeException {}
class WP_User {
public function __construct(public $ID, public $display_name) {}
}
function __($text, $domain = '') { return (string) $text; }
function esc_html($text) { return htmlspecialchars((string) $text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
function esc_attr($text) { return esc_html($text); }
function esc_textarea($text) { return esc_html($text); }
function esc_html__($text, $domain = '') { return esc_html($text); }
function esc_attr__($text, $domain = '') { return esc_attr($text); }
function esc_url($url) {
$url = (string) $url;
return preg_match('/^\s*javascript:/i', $url) ? '' : esc_attr($url);
}
function admin_url($path = '') { return 'https://example.test/wp-admin/' . ltrim((string) $path, '/'); }
function get_edit_user_link($id) { return admin_url('user-edit.php?user_id=' . abs((int) $id)); }
function get_avatar_url($id, $args = []) { return 'https://example.test/avatar.png?user=' . abs((int) $id); }
function get_userdata($id) {
$id = (int) $id;
return isset($GLOBALS['users'][$id]) ? new WP_User($id, $GLOBALS['users'][$id]) : false;
}
function current_user_can($capability, ...$args) { return !empty($GLOBALS['caps'][$capability]); }
function get_current_user_id() { return $GLOBALS['current_user_id'] ?? 7; }
function is_user_logged_in() { return get_current_user_id() > 0; }
function get_attached_file($id) { return false; }
function get_post_mime_type($id) { return 'image/png'; }
function delete_user_meta($user_id, $key) {
$GLOBALS['delete_user_meta_calls'][] = [(int) $user_id, (string) $key];
unset($GLOBALS['user_meta'][(int) $user_id][(string) $key]);
return true;
}
function update_user_meta($user_id, $key, $value) {
$GLOBALS['user_meta'][(int) $user_id][(string) $key] = $value;
return true;
}
function wp_die($message = '') { throw new HarnessWpDie((string) $message); }
function wp_unslash($value) { return $value; }
function absint($value) { return abs((int) $value); }
function sanitize_key($text) { return preg_replace('/[^a-z0-9_\-]/', '', strtolower((string) $text)) ?? ''; }
function sanitize_text_field($text) {
$text = strip_tags((string) $text);
$text = preg_replace('/[\r\n\t ]+/', ' ', $text) ?? '';
return trim($text);
}
function sanitize_textarea_field($text) {
$text = strip_tags((string) $text);
$text = preg_replace('/[\r\t ]+/', ' ', $text) ?? '';
$text = preg_replace('/\n{3,}/', "\n\n", $text) ?? '';
return trim($text);
}
function is_wp_error($value) { return $value instanceof WP_Error; }
function number_format_i18n($number) { return number_format((float) $number); }
function get_date_from_gmt($date, $format = 'Y-m-d H:i:s') {
if ((string) $date === '') return '';
$utc = new DateTimeImmutable((string) $date, new DateTimeZone('UTC'));
return $utc->setTimezone(new DateTimeZone('Asia/Shanghai'))->format($format);
}
function wp_create_nonce($action) { return 'nonce:' . (string) $action; }
function wp_nonce_field($action, $name = '_wpnonce', $referer = true, $display = true) {
$html = '<input type="hidden" name="' . esc_attr($name) . '" value="' . esc_attr(wp_create_nonce($action)) . '">';
if ($display) echo $html;
return $html;
}
function check_admin_referer($action, $query_arg = '_wpnonce') {
$GLOBALS['checked_nonces'][] = [$action, $query_arg];
$provided = (string) ($_POST[$query_arg] ?? '');
if (!hash_equals(wp_create_nonce($action), $provided)) {
throw new HarnessWpDie('nonce verification failed');
}
return 1;
}
function add_query_arg($key, $value = '', $url = '') {
if (is_array($key)) {
$args = $key;
$url = (string) $value;
} else {
$args = [$key => $value];
if ($url === '') $url = (string) ($_SERVER['REQUEST_URI'] ?? '');
}
$parts = parse_url((string) $url) ?: [];
parse_str($parts['query'] ?? '', $query);
foreach ($args as $name => $item) $query[$name] = $item;
$base = '';
if (isset($parts['scheme'])) $base .= $parts['scheme'] . '://';
if (isset($parts['host'])) $base .= $parts['host'];
if (isset($parts['port'])) $base .= ':' . $parts['port'];
$base .= $parts['path'] ?? '';
return $base . ($query !== [] ? '?' . http_build_query($query) : '') . (isset($parts['fragment']) ? '#' . $parts['fragment'] : '');
}
function cache_users($ids) { $GLOBALS['cache_calls'][] = array_values($ids); }
function update_meta_cache($type, $ids) { $GLOBALS['meta_cache_calls'][] = [$type, array_values($ids)]; }
function get_post_meta($id, $key, $single = false) { return $GLOBALS['post_meta'][(int) $id][$key] ?? ''; }
function get_post_type($id) { return in_array((int) $id, $GLOBALS['attachment_ids'], true) ? 'attachment' : false; }
function wp_delete_attachment($id, $force = false) {
$GLOBALS['deleted_attachments'][] = [(int) $id, (bool) $force];
return true;
}
function wp_enqueue_style(...$args) { $GLOBALS['assets'][] = ['style', ...$args]; }
function wp_enqueue_script(...$args) { $GLOBALS['assets'][] = ['script', ...$args]; }
function esc_html_e($text, $domain = '') { echo esc_html($text); }
final class FakeWpdb {
public $users = 'wp_users';
public $prepared = [];
public $executed = [];
public $delete_calls = [];
public $total = 45;
public $application_delete_result = 1;
public $event_delete_result = 1;
public $applications = [];
public function prepare($sql, ...$params) {
$params = count($params) === 1 && is_array($params[0]) ? $params[0] : $params;
$this->prepared[] = ['sql' => (string) $sql, 'params' => $params];
return (string) $sql;
}
public function esc_like($text) { return addcslashes((string) $text, '_%\\'); }
public function get_var($sql) {
$this->executed[] = (string) $sql;
return str_contains((string) $sql, 'SELECT COUNT(*)') ? $this->total : null;
}
public function get_results($sql, $format = null) {
$this->executed[] = (string) $sql;
if (str_contains((string) $sql, 'GROUP BY a.status')) {
return [
['status' => 'manual_pending', 'total' => 2],
['status' => 'verified', 'total' => 1],
['status' => 'rejected', 'total' => 1],
];
}
if (str_contains((string) $sql, 'LIMIT %d OFFSET %d')) return $GLOBALS['render_rows'];
return [];
}
public function get_row($sql, $format = null) {
$this->executed[] = (string) $sql;
$query = $this->prepared[count($this->prepared) - 1] ?? ['params' => []];
$requested_uuid = (string) ($query['params'][0] ?? '');
foreach ($this->applications as $application) {
if (
str_contains((string) $sql, 'application_uuid = %s')
&& str_contains((string) $sql, 'identity_application')
&& (string) ($application['application_uuid'] ?? '') === $requested_uuid
) {
return $application;
}
}
return null;
}
public function delete($table, $data, $format = null) {
$this->delete_calls[] = ['table' => $table, 'data' => $data, 'format' => $format];
if (str_contains((string) $table, 'identity_application')) return $this->application_delete_result;
if (str_contains((string) $table, 'identity_event')) return $this->event_delete_result;
return 1;
}
}
require $root . '/src/Admin/Native/NativeUi.php';
require $root . '/src/Admin/Native/AdminListPagination.php';
require $root . '/src/Identity/ApplicationRepository.php';
require $root . '/src/Identity/EvidenceService.php';
require $root . '/src/Identity/EventRepository.php';
require $root . '/src/Admin/Native/UsersMenusVerifyDialogs.php';
require $root . '/src/Admin/Native/UsersMenusVerify.php';
final class VerifyAdminProbe {
use \Jio\Admin\Native\UsersMenusVerify;
public const CAPABILITY = 'manage_options';
public $cap_checks = 0;
private function ensure_cap(): void {
$this->cap_checks++;
if (!current_user_can('manage_options')) wp_die('capability denied');
}
private function flash(): void {}
private function redirect(string $page, string $message, array $extra = []): void {
$url = add_query_arg(array_merge(['page' => $page, 'jio_users_msg' => $message], $extra), admin_url('users.php'));
throw new HarnessRedirect($url);
}
public function invoke(string $method, ...$args) { return $this->$method(...$args); }
public function page(): string {
ob_start();
$this->render_verify_page();
return (string) ob_get_clean();
}
public function html(string $method, ...$args): string {
ob_start();
$this->$method(...$args);
return (string) ob_get_clean();
}
}
$GLOBALS['users'] = [
41 => 'Applicant <script>alert(1)</script>',
42 => 'Approved User',
9 => 'Reviewer <img src=x onerror=alert(2)>',
];
$GLOBALS['caps'] = ['manage_options' => true, 'jio_review_identity' => true];
$GLOBALS['post_meta'] = [
1001 => [
'_jio_verify_evidence_owner' => 41,
'_jio_identity_application_uuid' => 'uuid-pending-301',
],
1002 => [
'_jio_verify_evidence_owner' => 42,
'_jio_identity_application_uuid' => 'uuid-approved-302',
],
];
$GLOBALS['attachment_ids'] = [1001, 1002];
$GLOBALS['deleted_attachments'] = [];
$GLOBALS['cache_calls'] = [];
$GLOBALS['meta_cache_calls'] = [];
$GLOBALS['checked_nonces'] = [];
$GLOBALS['review_calls'] = [];
$GLOBALS['assets'] = [];
$GLOBALS['delete_user_meta_calls'] = [];
$GLOBALS['user_meta'] = [41 => ['auth' => 1, 'auth_info' => ['name' => 'Already verified', 'desc' => 'kept']]];
$GLOBALS['render_rows'] = [
[
'id' => 301,
'application_uuid' => 'uuid-pending-301',
'user_id' => 41,
'status' => 'manual_pending',
'public_name' => 'Applicant name <script>alert(3)</script>',
'description' => 'Bio <img src=x onerror=alert(4)> & text',
'manual_reason' => 'Reason <svg onload=alert(5)>',
'review_note' => '',
'reviewer_id' => 0,
'evidence_ids' => '[1001]',
'created_at' => '2026-10-30 03:30:00',
'completed_at' => null,
'display_name' => 'Applicant <script>alert(1)</script>',
'user_login' => 'applicant"<script>login</script>',
'user_email' => 'applicant"<img src=x onerror=alert(6)>@example.test',
],
[
'id' => 302,
'application_uuid' => 'uuid-approved-302',
'user_id' => 42,
'status' => 'verified',
'public_name' => 'Approved name',
'description' => 'Approved bio',
'manual_reason' => 'Applied earlier',
'review_note' => 'Reviewed safely',
'reviewer_id' => 9,
'evidence_ids' => '[1002,1001]',
'created_at' => '2026-10-29 02:10:00',
'completed_at' => '2026-10-30 04:45:00',
'display_name' => 'Approved User',
'user_login' => 'approved_user',
'user_email' => 'approved@example.test',
],
];
$wpdb = new FakeWpdb();
$probe = new VerifyAdminProbe();
$_GET = [
'page' => 'jio_user_auth',
'keyword' => 'search <script>alert(1)</script> &_%',
'paged' => '999',
];
$html = $probe->page();
$page_prepared = $wpdb->prepared;
$page_executed = $wpdb->executed;
$page_cache_calls = $GLOBALS['cache_calls'];
$page_meta_cache_calls = $GLOBALS['meta_cache_calls'];
$pending_empty = $probe->html('render_verify_table', [], ['status' => 'pending', 'keyword' => ''], 1);
$search_empty = $probe->html('render_verify_table', [], ['status' => 'approved', 'keyword' => 'no match'], 1);
$GLOBALS['assets'] = [];
$probe->enqueue_verify_assets('users_page_jio_messages');
$wrong_hook_assets = $GLOBALS['assets'];
$probe->enqueue_verify_assets('users_page_jio_user_auth');
$auth_page_assets = $GLOBALS['assets'];
$evidence_service = new \Jio\Identity\EvidenceService();
$evidence_application = ['user_id' => 41, 'application_uuid' => 'uuid-pending-301', 'evidence_ids' => [1001]];
$evidence_permissions = [];
foreach ([
'manager' => [['manage_options' => true], 7],
'reviewer' => [['jio_review_identity' => true], 7],
'outsider' => [[], 7],
'owner' => [[], 41],
] as $role => [$caps, $current_user]) {
$GLOBALS['caps'] = $caps;
$GLOBALS['current_user_id'] = $current_user;
$evidence_permissions[$role] = [
'admin_items' => count($evidence_service->items($evidence_application, true)),
'owner_items' => count($evidence_service->items($evidence_application)),
];
$_GET = ['attachment' => 1001];
$_POST = ['_wpnonce' => wp_create_nonce('jio_verify_evidence_1001')];
try {
$evidence_service->serve();
} catch (HarnessWpDie $error) {
$evidence_permissions[$role]['serve_result'] = $error->getMessage();
}
}
$GLOBALS['current_user_id'] = 7;
$GLOBALS['caps'] = ['manage_options' => true, 'jio_review_identity' => true];
$GLOBALS['checked_nonces'] = [];
$wpdb->prepared = [];
$wpdb->executed = [];
$wpdb->applications = [[
'id' => 401,
'application_uuid' => 'review-uuid-401',
'user_id' => 41,
'type' => 'general',
'channel' => 'manual',
'status' => 'manual_pending',
'public_name' => 'Before review',
'provider_result_summary' => 'Before bio',
'manual_reason' => 'Manual reason',
'review_note' => '',
'reviewer_id' => 0,
'evidence_ids' => '[1001]',
'created_at' => '2026-10-30 03:30:00',
'completed_at' => null,
'updated_at' => '2026-10-30 03:30:00',
]];
$_POST = [
'application_uuid' => 'review-uuid-401',
'user_id' => '41',
'status' => 'REJECTED!',
'note' => 'Rejected <script>reason</script> & explain',
'name' => 'Ignored <img src=x onerror=alert(1)>',
'description' => 'Ignored <b>description</b>',
'filter_status' => 'approved',
'keyword' => 'review <script>search</script> & phrase',
'paged' => '4',
'_wpnonce' => wp_create_nonce('jio_users_review_verify_review-uuid-401'),
];
\Jio\User\AuthApply::$next_result = ['status' => 'rejected'];
try {
$probe->handle_review_verify();
} catch (HarnessRedirect $redirect) {
parse_str((string) parse_url($redirect->getMessage(), PHP_URL_QUERY), $query);
$review_redirect = $query;
}
$review_call = $GLOBALS['review_calls'][0] ?? null;
$review_nonce_calls = $GLOBALS['checked_nonces'];
$GLOBALS['review_calls'] = [];
$wpdb->prepared = [];
$_POST = [
'application_uuid' => 'review-uuid-401',
'user_id' => '41',
'status' => 'verified',
'note' => '',
'name' => 'Safe name',
'description' => 'Safe description',
'filter_status' => 'rejected',
'keyword' => 'keep this',
'paged' => '2',
'_wpnonce' => wp_create_nonce('jio_users_review_verify_review-uuid-401'),
];
\Jio\User\AuthApply::$next_result = new WP_Error('review_failed', '<script>review unavailable</script>');
try {
$probe->handle_review_verify();
} catch (HarnessRedirect $redirect) {
parse_str((string) parse_url($redirect->getMessage(), PHP_URL_QUERY), $query);
$review_error_redirect = $query;
}
$review_error_call = $GLOBALS['review_calls'][0] ?? null;
$GLOBALS['caps'] = ['manage_options' => false, 'jio_review_identity' => false];
$permission_denied = false;
try {
$probe->handle_review_verify();
} catch (HarnessWpDie $error) {
$permission_denied = $error->getMessage() === '权限不足';
}
$GLOBALS['caps'] = ['manage_options' => true, 'jio_review_identity' => true];
$_POST = [
'application_uuid' => 'review-uuid-401',
'_wpnonce' => wp_create_nonce('jio_users_review_verify-wrong-uuid'),
];
$bad_nonce_rejected = false;
try {
$probe->handle_review_verify();
} catch (HarnessWpDie $error) {
$bad_nonce_rejected = $error->getMessage() === 'nonce verification failed';
}
$delete_application = [
'id' => 501,
'application_uuid' => 'delete-uuid-501',
'user_id' => 41,
'type' => 'general',
'channel' => 'manual',
'status' => 'verified',
'public_name' => 'Existing identity',
'provider_result_summary' => 'Existing description',
'manual_reason' => 'Manual reason',
'review_note' => 'Approved',
'reviewer_id' => 9,
'evidence_ids' => '[1001,1002]',
'created_at' => '2026-10-29 02:10:00',
'completed_at' => '2026-10-30 04:45:00',
'updated_at' => '2026-10-30 04:45:00',
];
$wpdb->applications = [$delete_application];
$wpdb->prepared = [];
$wpdb->executed = [];
$wpdb->delete_calls = [];
$wpdb->application_delete_result = 1;
$GLOBALS['deleted_attachments'] = [];
$GLOBALS['user_meta'][41] = ['auth' => 1, 'auth_info' => ['name' => 'Already verified', 'desc' => 'kept']];
$GLOBALS['delete_user_meta_calls'] = [];
$_POST = [
'application_uuid' => 'delete-uuid-501',
'filter_status' => 'rejected',
'keyword' => 'delete search',
'paged' => '6',
'_wpnonce' => wp_create_nonce('jio_users_delete_verify_delete-uuid-501'),
];
try {
$probe->handle_delete_verify();
} catch (HarnessRedirect $redirect) {
parse_str((string) parse_url($redirect->getMessage(), PHP_URL_QUERY), $query);
$delete_success_redirect = $query;
}
$delete_success_calls = $wpdb->delete_calls;
$delete_success_attachments = $GLOBALS['deleted_attachments'];
$delete_auth_projection = $GLOBALS['user_meta'][41];
$wpdb->delete_calls = [];
$wpdb->event_delete_result = false;
try {
$probe->handle_delete_verify();
} catch (HarnessRedirect $redirect) {
parse_str((string) parse_url($redirect->getMessage(), PHP_URL_QUERY), $query);
$delete_event_failure_redirect = $query;
}
$delete_event_failure_calls = $wpdb->delete_calls;
$wpdb->event_delete_result = 1;
$wpdb->prepared = [];
$wpdb->executed = [];
$wpdb->delete_calls = [];
$wpdb->application_delete_result = 0;
$GLOBALS['deleted_attachments'] = [];
$_POST = [
'application_uuid' => 'delete-uuid-501',
'filter_status' => 'pending',
'keyword' => 'delete failure',
'paged' => '1',
'_wpnonce' => wp_create_nonce('jio_users_delete_verify_delete-uuid-501'),
];
try {
$probe->handle_delete_verify();
} catch (HarnessRedirect $redirect) {
parse_str((string) parse_url($redirect->getMessage(), PHP_URL_QUERY), $query);
$delete_failure_redirect = $query;
}
echo json_encode([
'html' => $html,
'pending_empty' => $pending_empty,
'search_empty' => $search_empty,
'page_prepared' => $page_prepared,
'page_executed' => $page_executed,
'cache_calls' => $page_cache_calls,
'meta_cache_calls' => $page_meta_cache_calls,
'wrong_hook_assets' => $wrong_hook_assets,
'auth_page_assets' => $auth_page_assets,
'evidence_permissions' => $evidence_permissions,
'delete_event_failure_redirect' => $delete_event_failure_redirect ?? [],
'delete_event_failure_calls' => $delete_event_failure_calls,
'review_call' => $review_call,
'review_redirect' => $review_redirect ?? [],
'review_nonce_calls' => $review_nonce_calls,
'review_error_redirect' => $review_error_redirect ?? [],
'review_error_call' => $review_error_call,
'permission_denied' => $permission_denied,
'bad_nonce_rejected' => $bad_nonce_rejected,
'delete_success_redirect' => $delete_success_redirect ?? [],
'delete_success_calls' => $delete_success_calls,
'delete_success_attachments' => $delete_success_attachments,
'delete_auth_projection' => $delete_auth_projection,
'delete_user_meta_calls' => $GLOBALS['delete_user_meta_calls'],
'delete_failure_redirect' => $delete_failure_redirect ?? [],
'delete_failure_calls' => $wpdb->delete_calls,
'delete_failure_attachments' => $GLOBALS['deleted_attachments'],
], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
}