Files
wordpress-theme-aksrt/tests/widget-system/invite-admin-harness.php
T
kate522andClaude Fable 5 a6776ffe0a feat(admin): upgrade invite code management with dialogs
Replace standalone add and export views with accessible dialogs, refine list interactions, and remove obsolete paths and unused counts. Fix filtered totals and reject oversized imports without truncation, with regression coverage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-30 18:53:01 +08:00

184 lines
8.7 KiB
PHP

<?php
/** WordPress-free render harness for the native invite admin page. */
namespace AKSRT\Core {
final class DBMigrator {
public static function table($key) { return 'wp_aksrt_' . $key; }
}
}
namespace {
define('ABSPATH', __DIR__);
define('ARRAY_A', 'ARRAY_A');
$root = dirname(__DIR__, 2);
define('AKSRT_DIR', $root);
define('AKSRT_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_aksrt_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_aksrt_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 \AKSRT\Admin\Native\UsersMenusInviteCode;
}
$_GET = [
'page' => 'aksrt_invite_codes',
'status' => 'used',
's' => 'search <script>alert(2)</script> & phrase',
'remark' => 'campaign & east',
'orderby' => 'code',
'desc' => 'ASC',
'aksrt_inv_title' => '<script>alert(1)</script>Completed',
'aksrt_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_aksrt_messages');
$wrong_hook_assets = $GLOBALS['assets'];
$probe->enqueue_invite_assets('users_page_aksrt_invite_codes');
$page_assets = $GLOBALS['assets'];
$service = \AKSRT\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);
}