feat(messages): redesign native message admin page

- Rebuild users.php?page=aksrt_messages: overview stats, collapsible
  compose card (users list / role recipients), read-state views,
  type/recipient/keyword filters, bulk mark-read/delete, shared
  AdminListPagination
- Extract NotificationAdminQuery / AdminBroadcast / MessageTypes under
  src/Messages; page logic lives in UsersMenusMessages trait
- Page-scoped assets/css/admin/messages.css
- Remove dead aksrt_notification_type_label() and
  Message::get_all_notifications()

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Fable 5 committed 2026-10-27 22:51:11 +08:00
1 parent bbb41547e2
commit 6681b0c306
9 files changed
+1081 -110

No files matched your search

@@ -0,0 +1,55 @@
'use strict';
/**
* 消息管理后台(users.php → aksrt_messages)契约:
* 1) 页面由独立 trait 承载,UsersMenus 只做组合与路由注册;
* 2) 写操作走 admin_post + nonce + 能力校验;
* 3) SQL 全部在 NotificationAdminQuery 内且经 prepare;
* 4) 分页用共享 AdminListPagination;
* 5) 旧实现与死代码已删除。
*/
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const root = path.resolve(__dirname, '../..');
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
test('messages page is composed from its own trait', () => {
const menus = read('src/Admin/Native/UsersMenus.php');
assert.ok(menus.includes('use UsersMenusMessages;'));
assert.ok(menus.includes("'aksrt_messages'"));
assert.ok(menus.includes("add_action('admin_post_aksrt_users_send_message', [$this, 'handle_send_message'])"));
assert.ok(menus.includes("add_action('admin_post_aksrt_users_bulk_messages', [$this, 'handle_bulk_messages'])"));
assert.ok(menus.includes("add_action('admin_enqueue_scripts', [$this, 'enqueue_message_assets'])"));
// 旧实现不得残留在壳类中。
assert.doesNotMatch(menus, /function render_messages_page|function handle_send_message|message_sent|message_failed/);
assert.doesNotMatch(menus, /aksrt_notification/);
});
test('write handlers check capability and nonce', () => {
const page = read('src/Admin/Native/UsersMenusMessages.php');
assert.match(page, /function handle_send_message\(\): void \{\s*\$this->ensure_cap\(\);\s*check_admin_referer\('aksrt_users_send_message'\);/);
assert.match(page, /function handle_bulk_messages\(\): void \{\s*\$this->ensure_cap\(\);\s*check_admin_referer\('aksrt_users_bulk_messages'\);/);
assert.ok(page.includes("wp_nonce_field('aksrt_users_send_message')"));
assert.ok(page.includes("wp_nonce_field('aksrt_users_bulk_messages')"));
// 页面层不直接碰数据库,分页走共享组件。
assert.doesNotMatch(page, /\$wpdb/);
assert.ok(page.includes('AdminListPagination::render('));
});
test('admin query keeps SQL prepared and bulk size capped', () => {
const query = read('src/Messages/NotificationAdminQuery.php');
assert.match(query, /DELETE FROM \{\$table\} WHERE id IN \(\{\$in\}\)", \$ids\)/);
assert.ok(query.includes("array_fill(0, count($ids), '%d')"));
assert.ok(query.includes('array_slice($ids, 0, self::MAX_BULK)'));
assert.ok(query.includes('$wpdb->esc_like($s)'));
});
test('dead notification helpers are removed', () => {
assert.doesNotMatch(read('inc/modules/user-notifications.php'), /aksrt_notification_type_label/);
assert.doesNotMatch(read('src/Messages/Message.php'), /get_all_notifications/);
assert.ok(fs.existsSync(path.join(root, 'assets/css/admin/messages.css')));
});