Files
wordpress-theme-aksrt/src/User/BanAppeal.php
T
kate522 a4af024f32 feat(ban): align ban prompts with the reference theme modals and block page
对照参考主题(Zibll)修正四项偏差,申诉链路从页面整体迁移至弹窗:

- feat(ban): 新增三个 1:1 复刻弹窗(REST /user-ban/{info,appeal,log}-modal):
  查看禁封状态(多彩头 c-gray + 信息行 + 申诉按钮)、禁封申诉(edit-header
  gavel/c-blue + 信息框 + 资料项 data[key] 输入 + 审核中态 + 禁止申诉态)、
  禁封记录(多彩头 c-yellow + 计数 + 逐条卡片);user-ban.js 增加
  data-aksrt-ban-info/appeal/log 委托与 admin-ajax 申诉提交
- feat(ban): 禁封/解封弹窗的「当前用户已有N次禁封记录」静态文案改为可点击
  黄链(N>1),打开禁封记录弹窗;解封视图信息行改复用统一数据源
- refactor(ban): 治理展示态收敛至 Restriction::state_badges()(封号=c-red
  违规已封号 / 小黑屋=c-yellow 小黑屋禁封中 / 禁言=c-gray 禁言中,颜色与
  字段统一管理)+ get_ban_info()(状态/起止时间/原因/说明/申诉权限,
  三弹窗与禁封页共用)
- feat(ban): 登录行为对齐参考主题——移除登录期拦截(enforce_login_ban)
  与 complete_login 禁封闸门,封号用户可正常登录;新增 template_redirect
  前台禁封页(1:1 zib_user_ban_restrict_login/zib_die_page:红盒提示 +
  信息行 + 申诉权限 + 退出登录/申诉按钮,模板 templates/parts/user/ban-block.php)
- feat(profile): 公开主页禁封状态从资料卡下方提示框改为占据身份行元信息位
  的 badg 徽章(参考主题 author_header_identity 语义);用户中心元信息改为
  but 按钮(徽章+箭头)打开查看禁封状态弹窗;评论拒绝态链接同源
- refactor: 移除申诉页面模板(page-ban-appeal.php)及其资产/注册/入队、
  get_profile_notice/profile_notice 链、state_labels/state_notices/
  login_ban_message、UserRuntime 的 public_notice/ban_notice_text 死配置、
  report_settings 死键、无消费方的 aksrt_ban_appeal_status 端点
- feat(ban): BanAppeal::ajax_submit 兼容弹窗 data[key] 表单结构
  (逐项清洗入库,申诉说明取 data['申诉说明'],旧平铺字段保持兼容)
- chore: 重新构建 public.css/public.min.css(三个新弹窗几何与头部样式)
- test: 契约测试重写(徽章元信息位、三弹窗与记录链接、禁封页、允许登录 +
  前台拦截、死代码移除断言),全量 362 项通过
2026-09-22 19:14:02 +08:00

177 lines
5.9 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
/**
* Frontend ban appeal (legacy user_ban_appeal parity) on Restriction meta.
*
* @package AKSRT
*/
namespace AKSRT\User;
defined('ABSPATH') || exit;
class BanAppeal {
public const META_STATUS = 'aksrt_ban_appeal_status'; // pending|approved|rejected|''
public const META_PAYLOAD = 'aksrt_ban_appeal_payload';
public const META_HISTORY = 'aksrt_ban_appeal_history';
private static $instance = null;
public static function get_instance(): self {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function init(): void {
add_action('wp_ajax_aksrt_ban_appeal_submit', [$this, 'ajax_submit']);
}
/**
* @return array{enabled:bool,desc:string,keys:string[]}
*/
public static function settings(): array {
$all = class_exists('\AKSRT\Settings\Settings') ? (array) \AKSRT\Settings\Settings::get_all() : [];
// 申诉资料:group 数组 [{t:...}](对齐参考主题)或旧版逗号分隔文本
$keys_raw = $all['ban_appeal_keys'] ?? null;
if (is_array($keys_raw)) {
$keys = array_values(array_filter(array_map(static function ($row): string {
return trim((string) ($row['t'] ?? ''));
}, $keys_raw)));
} else {
$keys = array_values(array_filter(array_map('trim', preg_split('/[,,\n]/', (string) $keys_raw) ?: [])));
}
return [
'enabled' => (bool) ($all['user_report_s'] ?? (isset($all['user_report_enabled']) ? $all['user_report_enabled'] : true)),
'desc' => (string) ($all['ban_appeal_desc'] ?? ''),
'keys' => $keys,
];
}
public static function get_status(int $user_id): string {
return sanitize_key((string) get_user_meta($user_id, self::META_STATUS, true));
}
/**
* @return array<string,mixed>
*/
public static function get_payload(int $user_id): array {
$p = get_user_meta($user_id, self::META_PAYLOAD, true);
return is_array($p) ? $p : [];
}
public function ajax_submit(): void {
if (!is_user_logged_in()) {
wp_send_json(['error' => 1, 'msg' => __('请先登录', 'aksrt')]);
}
if (!check_ajax_referer('aksrt_ban_appeal', '_wpnonce', false)) {
wp_send_json(['error' => 1, 'msg' => __('安全校验失败', 'aksrt')]);
}
$user_id = get_current_user_id();
$s = self::settings();
if (!$s['enabled']) {
wp_send_json(['error' => 1, 'msg' => __('申诉功能未开启', 'aksrt')]);
}
// 禁封状态门禁:封号(ban)与小黑屋(suspend)均可申诉
// (对齐参考主题 zib_user_is_ban 的 1/2 双类型语义)。
$restricted = class_exists('\AKSRT\User\Restriction')
&& in_array(
\AKSRT\User\Restriction::get_instance()->get_display_state($user_id),
[\AKSRT\User\Restriction::STATE_BANNED, \AKSRT\User\Restriction::STATE_RESTRICTED],
true
);
if (!$restricted) {
wp_send_json(['error' => 1, 'msg' => __('当前账号未处于禁封状态', 'aksrt')]);
}
// 禁封时勾选“禁止用户申诉”的门禁(参考主题 no_appeal 同语义)。
$no_appeal = get_user_meta($user_id, '_aksrt_no_appeal', true);
if (is_array($no_appeal) && $no_appeal['desc'] !== '') {
wp_send_json(['error' => 1, 'msg' => sprintf(__('当前账号已被限制申诉:%s', 'aksrt'), (string) $no_appeal['desc'])]);
}
if (self::get_status($user_id) === 'pending') {
wp_send_json(['error' => 1, 'msg' => __('申诉正在处理中,请勿重复提交', 'aksrt')]);
}
$reason_key = isset($_POST['reason_key']) ? sanitize_text_field(wp_unslash((string) $_POST['reason_key'])) : '';
$reason = isset($_POST['reason']) ? sanitize_textarea_field(wp_unslash((string) $_POST['reason'])) : '';
$data_rows = null;
// 申诉弹窗表单(1:1 参考主题 zib_get_user_ban_appeal_modal):资料项以
// data[<key>] 逐项提交,申诉说明取 data['申诉说明'];旧版平铺字段保持兼容。
if (isset($_POST['data']) && is_array($_POST['data'])) {
$clean = [];
foreach ($_POST['data'] as $k => $v) {
$clean[sanitize_text_field(wp_unslash((string) $k))] = sanitize_textarea_field(wp_unslash((string) $v));
}
$data_rows = $clean;
$reason_key = '';
$reason = (string) ($clean[__('申诉说明', 'aksrt')] ?? '');
if ($reason === '') {
foreach ($clean as $v) {
if (trim((string) $v) !== '') {
$reason = (string) $v;
break;
}
}
}
}
if ($reason === '') {
wp_send_json(['error' => 1, 'msg' => __('请填写申诉说明', 'aksrt')]);
}
$payload = [
'reason_key' => $reason_key,
'reason' => $reason,
'time' => current_time('Y-m-d H:i'),
];
if ($data_rows !== null) {
$payload['data'] = $data_rows;
}
update_user_meta($user_id, self::META_PAYLOAD, $payload);
update_user_meta($user_id, self::META_STATUS, 'pending');
$history = get_user_meta($user_id, self::META_HISTORY, true);
$history = is_array($history) ? $history : [];
$history[] = $payload;
update_user_meta($user_id, self::META_HISTORY, array_slice($history, -20));
// Notify admins via site message table if available.
if (class_exists('\AKSRT\Messages\Message')) {
$admins = get_users(['role' => 'administrator', 'fields' => 'ID', 'number' => 20]);
$summary = sprintf(
/* translators: 1: user login 2: reason */
__('用户 %1$s 提交禁封申诉:%2$s', 'aksrt'),
wp_get_current_user()->user_login,
mb_substr($reason, 0, 80)
);
foreach ($admins as $admin_id) {
\AKSRT\Messages\Message::get_instance()->send(
(int) $admin_id,
'ban_appeal',
$summary,
[
'title' => __('禁封申诉', 'aksrt'),
// 直达待处理申诉队列(与工单页 type=appeal&status=pending 过滤同参)。
'target_url' => admin_url('users.php?page=aksrt_user_ban&type=appeal&status=pending'),
]
);
}
}
/**
* @param int $user_id
* @param array $payload
*/
do_action('aksrt_ban_appeal_submitted', $user_id, $payload);
wp_send_json([
'error' => 0,
'msg' => __('申诉已提交,请等待管理员处理', 'aksrt'),
'reload' => true,
]);
}
}