Files
wordpress-theme-aksrt/src/Admin/Native/UsersMenusTickets.php
T
kate522andClaude Fable 5 18951b01d6 feat(admin): unify native admin pages on a shared modern UI
Add NativeUi helper + native-ui.css (tokens mirror the SPA Naive UI theme)
and move the pay center, orders, products, coupons, charge cards, income,
withdrawals, tickets, invite codes, VIP, verify, messages and report/ban
pages onto shared header, stat cards, cards, filters, badges and empty
states. Remove inline styles and dead helpers; split PayChargeCards into
sibling traits to stay under the file size limit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-30 15:05:38 +08:00

539 lines
30 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 举报&禁封申诉工单列表 — 对齐参考主题(Zibll)的后台管理页:
* 类型筛选按钮组 + 状态 subsubsub + 搜索 + 排序表头 + 行内处理表单 + 分页。
*
* @package AKSRT
*/
namespace AKSRT\Admin\Native;
defined('ABSPATH') || exit;
trait UsersMenusTickets {
private const TICKETS_PER_PAGE = 20;
/**
* 统一行结构:kind=report|appeal, id, user_id, status(pending|resolved|dismissed),
* raw_status, time, updated, payload。
*/
private function collect_ticket_rows(string $type, string $status, string $s, int $send_user, string $orderby, string $order): array {
$rows = [];
if ($type !== 'appeal' && class_exists('\AKSRT\Messages\ReportCenter')) {
$reports = \AKSRT\Messages\ReportCenter::get_instance()->get_reports(['limit' => 500]);
foreach ((array) $reports as $r) {
$st = sanitize_key((string) ($r['status'] ?? ''));
$norm = $st === 'reviewing' ? 'pending' : $st;
if (!in_array($norm, ['pending', 'resolved', 'dismissed'], true)) {
continue;
}
if ($status !== '' && $norm !== $status) {
continue;
}
if ($send_user > 0 && (int) ($r['reporter_id'] ?? 0) !== $send_user) {
continue;
}
$hay = (string) ($r['reason'] ?? '') . ' ' . (string) ($r['category'] ?? '') . ' ' . (string) ($r['target_type'] ?? '') . ' #' . (string) ($r['target_id'] ?? '');
if ($s !== '' && mb_stripos($hay, $s) === false) {
continue;
}
$rows[] = [
'kind' => 'report',
'id' => (int) ($r['id'] ?? 0),
'user_id' => (int) ($r['reporter_id'] ?? 0),
'status' => $norm,
'raw_status' => $st,
'time' => (string) ($r['created_at'] ?? ''),
'updated' => (string) ($r['updated_at'] ?? ''),
'payload' => $r,
];
}
}
if ($type !== 'report' && class_exists('\AKSRT\User\BanAppeal')) {
$users = get_users([
'meta_key' => \AKSRT\User\BanAppeal::META_STATUS,
'meta_compare' => 'EXISTS',
'number' => 500,
'fields' => ['ID'],
'orderby' => 'ID',
'order' => 'DESC',
]);
foreach ($users as $u) {
$uid = (int) $u->ID;
$st = \AKSRT\User\BanAppeal::get_status($uid);
if ($st === '' || !in_array($st, ['pending', 'approved', 'rejected'], true)) {
continue;
}
$norm = $st === 'approved' ? 'resolved' : ($st === 'rejected' ? 'dismissed' : 'pending');
if ($status !== '' && $norm !== $status) {
continue;
}
if ($send_user > 0 && $uid !== $send_user) {
continue;
}
$payload = \AKSRT\User\BanAppeal::get_payload($uid);
$hay = (string) ($payload['reason'] ?? '') . ' ' . (string) ($payload['reason_key'] ?? '');
if ($s !== '' && mb_stripos($hay, $s) === false) {
continue;
}
$rows[] = [
'kind' => 'appeal',
'id' => $uid,
'user_id' => $uid,
'status' => $norm,
'raw_status' => $st,
'time' => (string) ($payload['time'] ?? ''),
'updated' => (string) ($payload['handled_at'] ?? ''),
'payload' => $payload,
];
}
}
$weights = ['pending' => 0, 'resolved' => 1, 'dismissed' => 2];
usort($rows, static function (array $a, array $b) use ($orderby, $order, $weights): int {
if ($orderby === 'status') {
$cmp = ($weights[$a['status']] ?? 9) <=> ($weights[$b['status']] ?? 9);
} elseif ($orderby === 'type') {
$cmp = strcmp($a['kind'], $b['kind']);
} else {
$cmp = strcmp($a['time'], $b['time']);
}
return $order === 'ASC' ? $cmp : -$cmp;
});
return $rows;
}
public function render_tickets_tab(): void {
$type = isset($_GET['type']) ? sanitize_key((string) wp_unslash($_GET['type'])) : '';
$type = in_array($type, ['report', 'appeal'], true) ? $type : '';
$status = isset($_GET['status']) ? sanitize_key((string) wp_unslash($_GET['status'])) : '';
$status = in_array($status, ['pending', 'resolved', 'dismissed'], true) ? $status : '';
$s = isset($_GET['s']) ? sanitize_text_field((string) wp_unslash($_GET['s'])) : '';
$send_user = isset($_GET['send_user']) ? (int) $_GET['send_user'] : 0;
$paged = isset($_GET['paged']) ? max(1, (int) $_GET['paged']) : 1;
$orderby = isset($_GET['orderby']) ? sanitize_key((string) wp_unslash($_GET['orderby'])) : 'time';
$orderby = in_array($orderby, ['time', 'status', 'type'], true) ? $orderby : 'time';
$order = (isset($_GET['order']) && strtoupper((string) $_GET['order']) === 'ASC') ? 'ASC' : 'DESC';
$action_url = admin_url('users.php?page=aksrt_user_ban');
$process_action = isset($_GET['action']) ? sanitize_key((string) wp_unslash($_GET['action'])) : '';
$page_url = add_query_arg(array_filter([
'tab' => 'tickets',
'type' => $type,
'status' => $status,
's' => $s,
'send_user' => $send_user,
], static function ($v) { return $v !== '' && $v !== 0; }), $action_url);
$rows = $this->collect_ticket_rows($type, $status, $s, $send_user, $orderby, $order);
$total = count($rows);
$pages = max(1, (int) ceil($total / self::TICKETS_PER_PAGE));
$paged = min($paged, $pages);
$tickets = array_slice($rows, ($paged - 1) * self::TICKETS_PER_PAGE, self::TICKETS_PER_PAGE);
NativeUi::card_open();
echo '<div class="aksrt-native-toolbar">';
// 类型筛选按钮组(对齐参考主题:全部 / 禁封申诉 / 用户举报)
echo '<div class="aksrt-native-inline">';
foreach (['' => __('全部', 'aksrt'), 'appeal' => __('禁封申诉', 'aksrt'), 'report' => __('用户举报', 'aksrt')] as $t => $label) {
$url = $t === ''
? remove_query_arg(['s', 'type', 'action', 'id'], $page_url)
: add_query_arg('type', $t, remove_query_arg(['s', 'action', 'id'], $page_url));
printf('<a class="button%s" href="%s">%s</a> ', $type === $t ? ' button-primary' : '', esc_url($url), esc_html($label));
}
echo '</div>';
// 搜索(对齐参考主题:右侧搜索记录表单)
echo '<form class="aksrt-native-filters" method="get">';
echo '<input type="hidden" name="page" value="aksrt_user_ban" /><input type="hidden" name="tab" value="tickets" />';
if ($type !== '') {
echo '<input type="hidden" name="type" value="' . esc_attr($type) . '" />';
}
echo '<input type="search" name="s" value="' . esc_attr($s) . '" placeholder="' . esc_attr__('搜索记录', 'aksrt') . '" />';
submit_button(__('提交', 'aksrt'), 'primary', '', false);
echo '</form></div>';
// 状态 subsubsub(对齐参考主题:全部 | 待处理 | 已批准 | 已拒绝)
echo '<ul class="subsubsub">';
$status_links = [
'' => __('全部', 'aksrt'),
'pending' => __('待处理', 'aksrt'),
'resolved' => __('已批准', 'aksrt'),
'dismissed'=> __('已拒绝', 'aksrt'),
];
$i = 0;
foreach ($status_links as $key => $label) {
$url = $key === '' ? remove_query_arg(['s', 'status'], $page_url) : add_query_arg('status', $key, remove_query_arg('s', $page_url));
$sep = ++$i < count($status_links) ? ' |' : '';
$current = $status === $key ? ' class="current" aria-current="page"' : '';
printf('<li><a%s href="%s">%s</a>%s</li> ', $current, esc_url($url), esc_html($label), esc_html($sep));
}
echo '</ul>';
if ($s !== '') {
echo '<p class="aksrt-native-muted">"' . esc_html($s) . '" ' . esc_html__('的搜索结果', 'aksrt') . '</p>';
}
NativeUi::card_close();
$process_id = isset($_GET['id']) ? (string) wp_unslash($_GET['id']) : '';
// 处理视图不设“查看全部”链接(参考主题无此元素):重置过滤由顶部
// 类型按钮组与状态 subsubsub 承担,工单行内直接展开处理表单。
NativeUi::table_card_open();
echo '<table class="widefat fixed striped posts">';
echo '<thead><tr>';
// 表头(对齐参考主题:状态/类型/提交用户/详情/提交时间/更新时间 + 排序箭头)
$theads = [
['width' => '5%', 'orderby' => 'status', 'name' => __('状态', 'aksrt')],
['width' => '5%', 'orderby' => 'type', 'name' => __('类型', 'aksrt')],
['width' => '5%', 'orderby' => '', 'name' => __('提交用户', 'aksrt')],
['width' => '18%', 'orderby' => '', 'name' => __('详情', 'aksrt')],
['width' => '6%', 'orderby' => 'time', 'name' => __('提交时间', 'aksrt')],
['width' => '6%', 'orderby' => 'updated','name' => __('更新时间', 'aksrt')],
];
foreach ($theads as $thead) {
$orderby_html = '';
if ($thead['orderby'] !== '') {
$orderby_url = add_query_arg('orderby', $thead['orderby'], $page_url);
$orderby_html = '<span class="orderby-but">';
$orderby_html .= '<a title="' . esc_attr__('降序', 'aksrt') . '" href="' . esc_url(add_query_arg('order', 'ASC', $orderby_url)) . '"><span class="dashicons dashicons-arrow-up"></span></a>';
$orderby_html .= '<a title="' . esc_attr__('升序', 'aksrt') . '" href="' . esc_url(add_query_arg('order', 'DESC', $orderby_url)) . '"><span class="dashicons dashicons-arrow-down"></span></a>';
$orderby_html .= '</span>';
}
echo '<th width="' . esc_attr($thead['width']) . '">' . esc_html($thead['name']) . $orderby_html . '</th>';
}
echo '</tr></thead><tbody>';
if (!$tickets) {
echo NativeUi::empty_row(6, __('暂无申请记录', 'aksrt'));
}
foreach ($tickets as $row) {
$process_key = (string) $row['kind'] . '-' . (string) $row['id'];
$expanded = $process_action === 'process' && $process_id === $process_key && $row['status'] === 'pending';
$this->render_ticket_row($row, $page_url, $expanded);
if ($row['status'] !== 'pending') {
continue;
}
// 展开与收起走 JS 切换(无整页刷新,即点即达):展开时隐藏列表行、
// 显示处理表单(信息只出现一次);取消时换回。无 JS 环境由立即处理
// 链接(action=process)整页兜底,直链进入时表单默认展开。
$hidden_attr = $expanded ? '' : ' style="display:none;"';
echo '<tr data-aksrt-ticket-process="' . esc_attr($process_key) . '"' . $hidden_attr . '><td colspan="6">';
$this->render_ticket_process_form($row, $action_url);
echo '</td></tr>';
}
echo '</tbody></table>';
NativeUi::table_wrap_close();
$this->ticket_pagenavi($total, self::TICKETS_PER_PAGE);
NativeUi::card_close();
// 行内展开/收起的轻量交互(管理员环境必有 jQuery)。
echo '<script>
(function ($) {
function processRow(key) {
return $("[data-aksrt-ticket-process=\\"" + key + "\\"]");
}
function sourceRow(key) {
return $("[data-aksrt-ticket-row=\\"" + key + "\\"]");
}
$(document).on("click", "[data-aksrt-ticket-toggle]", function (e) {
var key = $(this).attr("data-aksrt-ticket-toggle");
var $row = processRow(key);
if (!$row.length) return;
e.preventDefault();
var show = !$row.is(":visible");
// 一次只展开一个:先收起其它展开的表单并换回其列表行。
$("[data-aksrt-ticket-process]:visible").not($row).each(function () {
$(this).hide();
sourceRow($(this).attr("data-aksrt-ticket-process")).show();
});
if (show) {
sourceRow(key).hide();
$row.show();
} else {
$row.hide();
sourceRow(key).show();
}
});
$(document).on("click", "[data-aksrt-ticket-cancel]", function (e) {
var key = $(this).attr("data-aksrt-ticket-cancel");
e.preventDefault();
processRow(key).hide();
sourceRow(key).show();
});
})(jQuery);
</script>';
}
/** 分页(对齐参考主题 zibpay_admin_pagenavi:« ‹ 第X页共Y页 › » + 每页显示数) */
private function ticket_pagenavi(int $total_count, int $per_page): void {
$current_page = isset($_GET['paged']) ? max(1, (int) $_GET['paged']) : 1;
$total_pages = max(1, (int) ceil($total_count / max(1, $per_page)));
$first_page_url = add_query_arg('paged', 1);
$last_page_url = add_query_arg('paged', $total_pages);
$prev_page_url = $current_page > 1 ? add_query_arg('paged', $current_page - 1) : '#';
$next_page_url = $current_page < $total_pages ? add_query_arg('paged', $current_page + 1) : '#';
echo '<div class="tablenav bottom"><div class="tablenav-pages">';
echo '<span class="displaying-num">' . esc_html(sprintf(__('每页 %1$s 共%2$s', 'aksrt'), $per_page, $total_count)) . '</span>';
echo '<span class="pagination-links">';
echo '<a class="first-page button' . ($current_page <= 1 ? ' disabled' : '') . '" title="' . esc_attr__('前往第一页', 'aksrt') . '" href="' . esc_url($first_page_url) . '">«</a>';
echo '<a class="prev-page button' . ($current_page <= 1 ? ' disabled' : '') . '" title="' . esc_attr__('前往上一页', 'aksrt') . '" href="' . esc_url($prev_page_url) . '">‹</a>';
echo '<span class="paging-input">' . esc_html(sprintf(__('第 %1$s 页,共 ', 'aksrt'), $current_page)) . '<span class="total-pages">' . esc_html((string) $total_pages) . '</span>' . esc_html__(' 页', 'aksrt') . '</span>';
echo '<a class="next-page button' . ($current_page >= $total_pages ? ' disabled' : '') . '" title="' . esc_attr__('前往下一页', 'aksrt') . '" href="' . esc_url($next_page_url) . '">›</a>';
echo '<a class="last-page button' . ($current_page >= $total_pages ? ' disabled' : '') . '" title="' . esc_attr__('前往最后一页', 'aksrt') . '" href="' . esc_url($last_page_url) . '">»</a>';
echo '</span></div><br class="clear"></div>';
}
private function render_ticket_row(array $row, string $page_url, bool $hidden = false): void {
$kind = $row['kind'];
$status = $row['status'];
$user_id = (int) $row['user_id'];
$process_key = $kind . '-' . (string) $row['id'];
$url = add_query_arg(['action' => 'process', 'id' => $process_key], $page_url);
// 状态列(对齐参考主题:待处理→立即处理按钮 / 已批准 / 处理完成 / 已拒绝 / 正在处理)
$is_processing = isset($_GET['action']) && sanitize_key((string) wp_unslash($_GET['action'])) === 'process'
&& isset($_GET['id']) && (string) wp_unslash($_GET['id']) === $kind . '-' . (string) $row['id'];
if ($is_processing) {
$status_html = NativeUi::badge(__('正在处理', 'aksrt'), 'warning');
} elseif ($status === 'pending') {
// JS 切换展开(无整页刷新);无 JS 时 href 整页兜底。
$status_html = '<a class="button" data-aksrt-ticket-toggle="' . esc_attr($process_key) . '" href="' . esc_url($url) . '">' . esc_html__('立即处理', 'aksrt') . '</a>';
} elseif ($status === 'resolved') {
$status_html = $kind === 'appeal'
? NativeUi::badge(__('已批准', 'aksrt'), 'success')
: NativeUi::badge(__('处理完成', 'aksrt'), 'info');
} else {
$status_html = NativeUi::badge(__('已拒绝', 'aksrt'), 'danger');
}
// 类型列(对齐参考主题:不良举报红 / 禁封申诉绿)
$type_html = $kind === 'report'
? NativeUi::badge(__('不良举报', 'aksrt'), 'danger', false)
: NativeUi::badge(__('禁封申诉', 'aksrt'), 'success', false);
// 提交用户列(显示名链接 + 管理|查看 row-actions)
$user = $user_id > 0 ? get_userdata($user_id) : null;
if ($user) {
$user_html = '<a href="' . esc_url(add_query_arg('send_user', $user_id, $page_url)) . '">' . esc_html($user->display_name) . '</a>';
$user_html .= '<div class="row-actions">';
$user_html .= '<a href="' . esc_url(get_edit_user_link($user_id)) . '">' . esc_html__('管理', 'aksrt') . '</a> | ';
$home = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($user_id) : get_author_posts_url($user_id);
$user_html .= '<a href="' . esc_url($home) . '">' . esc_html__('查看', 'aksrt') . '</a>';
$user_html .= '</div>';
} else {
$user_html = esc_html__('未登录用户', 'aksrt');
}
$desc_html = $kind === 'report' ? $this->ticket_report_desc_html($row['payload']) : $this->ticket_appeal_desc_html($row['payload'], (int) $row['id']);
echo '<tr' . ($hidden ? ' style="display:none;"' : '') . ' data-aksrt-ticket-row="' . esc_attr($process_key) . '">';
echo '<td>' . $status_html . '</td>';
echo '<td>' . $type_html . '</td>';
echo '<td>' . $user_html . '</td>';
echo '<td><div class="aksrt-native-clamp">' . $desc_html . '</div></td>';
echo '<td>' . esc_html((string) $row['time']) . '</td>';
echo '<td>' . esc_html((string) ($row['updated'] !== '' ? $row['updated'] : '—')) . '</td>';
echo '</tr>';
}
/** 举报行详情(对齐参考主题:举报用户/举报内容红字 + 原因) */
private function ticket_report_desc_html(array $r): string {
$target_type = sanitize_key((string) ($r['target_type'] ?? ''));
$target_id = (int) ($r['target_id'] ?? 0);
$link = $this->ticket_target_link($target_type, $target_id);
if ($target_type === 'user') {
$html = '<div class="aksrt-native-text--danger">' . esc_html__('举报用户:', 'aksrt') . ($link !== '' ? $link : esc_html('#' . $target_id)) . '</div>';
} else {
$html = '<div class="aksrt-native-text--danger">' . esc_html__('举报内容:', 'aksrt') . ($link !== '' ? $link : esc_html($target_type . ' #' . $target_id)) . '</div>';
}
$html .= '<div>' . esc_html(sprintf(__('举报原因:%s', 'aksrt'), (string) ($r['reason'] ?? ''))) . '</div>';
if (!empty($r['category'])) {
$html .= '<div>' . esc_html(sprintf(__('分类:%s', 'aksrt'), (string) $r['category'])) . '</div>';
}
return $html;
}
/** 申诉行详情(对齐参考主题:禁封状态/原因/起止时间,或已解封) */
private function ticket_appeal_desc_html(array $payload, int $user_id): string {
$ban_html = $this->ticket_ban_info_html($user_id, 'appeal_row');
$html = $ban_html;
if (!empty($payload['reason'])) {
$html .= '<div>' . esc_html(sprintf(__('申诉说明:%s', 'aksrt'), (string) $payload['reason'])) . '</div>';
}
return $html;
}
/**
* 用户当前限制状态摘要(对齐参考主题禁封信息展示)。
*
* @param string $context appeal_row(申诉列表行,已解封时自动结案)| form(处理表单)
*/
private function ticket_ban_info_html(int $user_id, string $context = 'form'): string {
if ($user_id <= 0 || !class_exists('\AKSRT\User\Restriction')) {
return '';
}
$svc = \AKSRT\User\Restriction::get_instance();
$ban = $svc->get_active_ban($user_id);
if ($ban) {
$end = !empty($ban['expires_at']) ? (string) $ban['expires_at'] : __('永久', 'aksrt');
$html = '<div class="aksrt-native-text--warning">' . esc_html(sprintf(__('禁封状态:%s', 'aksrt'), __('封禁中(禁止登录)', 'aksrt'))) . '</div>';
$html .= '<div>' . esc_html(sprintf(__('禁封原因:%s', 'aksrt'), (string) ($ban['reason'] ?? ''))) . '</div>';
$html .= '<div>' . esc_html(sprintf(__('开始时间:%s', 'aksrt'), (string) ($ban['created_at'] ?? ''))) . '</div>';
$html .= '<div>' . esc_html(sprintf(__('结束时间:%s', 'aksrt'), $end)) . '</div>';
return $html;
}
if ($svc->is_restricted($user_id, \AKSRT\User\Restriction::TYPE_SUSPEND)) {
return '<div class="aksrt-native-text--warning">' . esc_html(sprintf(__('限制状态:%s', 'aksrt'), __('停权中', 'aksrt'))) . '</div>';
}
if ($svc->is_restricted($user_id, \AKSRT\User\Restriction::TYPE_MUTE)) {
return '<div class="aksrt-native-text--warning">' . esc_html(sprintf(__('限制状态:%s', 'aksrt'), __('禁言中', 'aksrt'))) . '</div>';
}
// 对齐参考主题:申诉列表行中用户已无禁封 → 显示已解封并自动结案
if ($context === 'appeal_row' && class_exists('\AKSRT\User\BanAppeal') && \AKSRT\User\BanAppeal::get_status($user_id) === 'pending') {
update_user_meta($user_id, \AKSRT\User\BanAppeal::META_STATUS, 'approved');
return '<div class="aksrt-native-text--warning">' . esc_html__('此用户已解封', 'aksrt') . '</div>';
}
// 对齐参考主题:申诉处理表单中用户已解封的提示
if ($context === 'appeal_form') {
return '<div class="aksrt-native-text--success">' . esc_html__('当前用户已解封', 'aksrt') . '</div>';
}
return '<div class="aksrt-native-text--success">' . esc_html__('用户状态正常', 'aksrt') . '</div>';
}
private function ticket_target_link(string $target_type, int $target_id): string {
if ($target_type === 'post') {
$post = get_post($target_id);
if ($post instanceof \WP_Post) {
return '<a target="_blank" href="' . esc_url((string) get_permalink($post)) . '">' . esc_html(get_the_title($post)) . '</a>';
}
} elseif ($target_type === 'comment') {
$comment = get_comment($target_id);
if ($comment instanceof \WP_Comment) {
return '<a target="_blank" href="' . esc_url((string) get_comment_link($comment)) . '">#' . $target_id . '</a>';
}
} elseif ($target_type === 'user') {
$url = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($target_id) : '';
$user = get_userdata($target_id);
if ($url !== '' && $user) {
return '<a target="_blank" href="' . esc_url($url) . '">' . esc_html($user->display_name) . '</a>';
}
}
return '';
}
/** 行内处理表单(对齐参考主题 form-table 结构) */
private function render_ticket_process_form(array $row, string $action_url): void {
$kind = $row['kind'];
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
// admin-post.php 依赖 $_REQUEST['action'] 触发 admin_post_{action} 钩子;
// 缺失时 WordPress 不输出任何内容(白屏),这里按工单类型分发。
echo '<input type="hidden" name="action" value="' . esc_attr($kind === 'report' ? 'aksrt_users_update_report' : 'aksrt_users_process_appeal') . '" />';
echo '<table class="form-table"><tbody>';
if ($kind === 'report') {
$r = $row['payload'];
$target_type = sanitize_key((string) ($r['target_type'] ?? ''));
$target_id = (int) ($r['target_id'] ?? 0);
$link = $this->ticket_target_link($target_type, $target_id);
echo '<tr><th>' . esc_html__('被举报用户', 'aksrt') . '</th><td>';
echo $link !== '' ? $link : esc_html($target_type . ' #' . $target_id);
echo '<input type="hidden" name="report_id" value="' . esc_attr((string) $row['id']) . '" />';
echo '<input type="hidden" name="new_status" value="resolved" />';
echo '</td></tr>';
echo '<tr><th>' . esc_html__('举报信息', 'aksrt') . '</th><td>';
echo esc_html(sprintf(__('举报原因:%s', 'aksrt'), (string) ($r['reason'] ?? ''))) . '<br>';
if (!empty($r['category'])) {
echo esc_html(sprintf(__('分类:%s', 'aksrt'), (string) $r['category'])) . '<br>';
}
echo esc_html(sprintf(__('提交时间:%s', 'aksrt'), (string) $row['time']));
echo '</td></tr>';
if ($target_type === 'user') {
echo '<tr><th>' . esc_html__('被举报用户禁封信息', 'aksrt') . '</th><td>';
if (class_exists('\AKSRT\User\Restriction') && \AKSRT\User\Restriction::get_instance()->is_restricted($target_id)) {
echo $this->ticket_ban_info_html($target_id);
} else {
// 与参考主题一致:跳转到前台该用户公开主页,经封面右下角三点
// 菜单的「禁封此用户」打开封禁弹窗完成处理(后台不提供禁封表单)。
$ban_url = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($target_id) : get_author_posts_url($target_id);
echo '<div class="aksrt-native-text--success">' . esc_html__('用户状态正常', 'aksrt') . '</div>';
echo '<p class="description">' . esc_html__('请查阅相关资料以及举证,如举报事实清楚,可以', 'aksrt');
echo ' <a target="_blank" href="' . esc_url($ban_url) . '">' . esc_html__('点击此处', 'aksrt') . '</a> ';
echo esc_html__('对用户做禁封处理,并将处理结果填写至下方', 'aksrt') . '</p>';
}
echo '</td></tr>';
}
echo '<tr><th>' . esc_html__('处理说明', 'aksrt') . '</th><td>';
echo '<textarea rows="2" cols="40" name="note" placeholder="' . esc_attr__('请输入本次处理的方式及说明', 'aksrt') . '"></textarea>';
echo '<p class="description">' . esc_html__('此处内容会反馈给举报人', 'aksrt') . '</p>';
echo '</td></tr>';
wp_nonce_field('aksrt_users_update_report_' . (int) $row['id']);
} else {
$uid = (int) $row['id'];
$user = get_userdata($uid);
echo '<tr><th>' . esc_html__('申诉用户', 'aksrt') . '</th><td>';
if ($user instanceof \WP_User) {
$url = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($uid) : get_author_posts_url($uid);
echo '<a target="_blank" href="' . esc_url($url) . '">' . esc_html($user->display_name) . '</a>';
} else {
echo esc_html('#' . $uid);
}
echo '<input type="hidden" name="user_id" value="' . esc_attr((string) $uid) . '" />';
echo '</td></tr>';
echo '<tr><th>' . esc_html__('禁封信息', 'aksrt') . '</th><td>' . $this->ticket_ban_info_html($uid, 'appeal_form') . '</td></tr>';
echo '<tr><th>' . esc_html__('申诉信息', 'aksrt') . '</th><td>';
$payload = $row['payload'];
echo esc_html(sprintf(__('申诉类型:%s', 'aksrt'), (string) ($payload['reason_key'] ?? '—'))) . '<br>';
echo esc_html(sprintf(__('申诉说明:%s', 'aksrt'), (string) ($payload['reason'] ?? ''))) . '<br>';
echo esc_html(sprintf(__('提交时间:%s', 'aksrt'), (string) ($payload['time'] ?? '')));
echo '</td></tr>';
echo '<tr><th>' . esc_html__('处理方式', 'aksrt') . '</th><td>';
echo '<p><input type="radio" name="decision" id="aksrt-process-1-' . $uid . '" value="approve" checked="checked"><label for="aksrt-process-1-' . $uid . '" class="aksrt-native-text--info">' . esc_html__('申诉通过(解封该用户)', 'aksrt') . '</label></p>';
echo '<p><input type="radio" name="decision" id="aksrt-process-2-' . $uid . '" value="reject"><label for="aksrt-process-2-' . $uid . '" class="aksrt-native-text--danger">' . esc_html__('申诉拒绝(保持原禁封状态)', 'aksrt') . '</label></p>';
echo '</td></tr>';
echo '<tr><th>' . esc_html__('处理留言', 'aksrt') . '</th><td>';
echo '<textarea rows="2" cols="40" name="note" placeholder="' . esc_attr__('给用户留言', 'aksrt') . '"></textarea>';
echo '<p class="description">' . esc_html__('如需给用户留言请填写此处,如果拒绝请填写拒绝原因', 'aksrt') . '</p>';
echo '</td></tr>';
wp_nonce_field('aksrt_users_process_appeal_' . $uid);
}
echo '<tr><th></th><td><p>';
echo '<button type="submit" class="button button-primary process-submit">' . esc_html__('确认提交', 'aksrt') . '</button> ';
// 取消:收起表单、换回列表行(JS 切换;无 JS 时整页回列表兜底)。
echo '<a class="button" data-aksrt-ticket-cancel="' . esc_attr($row['kind'] . '-' . (string) $row['id']) . '" href="' . esc_url(remove_query_arg(['action', 'id'])) . '">' . esc_html__('取消', 'aksrt') . '</a>';
echo '</p></td></tr>';
echo '</tbody></table></form>';
}
}