feat(admin): rebuild ban page to mirror the reference theme

- rename the users.php submenu to 举报&禁封申诉 and the page heading to
  用户禁封申诉及用户举报管理
- merge report tickets and ban appeals into one Zibll-style queue:
  type filter buttons (全部/禁封申诉/用户举报), status subsubsub
  (全部/待处理/已批准/已拒绝), search box, sortable widefat table with
  the same column set (状态/类型/提交用户/详情/提交时间/更新时间),
  inline process forms and 20-per-page pagination
- report process form shows the reported target, restriction status and
  an enforcement action select; appeal form offers approve/reject radios
  with a message to the user
- move restriction enforcement into ReportCenter::apply_action so REST
  and the native page share one implementation; add get_report_row()
- drop the old three-tab rendering (reports/appeals tabs) and fix all
  notice/mail/SPA links to the merged page
This commit is contained in:
2026-09-19 22:00:17 +08:00
parent 3764d48755
commit 902e71ee62
8 changed files with 626 additions and 217 deletions
@@ -43,11 +43,13 @@ test('report center has no legacy ajax handlers or option migration', () => {
assert.match(reportOps, /has_pending_report\(\$reporter_id, \$target_type, \$target_id\)/)
})
test('report processing writes real restrictions, not a display-only meta', () => {
assert.match(reportOps, /->restrict\(\s*\$target_id,\s*\$action_taken,/)
assert.match(reportOps, /lift_user_restriction\(\$target_id\)/)
assert.match(reportOps, /in_array\(\$action_taken, \\AKSRT\\User\\Restriction::TYPES, true\)/)
assert.ok(!reportOps.includes('aksrt_user_ban_state'), 'dual-track ban state meta must not be written or read')
test('report processing delegates actions to the shared ReportCenter entry', () => {
assert.match(reportOps, /ReportCenter::get_instance\(\)->apply_action\(\$target_type, \$target_id, \$action_taken, \$reason\)/)
assert.match(reportCenter, /public function apply_action\(/)
assert.match(reportCenter, /->restrict\(\s*\$target_id,\s*\$action_taken,/)
assert.match(reportCenter, /lift_user_restriction\(\$target_id\)/)
assert.match(reportCenter, /in_array\(\$action_taken, \\AKSRT\\User\\Restriction::TYPES, true\)/)
assert.ok(!reportCenter.includes('aksrt_user_ban_state'), 'dual-track ban state meta must not be written or read')
for (const deadAction of ["'value' => 'warned'", "'value' => 'muted'", "'value' => 'restricted'", "'value' => 'banned'"]) {
assert.ok(!reportOps.includes(deadAction), `action value must map to Restriction types: ${deadAction}`)
}
@@ -55,6 +57,8 @@ test('report processing writes real restrictions, not a display-only meta', () =
assert.ok(reportOps.includes(action), `missing action option: ${action}`)
}
assert.match(reportOps, /get_user_restriction_state/)
assert.match(reportCenter, /public function get_report_row\(/)
assert.match(usersMenus, /\$svc->apply_action\(/)
})
test('restriction class supports lifting and kicks sessions on ban', () => {
@@ -93,8 +97,46 @@ test('profile governance notice reads from the restriction table', () => {
})
test('admin report notification links to the real page', () => {
assert.match(mailNotifications, /users\.php\?page=aksrt_user_ban&tab=reports/)
assert.match(mailNotifications, /users\.php\?page=aksrt_user_ban'/)
assert.ok(!mailNotifications.includes('page=aksrt_reports&'), 'dead admin page link')
assert.ok(!mailNotifications.includes('tab=reports'), 'merged ticket page has no reports tab')
})
test('native ban page mirrors the reference theme layout', () => {
const tickets = read('src/Admin/Native/UsersMenusTickets.php')
// 菜单与页面标题对齐参考主题
assert.ok(usersMenus.includes(`__('举报&禁封申诉', 'aksrt'),\n __('举报&禁封申诉', 'aksrt')`), 'submenu title should be 举报&禁封申诉')
assert.ok(usersMenus.includes('用户禁封申诉及用户举报管理'), 'page heading should match the reference theme')
// 类型筛选按钮组 + 状态 subsubsub + 搜索
for (const label of [`__('禁封申诉', 'aksrt')`, `__('用户举报', 'aksrt')`, `__('待处理', 'aksrt')`, `__('已批准', 'aksrt')`, `__('已拒绝', 'aksrt')`, `__('搜索记录', 'aksrt')`]) {
assert.ok(tickets.includes(label), `missing reference-theme label: ${label}`)
}
// 表头列与参考主题一致:状态/类型/提交用户/详情/提交时间/更新时间(带排序)
for (const head of ['状态', '类型', '提交用户', '详情', '提交时间', '更新时间']) {
assert.ok(tickets.includes(`__('${head}', 'aksrt')`), `missing table head: ${head}`)
}
assert.match(tickets, /orderby-but/)
assert.match(tickets, /dashicons-arrow-up/)
// 行内处理表单对齐参考主题
for (const label of [`__('立即处理', 'aksrt')`, `__('申诉通过(解封该用户)', 'aksrt')`, `__('申诉拒绝(保持原禁封状态)', 'aksrt')`, `__('此处内容会反馈给举报人', 'aksrt')`, `__('确认提交', 'aksrt')`, `__('用户状态正常', 'aksrt')`, `__('暂无申请记录', 'aksrt')`]) {
assert.ok(tickets.includes(label), `missing process form label: ${label}`)
}
// 分页(每页 20 条,与参考主题一致)
assert.match(tickets, /TICKETS_PER_PAGE = 20/)
assert.match(usersMenus, /private function pagination\(int \$pages, int \$page_num\): void/)
assert.match(usersMenus, /paginate_links\(/)
// 旧的三 tab 结构已收敛
assert.ok(!usersMenus.includes('tab=reports'), 'old reports tab is gone')
assert.ok(!usersMenus.includes('tab=appeals'), 'old appeals tab is gone')
assert.ok(!usersMenus.includes('render_reports_tab'))
assert.ok(!usersMenus.includes('render_appeals_tab'))
assert.match(usersMenus, /use UsersMenusTickets;/)
})
test('comments expose a report entry for other users when reporting is enabled', () => {