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>
168 lines
10 KiB
PHP
168 lines
10 KiB
PHP
<?php
|
|
/** WordPress-free message admin render / handler / SQL harness. No actual notifications are sent. */
|
|
namespace Jio\Core {
|
|
final class DBMigrator {
|
|
public static function table($key) { return 'wp_jio_' . $key; }
|
|
}
|
|
}
|
|
namespace Jio\Messages {
|
|
final class Message {
|
|
public static $sent = [];
|
|
public static function get_instance() { return new self(); }
|
|
public function send($id, $type, $content, $args) {
|
|
self::$sent[] = compact('id', 'type', 'content', 'args');
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
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 WP_User {
|
|
public $ID = 7;
|
|
public $display_name = 'Test <user>';
|
|
}
|
|
class Redirect extends \RuntimeException {}
|
|
function __($text, $domain = '') { return $text; }
|
|
function esc_html($text) { return htmlspecialchars((string) $text, ENT_QUOTES, '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('/^javascript:/i', $url) ? '' : esc_attr($url); }
|
|
function esc_url_raw($url) { return preg_match('/^javascript:/i', $url) ? '' : $url; }
|
|
function sanitize_key($text) { return preg_replace('/[^a-z0-9_\-]/', '', strtolower($text)); }
|
|
function sanitize_text_field($text) { return trim(strip_tags($text)); }
|
|
function sanitize_textarea_field($text) { return trim(strip_tags($text)); }
|
|
function sanitize_user($text) { return $text; }
|
|
function wp_unslash($text) { return $text; }
|
|
function absint($value) { return abs((int) $value); }
|
|
function is_wp_error($value) { return $value instanceof WP_Error; }
|
|
function is_email($value) { return filter_var($value, FILTER_VALIDATE_EMAIL); }
|
|
function admin_url($path) { return 'https://example.test/wp-admin/' . $path; }
|
|
function get_edit_user_link($id) { return admin_url('user-edit.php?user_id=' . $id); }
|
|
function get_avatar_url($id, $args) { return 'https://example.test/avatar.png'; }
|
|
function get_userdata($id) { return $id > 0 ? new WP_User() : false; }
|
|
function get_user_by($key, $value) { return new WP_User(); }
|
|
function cache_users($ids) { $GLOBALS['cache_calls'][] = $ids; }
|
|
function number_format_i18n($count) { return number_format($count); }
|
|
function human_time_diff($time) { return '1 分钟'; }
|
|
function checked($actual, $expected, $echo = true) { return $actual === $expected ? ' checked' : ''; }
|
|
function selected($actual, $expected, $echo = true) { return $actual === $expected ? ' selected' : ''; }
|
|
function wp_nonce_field($action) { echo '<input type="hidden" name="_wpnonce" value="' . esc_attr($action) . '" />'; }
|
|
function check_admin_referer($action) { $GLOBALS['nonce_checked'][] = $action; }
|
|
function wp_roles() {
|
|
return new class {
|
|
public $roles = ['subscriber' => [], 'editor' => [], 'author' => [], 'contributor' => []];
|
|
public function get_names() { return ['subscriber' => '订阅者', 'editor' => '编辑', 'author' => '作者', 'contributor' => '贡献者']; }
|
|
};
|
|
}
|
|
function translate_user_role($role) { return $role; }
|
|
function count_users($strategy) { return ['avail_roles' => ['subscriber' => 3, 'editor' => 501, 'author' => 0, 'contributor' => 500]]; }
|
|
function get_users($args) { return array_slice(range(1, $GLOBALS['role_count'] ?? 3), 0, $args['number']); }
|
|
function wp_safe_redirect($url) { throw new Redirect($url); }
|
|
function add_query_arg($args, $url) { return $url . '?' . http_build_query($args); }
|
|
function submit_button($text, $type, $name, $wrap) { echo '<button type="submit">' . esc_html($text) . '</button>'; }
|
|
function current_datetime() { return new \DateTimeImmutable($GLOBALS['now'] ?? '2026-10-30 12:00:00', new \DateTimeZone($GLOBALS['zone'] ?? 'Asia/Shanghai')); }
|
|
function get_gmt_from_date($local) { return (new \DateTimeImmutable($local, new \DateTimeZone($GLOBALS['zone'] ?? 'Asia/Shanghai')))->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d H:i:s'); }
|
|
function get_date_from_gmt($gmt, $format) { return (new \DateTimeImmutable($gmt, new \DateTimeZone('UTC')))->setTimezone(new \DateTimeZone('Asia/Shanghai'))->format($format); }
|
|
function current_time($format, $gmt = false) { return '2026-10-30 04:00:00'; }
|
|
class Database {
|
|
public $prepared = [];
|
|
public $queries = [];
|
|
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 2; }
|
|
public function get_results($sql, $format) { return []; }
|
|
public function get_row($sql, $format) { return ['total' => 2, 'unread' => 1, 'today' => 1, 'recipients' => 2]; }
|
|
public function get_col($sql) { return ['system', 'asset_balance_increase', 'asset_balance_decrease', 'asset_points_increase', 'asset_points_decrease']; }
|
|
public function query($sql) { $this->queries[] = $sql; return 1; }
|
|
public function esc_like($text) { return addcslashes($text, '_%\\'); }
|
|
}
|
|
$wpdb = new Database();
|
|
require $root . '/src/Messages/MessageTypes.php';
|
|
require $root . '/src/Messages/AdminBroadcast.php';
|
|
require $root . '/src/Messages/NotificationAdminQuery.php';
|
|
require $root . '/src/Admin/Native/NativeUi.php';
|
|
require $root . '/src/Admin/Native/UsersMenusMessagesList.php';
|
|
require $root . '/src/Admin/Native/UsersMenusMessagesCompose.php';
|
|
require $root . '/src/Admin/Native/UsersMenusMessages.php';
|
|
class Probe {
|
|
use \Jio\Admin\Native\UsersMenusMessages;
|
|
public $cap_checked = 0;
|
|
private function ensure_cap(): void { $this->cap_checked++; }
|
|
public function invoke($method, ...$args) { return $this->$method(...$args); }
|
|
public function html($method, ...$args) {
|
|
ob_start(); $this->$method(...$args); return ob_get_clean();
|
|
}
|
|
}
|
|
$probe = new Probe();
|
|
$out = [];
|
|
$filters = $probe->invoke('message_filters', ['period' => 'today', 'status' => 'unread', 's' => 'a%_']);
|
|
$out['filters'] = $filters;
|
|
$out['bad_period'] = $probe->invoke('message_filters', ['period' => 'evil']);
|
|
$out['query_filters'] = $probe->invoke('message_query_filters', $filters);
|
|
$out['compose'] = $probe->html('render_message_compose');
|
|
$out['dialogs'] = $probe->html('render_message_dialogs');
|
|
$out['overview'] = $probe->html('render_message_overview');
|
|
$out['filter_form'] = $probe->html('render_message_filter_form', $filters);
|
|
$out['views'] = $probe->invoke('message_views_html', $filters, ['all' => 2, 'unread' => 1, 'read' => 1]);
|
|
$row = ['id' => 8, 'user_id' => 7, 'type' => 'system', 'title' => '<script>alert(1)</script>', 'description' => str_repeat('完整内容', 100) . "\n<script>x</script>", 'target_url' => 'javascript:alert(1)', 'is_read' => 0, 'created_at' => '2026-10-30 03:00:00', 'read_at' => null];
|
|
$out['table'] = $probe->html('render_message_table', [$row, array_merge($row, ['id' => 9, 'is_read' => 1, 'read_at' => '2026-10-30 03:30:00'])], $filters, 3, false);
|
|
$out['empty'] = $probe->html('render_message_table', [], $filters, 1, false);
|
|
$out['cache_calls'] = $GLOBALS['cache_calls'];
|
|
$out['asset_table'] = $probe->html('render_message_table', [array_merge($row, ['type' => 'asset_balance_increase'])], $filters, 1, false);
|
|
foreach (['asset_balance_increase', 'asset_balance_decrease', 'asset_points_increase', 'asset_points_decrease'] as $type) {
|
|
$out['asset_types'][$type] = [\Jio\Messages\MessageTypes::label($type), \Jio\Messages\MessageTypes::tone($type)];
|
|
}
|
|
|
|
$wpdb->prepared = [];
|
|
\Jio\Messages\NotificationAdminQuery::paginate($out['query_filters'], 1);
|
|
\Jio\Messages\NotificationAdminQuery::status_counts($out['query_filters']);
|
|
\Jio\Messages\NotificationAdminQuery::overview();
|
|
$out['sql'] = $wpdb->prepared;
|
|
$GLOBALS['zone'] = 'America/New_York';
|
|
$GLOBALS['now'] = '2026-11-01 12:00:00';
|
|
$wpdb->prepared = [];
|
|
\Jio\Messages\NotificationAdminQuery::overview();
|
|
$out['dst_sql'] = $wpdb->prepared;
|
|
|
|
$base = ['recipient_mode' => 'role', 'role' => 'subscriber', 'content' => 'Hello', 'title' => 'Title'];
|
|
$cases = [
|
|
'unconfirmed' => $base,
|
|
'changed_count' => $base + ['confirmed_role' => 'subscriber', 'confirmed_count' => 2],
|
|
'changed_role' => $base + ['confirmed_role' => 'editor', 'confirmed_count' => 3],
|
|
'confirmed' => $base + ['confirmed_role' => 'subscriber', 'confirmed_count' => 3],
|
|
'too_large' => $base + ['confirmed_role' => 'subscriber', 'confirmed_count' => 501],
|
|
'users' => ['recipient_mode' => 'users', 'users' => '7', 'content' => 'Hello'],
|
|
'long_title' => array_merge($base, ['title' => str_repeat('字', 192)]),
|
|
];
|
|
foreach ($cases as $name => $post) {
|
|
$_POST = $post;
|
|
$GLOBALS['role_count'] = $name === 'too_large' ? 501 : 3;
|
|
\Jio\Messages\Message::$sent = [];
|
|
try { $probe->handle_send_message(); } catch (Redirect $redirect) {
|
|
parse_str(parse_url($redirect->getMessage(), PHP_URL_QUERY), $query);
|
|
$out['send'][$name] = ['redirect' => $query, 'sent' => count(\Jio\Messages\Message::$sent)];
|
|
}
|
|
}
|
|
$_POST = ['bulk_action' => 'delete', 'message_id' => 8, 'ids' => [9, 10], 'period' => 'today', 'status' => 'unread', 'paged' => 3];
|
|
$wpdb->prepared = [];
|
|
try { $probe->handle_bulk_messages(); } catch (Redirect $redirect) {
|
|
parse_str(parse_url($redirect->getMessage(), PHP_URL_QUERY), $query);
|
|
$out['single_delete'] = ['redirect' => $query, 'sql' => $wpdb->prepared];
|
|
}
|
|
$out['cap_checked'] = $probe->cap_checked;
|
|
$out['nonce_checked'] = $GLOBALS['nonce_checked'];
|
|
echo json_encode($out, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
|
|
}
|