Files
wordpress-theme-aksrt/tests/widget-system/invite-admin-harness.php
T
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

184 lines
8.7 KiB
PHP

<?php
/** WordPress-free render harness for the native invite admin page. */
namespace Jio\Core {
final class DBMigrator {
public static function table($key) { return 'wp_jio_' . $key; }
}
}
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 $code;
public $message;
public function __construct($code, $message) { $this->code = $code; $this->message = $message; }
public function get_error_message() { return $this->message; }
}
function is_wp_error($value) { return $value instanceof WP_Error; }
function wp_json_encode($value, $options = 0) { return json_encode($value, $options); }
function get_current_user_id() { return 7; }
function wp_generate_password($length, $special_chars = true, $extra_special_chars = false) { return str_repeat('A', (int) $length); }
function __($text, $domain = '') { return $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_html__($text, $domain = '') { return esc_html($text); }
function esc_attr__($text, $domain = '') { return esc_attr($text); }
function esc_url($url) { return preg_match('/^\s*javascript:/i', (string) $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=' . (int) $id); }
function current_user_can($capability) { return $capability === 'edit_users'; }
function wp_die($message = '') { throw new RuntimeException((string) $message); }
function wp_create_nonce($action) { return 'nonce"<script>&'; }
function check_admin_referer($action, $query_arg = '_wpnonce') { $GLOBALS['checked_nonces'][] = [$action, $query_arg]; }
function sanitize_key($text) { return preg_replace('/[^a-z0-9_\-]/', '', strtolower((string) $text)); }
function sanitize_text_field($text) { return trim(strip_tags((string) $text)); }
function wp_unslash($text) { return $text; }
function current_time($format) { return $format === 'mysql' ? '2026-10-30 12:00:00' : '20261030120000'; }
function number_format_i18n($number) { return number_format((float) $number); }
function checked($actual, $expected, $echo = true) { return (string) $actual === (string) $expected ? ' checked="checked"' : ''; }
function add_query_arg($key, $value = '', $url = '') {
if (is_array($key)) {
$args = $key;
$url = (string) $value;
} else {
$args = [$key => $value];
if ($url === '') {
$url = $_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 wp_kses($html, $allowed_html) {
return preg_replace_callback('/<\/?([a-z][a-z0-9]*)\b[^>]*>/i', static function ($match) {
return strtolower($match[1]) === 'br' ? '<br>' : '';
}, (string) $html);
}
function paginate_links($args) { return '<span class="pagination-probe">' . esc_html((string) $args['current']) . '</span>'; }
function wp_enqueue_style(...$args) { $GLOBALS['assets'][] = ['style', ...$args]; }
function wp_enqueue_script(...$args) { $GLOBALS['assets'][] = ['script', ...$args]; }
final class FakeWpdb {
public $users = 'wp_users';
public $prepared = [];
public $row_queries = [];
public $inserts = 0;
public function prepare($sql, ...$params) {
$params = count($params) === 1 && is_array($params[0]) ? $params[0] : $params;
$this->prepared[] = ['sql' => $sql, 'params' => $params];
return $sql;
}
public function get_var($sql) { return null; }
public function insert($table, $data, $formats = null) { $this->inserts++; return 1; }
public function get_row($sql, $format = null) {
$this->row_queries[] = $sql;
return ['all_count' => 2, 'unused_count' => 1, 'used_count' => 1];
}
public function get_results($sql, $format = null) {
if (str_contains($sql, 'FROM wp_jio_invite_code_usage u')) {
return [['code_id' => 41, 'user_id' => 7, 'used_at' => '2026-10-30 11:00:00', 'display_name' => 'Admin"><script>alert(1)</script>']];
}
if (str_contains($sql, 'SELECT * FROM wp_jio_invite_code')) {
return [
[
'id' => 41,
'code' => 'X&Y"<img src=x onerror=alert(1)>',
'batch_id' => 'B1',
'remark' => 'owner"><img src=x onerror=alert(1)>',
'rewards' => '{"points":5,"vip_level":1,"vip_days":7}',
'max_uses' => 3,
'used_count' => 1,
'status' => 'active',
'expires_at' => null,
'created_at' => '<script>date</script>',
'updated_at' => '2026-10-30 11:00:00',
],
[
'id' => 42,
'code' => 'CLEAN-CODE',
'batch_id' => 'B2',
'remark' => '',
'rewards' => '{"vip_level":2,"vip_days":5}',
'max_uses' => 1,
'used_count' => 0,
'status' => 'active',
'expires_at' => null,
'created_at' => '2026-10-29 10:00:00',
'updated_at' => '2026-10-29 10:00:00',
],
];
}
return [];
}
public function esc_like($text) { return addcslashes((string) $text, '_%\\'); }
}
$wpdb = new FakeWpdb();
require $root . '/src/Admin/Native/NativeUi.php';
require $root . '/src/Payment/InviteCode.php';
require $root . '/src/Admin/Native/UsersMenusInviteForms.php';
require $root . '/src/Admin/Native/UsersMenusInviteCode.php';
final class InviteAdminProbe {
use \Jio\Admin\Native\UsersMenusInviteCode;
}
$_GET = [
'page' => 'jio_invite_codes',
'status' => 'used',
's' => 'search <script>alert(2)</script> & phrase',
'remark' => 'campaign & east',
'orderby' => 'code',
'desc' => 'ASC',
'jio_inv_title' => '<script>alert(1)</script>Completed',
'jio_inv_msg' => '<img src=x onerror=alert(1)>notice',
];
$probe = new InviteAdminProbe();
ob_start();
$probe->render_invite_codes_page();
$page = ob_get_clean();
$GLOBALS['assets'] = [];
$probe->enqueue_invite_assets('users_page_jio_messages');
$wrong_hook_assets = $GLOBALS['assets'];
$probe->enqueue_invite_assets('users_page_jio_invite_codes');
$page_assets = $GLOBALS['assets'];
$service = \Jio\Payment\InviteCode::get_instance();
$guard = new \ReflectionMethod($service, 'guard_and_redirect');
$list_result = $service->list_codes(['status' => 'used']);
$import_lines = [];
for ($i = 1; $i <= 1000; $i++) {
$import_lines[] = sprintf('CODE%05d 无', $i);
}
$over_limit_result = $service->import_codes(['data' => implode("\n", array_merge($import_lines, ['OVERLIMIT 无']))]);
$blank_plus_limit_result = $service->import_codes(['data' => "\n" . implode("\n", $import_lines)]);
$return_url = $guard->invoke($service);
echo json_encode([
'html' => $page,
'wrong_hook_assets' => $wrong_hook_assets,
'page_assets' => $page_assets,
'return_url' => $return_url,
'list_result' => $list_result,
'row_queries' => $wpdb->row_queries,
'over_limit_is_error' => is_wp_error($over_limit_result),
'leading_blank_imported' => is_wp_error($blank_plus_limit_result) ? null : $blank_plus_limit_result['imported'],
'import_insert_count' => $wpdb->inserts,
'checked_nonces' => $GLOBALS['checked_nonces'],
], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
}