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>
539 lines
30 KiB
PHP
539 lines
30 KiB
PHP
<?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>';
|
||
}
|
||
}
|