feat(admin): replace ticket dialogs with in-page views

Ticket handling on users.php?page=aksrt_tickets no longer uses dialogs.
The list, the workspace (?id=N) and the assisted create form (?view=new)
are server-rendered page views that carry the list filters and page
for going back. Writes still go through the ticket REST routes; on
success the page reloads with a native notice, and unsent drafts in
other forms survive the reload via sessionStorage. Uncertain or
conflicting writes lock every form and only offer a reload.

Remove dead code: UsersMenusTicketsDialogs, the aksrt_ticket_view AJAX
read and its nonce, dialog CSS/JS (backdrop, scroll lock, focus return,
read races) and the dialog runtime test. Fix the create link carrying
an unclamped page number on out-of-range list pages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Fable 5 committed 2026-10-31 14:45:27 +08:00
1 parent 48e54dc115
commit d86a85e6a6
13 files changed
+720 -1264

No files matched your search

+20 -36
View File
@@ -3,8 +3,6 @@
namespace {
define('ABSPATH', __DIR__);
define('ARRAY_A', 'ARRAY_A');
$GLOBALS['allowed'] = true;
$GLOBALS['valid_nonce'] = true;
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); }
@@ -25,19 +23,12 @@ namespace {
function get_users($args) { return [(object) ['ID' => 7, 'display_name' => 'Operator'], (object) ['ID' => 8, 'display_name' => 'Second']]; }
function get_edit_user_link($id) { return admin_url('user-edit.php?user_id=' . $id); }
function aksrt_get_avatar_url($id, $size) { return 'https://example.test/avatar/' . $id; }
function current_user_can($cap) { return $GLOBALS['allowed']; }
function check_ajax_referer($action, $key, $die) { return $action === 'aksrt_ticket_view' && $key === 'nonce' && $GLOBALS['valid_nonce']; }
function add_query_arg($args, $url, $base = null) {
if (!is_array($args)) { $args = [$args => $url]; $url = $base; }
$parts = parse_url($url);
parse_str($parts['query'] ?? '', $query);
return $parts['scheme'] . '://' . $parts['host'] . ($parts['path'] ?? '') . '?' . http_build_query(array_merge($query, $args));
}
class JsonReply extends \Exception {
public function __construct(public array $payload, public int $status) { parent::__construct(); }
}
function wp_send_json_success($data) { throw new JsonReply(['success' => true, 'data' => $data], 200); }
function wp_send_json_error($data, $status) { throw new JsonReply(['success' => false, 'data' => $data], $status); }
}
namespace AKSRT\Ticket {
class TicketSettings {
@@ -108,7 +99,7 @@ namespace AKSRT\Core {
}
namespace {
$root = dirname(__DIR__, 2);
foreach (['NativeUi', 'AdminListPagination', 'UsersMenusTicketsList', 'UsersMenusTicketsDialogs', 'UsersMenusTicketsDetail', 'UsersMenusTicketsCenter'] as $file) {
foreach (['NativeUi', 'AdminListPagination', 'UsersMenusTicketsList', 'UsersMenusTicketsCreate', 'UsersMenusTicketsDetail', 'UsersMenusTicketsCenter'] as $file) {
require $root . '/src/Admin/Native/' . $file . '.php';
}
require $root . '/src/Ticket/TicketQuery.php';
@@ -129,38 +120,31 @@ namespace {
public function prepare($sql, $params) { $this->sql = $sql; $this->params = $params; return $sql; }
public function get_results($sql, $format) { return [['status' => 'closed', 'total' => 2]]; }
}
$page = new TicketPage();
$_GET = ['status' => 'handling', 'ticket_type_id' => 1, 's' => 'needle', 'paged' => 99, 'id' => 99];
ob_start();
$page->render_tickets_page();
$html = ob_get_clean();
$initial = ['queries' => \AKSRT\Ticket\TicketRepository::$queries, 'counts' => \AKSRT\Ticket\TicketRepository::$counts, 'detail_reads' => \AKSRT\Ticket\TicketRepository::$reads, 'reply_reads' => \AKSRT\Ticket\TicketRepository::$replies_read];
function view($page, array $args) {
$_GET = $args;
try { $page->ajax_ticket_view(); } catch (JsonReply $reply) { return ['status' => $reply->status] + $reply->payload; }
throw new \RuntimeException('Expected JSON response');
function render_page(TicketPage $page, array $get): string {
$_GET = $get;
ob_start();
$page->render_tickets_page();
return (string) ob_get_clean();
}
$closed = view($page, ['id' => 99, 'status' => 'pending', 's' => 'not in list', 'refresh_list' => 1, 'paged' => 99]);
$open = view($page, ['id' => 21]);
$missing = view($page, ['id' => 404]);
$GLOBALS['allowed'] = false;
$denied = view($page, ['id' => 21]);
$GLOBALS['allowed'] = true;
$GLOBALS['valid_nonce'] = false;
$nonce = view($page, ['id' => 21]);
$GLOBALS['valid_nonce'] = true;
$repo = \AKSRT\Ticket\TicketRepository::class;
$page = new TicketPage();
$list = render_page($page, ['status' => 'handling', 'ticket_type_id' => 1, 's' => 'needle', 'paged' => 99]);
$initial = ['queries' => $repo::$queries, 'counts' => $repo::$counts, 'detail_reads' => $repo::$reads, 'reply_reads' => $repo::$replies_read];
$list_queries = count($repo::$queries);
$open = render_page($page, ['id' => 21, 'status' => 'pending', 's' => 'not in list', 'paged' => 3, 'aksrt_ticket_msg' => 'reply']);
$closed = render_page($page, ['id' => 99]);
$missing = render_page($page, ['id' => 404]);
$detail_list_queries = count($repo::$queries) - $list_queries;
$create = render_page($page, ['view' => 'new', 'status' => 'pending', 'paged' => 2]);
$bogus_notice = render_page($page, ['id' => 21, 'aksrt_ticket_msg' => '<script>']);
\AKSRT\Ticket\TicketTypeService::$empty = true;
ob_start();
$page->render_tickets_page();
$no_types = ob_get_clean();
$no_types = render_page($page, ['view' => 'new']);
\AKSRT\Ticket\TicketTypeService::$empty = false;
\AKSRT\Ticket\TicketSettings::$orders = false;
ob_start();
$page->render_tickets_page();
$no_orders = ob_get_clean();
$no_orders = render_page($page, ['view' => 'new']);
$GLOBALS['wpdb'] = new FakeDb();
$query = new QueryProbe();
$where = $query->where([]);
$statistic = $query->counts(['status' => ['pending', 'handling', 'closed', 'archived'], 'ticket_type_id' => 1, 'keywords' => 'needle']);
echo json_encode(compact('html', 'initial', 'closed', 'open', 'missing', 'denied', 'nonce', 'no_types', 'no_orders', 'where', 'statistic') + ['count_sql' => $GLOBALS['wpdb']->sql, 'count_params' => $GLOBALS['wpdb']->params], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
echo json_encode(compact('list', 'initial', 'open', 'closed', 'missing', 'detail_list_queries', 'create', 'bogus_notice', 'no_types', 'no_orders', 'where', 'statistic') + ['count_sql' => $GLOBALS['wpdb']->sql, 'count_params' => $GLOBALS['wpdb']->params], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}