对照参考主题(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 项通过
177 lines
5.9 KiB
PHP
177 lines
5.9 KiB
PHP
<?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,
|
||
]);
|
||
}
|
||
}
|