feat(admin): rebuild the restrictions tab on the reference ban modal

Replace the self-invented CRUD (warn/mute/suspend/ban + scope selector)
with the reference-theme ban model: ban method (blackroom suspend /
login ban), duration radios (1/7/14/30/100 days or permanent, default 7),
preset reason chips with an other-reason input, ban note, delete-all-
content switch (posts/comments to trash), and a no-appeal switch with a
required notice that also gates the frontend appeal entry. Banned users
render the status info view (status/start/end/reason) with an unban
leave-a-message form; the record list uses the reference columns and
per-user unban links. Bans notify the user in-site like the reference
zib_updata_user_ban_msg.
This commit is contained in:
2026-09-20 15:48:48 +08:00
parent 970b377b9a
commit c38616b290
4 changed files with 290 additions and 58 deletions
+256 -53
View File
@@ -305,40 +305,141 @@ class UsersMenus {
echo '</div>';
}
/**
* 禁封限制页 — 1:1 对齐参考主题 zib_get_set_user_ban_modal:
* 禁封方式(拉入小黑屋/禁封用户)+ 禁封时间(1/7/14/30/100/永久)+
* 选择禁封原因(预置徽章 + 其它原因)+ 禁封说明 + 删除全部内容 +
* 禁止用户申诉(含说明);已禁封用户展示状态信息与解封留言表单。
* 参考主题无 scope 维度,禁封一律全局。
*/
private function render_restrictions_tab(): void {
if (!class_exists('\AKSRT\User\Restriction')) {
echo '<div class="notice notice-error"><p>' . esc_html__('限制模块未初始化。', 'aksrt') . '</p></div>';
return;
}
$svc = \AKSRT\User\Restriction::get_instance();
$stats = $svc->get_stats();
echo '<p>' . esc_html(sprintf(
/* translators: 1 total 2 active 3 banned */
__('限制记录 %1$d · 生效中 %2$d · 封禁中 %3$d', 'aksrt'),
(int) ($stats['total'] ?? 0),
(int) ($stats['active'] ?? 0),
(int) ($stats['banned'] ?? 0)
)) . '</p>';
echo '<h2>' . esc_html__('添加限制', 'aksrt') . '</h2>';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="margin-bottom:20px;display:flex;gap:8px;flex-wrap:wrap;align-items:end;">';
echo '<input type="hidden" name="action" value="aksrt_users_add_restriction" />';
wp_nonce_field('aksrt_users_add_restriction');
echo '<p><label>' . esc_html__('用户 ID', 'aksrt') . '<br><input type="number" name="user_id" min="1" required value="' . esc_attr((string) (isset($_GET['user_id']) ? max(0, (int) $_GET['user_id']) : 0)) . '" /></label></p>';
echo '<p><label>' . esc_html__('类型', 'aksrt') . '<br><select name="type">';
foreach (['warn' => __('警告', 'aksrt'), 'mute' => __('禁言', 'aksrt'), 'suspend' => __('停权', 'aksrt'), 'ban' => __('封禁', 'aksrt')] as $key => $label) {
printf('<option value="%s">%s</option>', esc_attr($key), esc_html($label));
$page_url = admin_url('users.php?page=aksrt_user_ban&tab=restrictions');
$target_id = isset($_GET['user_id']) ? max(0, (int) $_GET['user_id']) : 0;
$unban = isset($_GET['action']) && sanitize_key((string) wp_unslash($_GET['action'])) === 'unban';
$target = $target_id > 0 ? get_userdata($target_id) : null;
// 页面级样式(wp-admin 不加载前端 kit,就地声明参考主题同款外观)
echo '<style>
.aksrt-ban-modal .muted-2-color{color:#777;}
.aksrt-ban-modal .author-set-left{min-width:80px;flex:0 0 auto;color:#777;}
.aksrt-ban-modal .author-set-right{margin-top:0;}
.aksrt-ban-modal .form-radio label{display:inline-block;margin:0 6px 6px 0;}
.aksrt-ban-modal .form-radio input[type=radio]{display:none;}
.aksrt-ban-modal .form-radio input[type=radio]+span{display:inline-block;padding:4px 10px;border:1px solid #dcdcde;border-radius:4px;background:#f6f7f7;cursor:pointer;}
.aksrt-ban-modal .form-radio input[type=radio]:checked+span{background:#2271b1;border-color:#2271b1;color:#fff;}
.aksrt-ban-modal .but-average.modal-buts{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:16px;max-width:480px;}
.aksrt-ban-modal .but-average.modal-buts .but{min-height:40px;text-align:center;border-radius:10px;border:1px solid #dcdcde;background:#f6f7f7;font-weight:600;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;}
.aksrt-ban-modal .but-average.modal-buts .but.c-blue{background:#2271b1;border-color:#2271b1;color:#fff;}
.aksrt-ban-modal .but-average.modal-buts .but.c-yellow{background:#dba617;border-color:#dba617;color:#fff;}
.aksrt-ban-modal .switch-row{display:flex;align-items:center;margin:0 0 14px;font-weight:400;}
.aksrt-ban-modal .switch-row .switch-title{color:#777;margin-bottom:3px;}
.aksrt-ban-modal .switch-row .switch-help{color:#a7aaad;font-size:12px;}
.aksrt-ban-modal textarea,.aksrt-ban-modal input[type=text]{width:100%;max-width:560px;}
</style>';
// 当前生效中的全局禁封(ban 优先于 suspend,对齐参考主题单一状态语义)
$current = null;
if ($target_id > 0) {
foreach ($svc->get_user_restrictions($target_id, true) as $r) {
if (($r['scope'] ?? '') !== 'global' || !in_array($r['type'], ['ban', 'suspend'], true)) {
continue;
}
echo '</select></label></p>';
echo '<p><label>' . esc_html__('范围', 'aksrt') . '<br><select name="scope">';
foreach (['global' => __('全局', 'aksrt'), 'comment' => __('评论', 'aksrt')] as $key => $label) {
printf('<option value="%s">%s</option>', esc_attr($key), esc_html($label));
if ($current === null || ($r['type'] === 'ban' && $current['type'] !== 'ban')) {
$current = $r;
}
echo '</select></label></p>';
echo '<p><label>' . esc_html__('原因', 'aksrt') . '<br><input type="text" name="reason" class="regular-text" required /></label></p>';
submit_button(__('添加', 'aksrt'), 'primary', '', false);
}
}
echo '<div class="table-box aksrt-ban-modal" style="max-width:760px;padding:12px 16px;">';
if (!$target) {
echo '<p>' . esc_html__('请先通过“用户 ID”定位要禁封或解封的用户(也可从工单列表的“点击此处”进入)。', 'aksrt') . '</p>';
} elseif ($current !== null) {
// —— 已禁封视图(参考主题解封弹窗同构):状态信息 + 解封留言表单 ——
$unban_title = $current['type'] === 'ban' ? __('解封用户', 'aksrt') : __('移出小黑屋', 'aksrt');
$status_text = $current['type'] === 'ban' ? __('禁封中(禁止登录)', 'aksrt') : __('小黑屋禁封中', 'aksrt');
$end_time = !empty($current['expires_at']) ? (string) $current['expires_at'] : __('永久', 'aksrt');
echo '<h2 style="margin-top:0;">' . esc_html($unban_title) . '</h2>';
echo '<div class="mb10 flex"><div class="author-set-left">' . esc_html__('用户', 'aksrt') . '</div><div class="author-set-right">' . esc_html($target->display_name) . '</div></div>';
echo '<div class="mb10 flex"><div class="author-set-left">' . esc_html__('状态', 'aksrt') . '</div><div class="author-set-right">' . esc_html($status_text) . '</div></div>';
echo '<div class="mb10 flex"><div class="author-set-left">' . esc_html__('开始时间', 'aksrt') . '</div><div class="author-set-right">' . esc_html((string) ($current['created_at'] ?? '')) . '</div></div>';
echo '<div class="mb10 flex"><div class="author-set-left">' . esc_html__('结束时间', 'aksrt') . '</div><div class="author-set-right">' . esc_html($end_time) . '</div></div>';
echo '<div class="mb10 flex"><div class="author-set-left">' . esc_html__('原因', 'aksrt') . '</div><div class="author-set-right">' . nl2br(esc_html((string) ($current['reason'] ?? ''))) . '</div></div>';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
echo '<input type="hidden" name="action" value="aksrt_users_remove_restriction" />';
echo '<input type="hidden" name="user_id" value="' . esc_attr((string) $target_id) . '" />';
wp_nonce_field('aksrt_users_unban_' . $target_id);
echo '<p><textarea class="large-text" name="desc" rows="2" placeholder="' . esc_attr__('给用户的留言', 'aksrt') . '"></textarea></p>';
echo '<div class="but-average modal-buts">';
echo '<a class="but" href="' . esc_url($page_url) . '">' . esc_html__('取消', 'aksrt') . '</a>';
echo '<button type="submit" class="but c-yellow">' . esc_html(sprintf(__('确认%s', 'aksrt'), $unban_title)) . '</button>';
echo '</div>';
echo '</form>';
} else {
// —— 禁封表单(参考主题禁封弹窗同构) ——
echo '<h2 style="margin-top:0;">' . esc_html__('禁封用户', 'aksrt') . '</h2>';
echo '<form class="aksrt-ban-form" method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
echo '<input type="hidden" name="action" value="aksrt_users_add_restriction" />';
echo '<input type="hidden" name="user_id" value="' . esc_attr((string) $target_id) . '" />';
wp_nonce_field('aksrt_users_add_restriction');
echo '<div class="mt10 mb20 flex ac mb6">' . get_avatar($target_id, 40, '', '', ['class' => 'avatar-mini mr6']) . '<b>' . esc_html($target->display_name) . '</b></div>';
// 禁封方式
echo '<div class="mb6 muted-2-color">' . esc_html__('禁封方式', 'aksrt') . '</div>';
echo '<div class="form-radio form-but-radio">';
echo '<label><input type="radio" name="ban" value="1" checked="checked"><span class="p2-10 mr6">' . esc_html__('拉入小黑屋', 'aksrt') . '</span></label>';
echo '<label><input type="radio" name="ban" value="2"><span class="p2-10 mr6">' . esc_html__('禁封用户', 'aksrt') . '</span></label>';
echo '</div>';
echo '<div class="em09 c-red opacity8 mb10">';
echo '<div data-aksrt-ban-desc="1">' . esc_html__('将用户拉入小黑屋后,用户将失去所有的发布权限及大部分的操作权限,例如发帖、评论等', 'aksrt') . '</div>';
echo '<div data-aksrt-ban-desc="2" style="display:none;">' . esc_html__('禁封的用户将不能登录', 'aksrt') . '</div>';
echo '</div>';
// 禁封时间
echo '<div class="mb6 muted-2-color">' . esc_html__('禁封时间', 'aksrt') . '</div>';
echo '<div class="form-radio mb10 form-but-radio">';
foreach ([1 => __('1天', 'aksrt'), 7 => __('7天', 'aksrt'), 14 => __('14天', 'aksrt'), 30 => __('30天', 'aksrt'), 100 => __('100天', 'aksrt'), 0 => __('永久', 'aksrt')] as $days => $label) {
echo '<label><input type="radio" name="time_day" value="' . esc_attr((string) $days) . '"' . checked(7, $days, false) . '><span class="p2-10 mr6">' . esc_html($label) . '</span></label>';
}
echo '</div>';
// 选择禁封原因(预置原因 + 其它原因)
echo '<div class="mb6 muted-2-color">' . esc_html__('选择禁封原因', 'aksrt') . '</div>';
echo '<div class="form-radio mb10 form-but-radio">';
foreach (\AKSRT\Settings\UserRuntime::ban_settings()['reasons'] ?? [] as $reason) {
echo '<label><input type="radio" name="reason" value="' . esc_attr($reason['name']) . '"><span class="p2-10 mr6">' . esc_html($reason['name']) . '</span></label>';
}
echo '<label><input type="radio" name="reason" value="other"><span class="p2-10 mr6">' . esc_html__('其它原因', 'aksrt') . '</span></label>';
echo '</div>';
echo '<div class="aksrt-reason-other mb20" style="display:none;"><input type="text" class="regular-text" name="reason_other" placeholder="' . esc_attr__('请填写禁封原因', 'aksrt') . '"></div>';
// 禁封说明
echo '<div class="mb6 muted-2-color">' . esc_html__('禁封说明', 'aksrt') . '</div>';
echo '<p><textarea class="large-text" name="desc" rows="2" placeholder="' . esc_attr__('请输入禁封说明或给用户的留言', 'aksrt') . '"></textarea></p>';
// 删除全部内容
echo '<div class="switch-row"><label><input type="checkbox" name="delete_content" value="1"> <span class="flex1 mr20"><span class="switch-title">' . esc_html__('删除全部内容', 'aksrt') . '</span><br><span class="switch-help">' . esc_html__('将该用户发布的全部文章、帖子、评论移至回收站', 'aksrt') . '</span></span></label></div>';
// 禁止用户申诉
echo '<div class="switch-row"><label><input type="checkbox" name="no_appeal" value="1"> <span class="flex1 mr20"><span class="switch-title">' . esc_html__('禁止用户申诉', 'aksrt') . '</span><br><span class="switch-help">' . esc_html__('开启后用户将无法进行申诉', 'aksrt') . '</span></span></label></div>';
echo '<div class="aksrt-no-appeal-desc mb20" style="display:none;"><textarea class="large-text" name="no_appeal_desc" rows="2" placeholder="' . esc_attr__('请填写禁止申诉说明', 'aksrt') . '"></textarea></div>';
echo '<div class="but-average modal-buts">';
echo '<a class="but" href="' . esc_url($page_url) . '">' . esc_html__('取消', 'aksrt') . '</a>';
echo '<button type="submit" class="but c-blue">' . esc_html__('确认提交', 'aksrt') . '</button>';
echo '</div>';
echo '</form>';
}
echo '</div>';
// —— 限制记录列表(参考主题列语义:状态/开始/结束/原因) ——
global $wpdb;
$table = class_exists('\AKSRT\Core\DBMigrator') ? \AKSRT\Core\DBMigrator::table('user_restriction') : '';
if ($table === '') {
@@ -346,31 +447,63 @@ class UsersMenus {
return;
}
$items = $wpdb->get_results("SELECT * FROM {$table} ORDER BY id DESC LIMIT 100", ARRAY_A);
$now = gmdate('Y-m-d H:i:s');
echo '<h2>' . esc_html__('限制记录', 'aksrt') . '</h2>';
echo '<table class="wp-list-table widefat fixed striped"><thead><tr>';
echo '<th>ID</th><th>' . esc_html__('用户', 'aksrt') . '</th><th>' . esc_html__('类型', 'aksrt') . '</th><th>' . esc_html__('范围', 'aksrt') . '</th><th>' . esc_html__('原因', 'aksrt') . '</th><th>' . esc_html__('过期', 'aksrt') . '</th><th>' . esc_html__('操作', 'aksrt') . '</th>';
echo '<th>' . esc_html__('用户', 'aksrt') . '</th><th>' . esc_html__('禁封状态', 'aksrt') . '</th><th>' . esc_html__('开始时间', 'aksrt') . '</th><th>' . esc_html__('结束时间', 'aksrt') . '</th><th>' . esc_html__('原因', 'aksrt') . '</th><th>' . esc_html__('操作', 'aksrt') . '</th>';
echo '</tr></thead><tbody>';
if (!$items) {
echo '<tr><td colspan="7">' . esc_html__('暂无限制记录', 'aksrt') . '</td></tr>';
echo '<tr><td colspan="6">' . esc_html__('暂无限制记录', 'aksrt') . '</td></tr>';
}
foreach ((array) $items as $row) {
$id = (int) ($row['id'] ?? 0);
$user_id = (int) ($row['user_id'] ?? 0);
$user = $user_id > 0 ? get_userdata($user_id) : null;
foreach ((array) $items as $r) {
$type = (string) ($r['type'] ?? '');
$expired = !empty($r['expires_at']) && (string) $r['expires_at'] <= $now;
$is_global = ($r['scope'] ?? '') === 'global' && in_array($type, ['ban', 'suspend'], true);
if ($expired) {
$status_html = '<span style="color:#a7aaad;">' . esc_html__('已过期', 'aksrt') . '</span>';
} elseif ($type === 'ban') {
$status_html = '<span style="color:#fb4444;">' . esc_html__('禁封中(禁止登录)', 'aksrt') . '</span>';
} elseif ($type === 'suspend') {
$status_html = '<span style="color:#fd6a11;">' . esc_html__('小黑屋禁封中', 'aksrt') . '</span>';
} elseif ($type === 'mute') {
$status_html = '<span style="color:#fd6a11;">' . esc_html__('禁言中', 'aksrt') . '</span>';
} else {
$status_html = '<span>' . esc_html__('警告', 'aksrt') . '</span>';
}
$r_user_id = (int) ($r['user_id'] ?? 0);
$r_user = $r_user_id > 0 ? get_userdata($r_user_id) : null;
echo '<tr>';
echo '<td>' . esc_html((string) $id) . '</td>';
echo '<td>' . ($user ? esc_html($user->display_name) : esc_html((string) $user_id)) . '</td>';
echo '<td>' . esc_html((string) ($row['type'] ?? '')) . '</td>';
echo '<td>' . esc_html((string) ($row['scope'] ?? '')) . '</td>';
echo '<td>' . esc_html((string) ($row['reason'] ?? '')) . '</td>';
echo '<td>' . esc_html((string) ($row['expires_at'] ?? '—')) . '</td>';
echo '<td><form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
echo '<input type="hidden" name="action" value="aksrt_users_remove_restriction" />';
echo '<input type="hidden" name="restriction_id" value="' . esc_attr((string) $id) . '" />';
wp_nonce_field('aksrt_users_remove_restriction_' . $id);
submit_button(__('移除', 'aksrt'), 'small', '', false);
echo '</form></td></tr>';
echo '<td>' . ($r_user ? esc_html($r_user->display_name) : esc_html((string) $r_user_id)) . '</td>';
echo '<td>' . $status_html . '</td>';
echo '<td>' . esc_html((string) ($r['created_at'] ?? '')) . '</td>';
echo '<td>' . esc_html(!empty($r['expires_at']) ? (string) $r['expires_at'] : __('永久', 'aksrt')) . '</td>';
echo '<td>' . nl2br(esc_html((string) ($r['reason'] ?? ''))) . '</td>';
echo '<td>';
if ($is_global && !$expired) {
echo '<a class="button" href="' . esc_url(add_query_arg(['action' => 'unban', 'user_id' => $r_user_id], $page_url)) . '">' . esc_html(($type === 'ban' ? __('解封', 'aksrt') : __('移出小黑屋', 'aksrt'))) . '</a>';
}
echo '</td></tr>';
}
echo '</tbody></table>';
// 依赖联动(参考主题 dependency-box 同语义):禁封方式说明/其它原因/禁止申诉说明
echo '<script>
(function ($) {
function syncBanForm() {
$(".aksrt-ban-modal").each(function () {
var $box = $(this);
var ban = $box.find("input[name=\\"ban\\"]:checked").val();
$box.find("[data-aksrt-ban-desc]").each(function () {
$(this).toggle($(this).attr("data-aksrt-ban-desc") === ban);
});
$box.find(".aksrt-reason-other").toggle($box.find("input[name=\\"reason\\"]:checked").val() === "other");
$box.find(".aksrt-no-appeal-desc").toggle($box.find("input[name=\\"no_appeal\\"]").is(":checked"));
});
}
$(document).on("change", ".aksrt-ban-modal input", syncBanForm);
$(document).ready(syncBanForm);
})(jQuery);
</script>';
}
public function handle_update_report(): void {
@@ -390,26 +523,96 @@ class UsersMenus {
wp_die(esc_html__('权限不足', 'aksrt'));
}
check_admin_referer('aksrt_users_add_restriction');
$user_id = isset($_POST['user_id']) ? (int) $_POST['user_id'] : 0;
$type = isset($_POST['type']) ? sanitize_key((string) wp_unslash($_POST['type'])) : 'warn';
$scope = isset($_POST['scope']) ? sanitize_key((string) wp_unslash($_POST['scope'])) : 'global';
$reason = isset($_POST['reason']) ? sanitize_text_field((string) wp_unslash($_POST['reason'])) : '';
$ok = false;
if ($user_id > 0 && $reason !== '' && class_exists('\AKSRT\User\Restriction')) {
$result = \AKSRT\User\Restriction::get_instance()->restrict($user_id, $type, $reason, $scope, '', get_current_user_id());
$ok = !empty($result['id']) || !empty($result['success']) || is_array($result);
if ($user_id <= 0 || !get_userdata($user_id)) {
$this->redirect('aksrt_user_ban', 'restriction_failed', ['tab' => 'restrictions']);
}
$this->redirect('aksrt_user_ban', $ok ? 'restriction_added' : 'restriction_failed', ['tab' => 'restrictions']);
// 禁封方式(参考主题语义):1=拉入小黑屋(suspend) / 2=禁封用户(ban,禁止登录)
$ban = isset($_POST['ban']) ? (int) $_POST['ban'] : 1;
$type = $ban === 2 ? 'ban' : 'suspend';
$time_day = isset($_POST['time_day']) ? (int) $_POST['time_day'] : 0;
$desc = isset($_POST['desc']) ? sanitize_textarea_field((string) wp_unslash($_POST['desc'])) : '';
// 原因:预置徽章值或“其它原因”自定义文本
$reason = isset($_POST['reason']) ? sanitize_text_field((string) wp_unslash($_POST['reason'])) : '';
if ($reason === '' || $reason === 'other') {
$reason = isset($_POST['reason_other']) ? sanitize_text_field((string) wp_unslash($_POST['reason_other'])) : '';
}
if ($reason === '') {
$this->redirect('aksrt_user_ban', 'restriction_failed', ['tab' => 'restrictions', 'user_id' => $user_id]);
}
if ($desc !== '') {
$reason .= "\n" . sprintf(__('禁封说明:%s', 'aksrt'), $desc);
}
$no_appeal = !empty($_POST['no_appeal']);
$no_appeal_desc = isset($_POST['no_appeal_desc']) ? sanitize_textarea_field((string) wp_unslash($_POST['no_appeal_desc'])) : '';
if ($no_appeal && $no_appeal_desc === '') {
// 参考主题同校验:勾选禁止申诉必须填写说明
$this->redirect('aksrt_user_ban', 'restriction_failed', ['tab' => 'restrictions', 'user_id' => $user_id]);
}
$expires = $time_day > 0 ? date('Y-m-d H:i:s', strtotime('+' . $time_day . ' day', current_time('timestamp'))) : '';
$result = \AKSRT\User\Restriction::get_instance()->restrict($user_id, $type, $reason, 'global', $expires, get_current_user_id());
$ok = !empty($result['success']);
if ($ok) {
// 禁封状态变更站内信(参考主题 zib_updata_user_ban_msg 同语义)
$msg = $type === 'ban' ? __('您的账号已被禁封,将无法登录。', 'aksrt') : __('您已被拉入小黑屋,发布等相关权限已被限制。', 'aksrt');
if ($time_day > 0) {
$msg .= ' ' . sprintf(__('期限:%1$d 天,%2$s 起自动解封。', 'aksrt'), $time_day, $expires);
}
\AKSRT\Messages\Message::get_instance()->send($user_id, 'system', $msg, ['title' => __('账号限制', 'aksrt')]);
if ($no_appeal) {
update_user_meta($user_id, '_aksrt_no_appeal', [
'desc' => $no_appeal_desc,
'at' => current_time('mysql'),
]);
}
// 删除全部内容:文章/帖子/评论移至回收站(参考主题同行为)
if (!empty($_POST['delete_content'])) {
$post_ids = get_posts([
'author' => $user_id,
'post_type' => get_post_types(['public' => true]),
'post_status' => 'publish',
'numberposts' => -1,
'fields' => 'ids',
]);
foreach ($post_ids as $pid) {
wp_trash_post((int) $pid);
}
foreach (get_comments(['user_id' => $user_id, 'status' => 'approve', 'number' => 500]) as $comment) {
wp_trash_comment((int) $comment->comment_ID);
}
}
}
$this->redirect('aksrt_user_ban', $ok ? 'restriction_added' : 'restriction_failed', ['tab' => 'restrictions', 'user_id' => $user_id]);
}
public function handle_remove_restriction(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('权限不足', 'aksrt'));
}
$id = isset($_POST['restriction_id']) ? (int) $_POST['restriction_id'] : 0;
check_admin_referer('aksrt_users_remove_restriction_' . $id);
$ok = $id > 0 && class_exists('\AKSRT\User\Restriction') && \AKSRT\User\Restriction::get_instance()->remove_restriction($id);
$this->redirect('aksrt_user_ban', $ok ? 'restriction_removed' : 'restriction_failed', ['tab' => 'restrictions']);
$user_id = isset($_POST['user_id']) ? (int) $_POST['user_id'] : 0;
check_admin_referer('aksrt_users_unban_' . $user_id);
$desc = isset($_POST['desc']) ? sanitize_textarea_field((string) wp_unslash($_POST['desc'])) : '';
$removed = $user_id > 0 && class_exists('\AKSRT\User\Restriction')
? \AKSRT\User\Restriction::get_instance()->lift_user_restriction($user_id)
: 0;
delete_user_meta($user_id, '_aksrt_no_appeal');
// 解封站内信(含给用户的留言)
$msg = __('您的账号限制已解除。', 'aksrt');
if ($desc !== '') {
$msg .= ' ' . sprintf(__('留言:%s', 'aksrt'), $desc);
}
\AKSRT\Messages\Message::get_instance()->send($user_id, 'system', $msg, ['title' => __('限制解除', 'aksrt')]);
$this->redirect('aksrt_user_ban', $removed > 0 ? 'restriction_removed' : 'restriction_failed', ['tab' => 'restrictions', 'user_id' => $user_id]);
}
public function handle_process_appeal(): void {
+5
View File
@@ -97,6 +97,11 @@ class BanAppeal {
if (!$banned) {
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')]);
}
@@ -251,8 +251,31 @@ test('comments expose a report entry for other users when reporting is enabled',
assert.doesNotMatch(commentItem, /\$report\s*=\s*aksrt_get_comment_report_html\(\$comment\)/)
})
test('restrictions tab mirrors the reference ban modal 1:1', () => {
// 禁封方式/禁封时间/预置原因徽章/禁封说明/删除全部内容/禁止用户申诉,
// 以及已禁封用户的状态信息与解封留言表单(zib_get_set_user_ban_modal 同构)。
for (const label of ['拉入小黑屋', '禁封用户', '禁封方式', '禁封时间', '选择禁封原因', '其它原因', '禁封说明', '删除全部内容', '禁止用户申诉', '请填写禁止申诉说明', '解封用户', '移出小黑屋', '小黑屋禁封中', '禁封中(禁止登录)', '给用户的留言', '开始时间', '结束时间']) {
assert.ok(usersMenus.includes(label), `restrictions tab missing reference label: ${label}`)
}
// 时长选项 1/7/14/30/100/永久(默认 7 天);类型映射:小黑屋→suspend,禁封→ban。
assert.match(usersMenus, /1 => __\('1天', 'aksrt'\)/)
assert.match(usersMenus, /7 => __\('7天', 'aksrt'\)/)
assert.match(usersMenus, /100 => __\('100天', 'aksrt'\)/)
assert.match(usersMenus, /checked\(7, \$days, false\)/)
assert.match(usersMenus, /\$type\s*=\s*\$ban === 2 \? 'ban' : 'suspend'/)
// 勾选禁止申诉必须填写说明(参考主题同校验),落用户 meta 供申诉入口拦截。
assert.match(usersMenus, /_aksrt_no_appeal/)
assert.match(usersMenus, /请填写禁止申诉说明/)
const appeal = read('src/User/BanAppeal.php')
assert.match(appeal, /_aksrt_no_appeal/)
// 解封流程:按用户解封 + 留言站内信(旧 restriction_id 通道退役)。
assert.match(usersMenus, /wp_nonce_field\('aksrt_users_unban_' \. \$target_id\)/)
assert.match(usersMenus, /lift_user_restriction\(\$user_id\)/)
assert.ok(!usersMenus.includes('name="restriction_id"'), 'per-record removal superseded by per-user unban')
})
test('uninstall cleans up every ban/report meta key written by the theme', () => {
for (const key of ['_aksrt_active_restrictions', '_aksrt_is_banned', 'aksrt_user_ban_state']) {
for (const key of ['_aksrt_active_restrictions', '_aksrt_is_banned', '_aksrt_no_appeal', 'aksrt_user_ban_state']) {
assert.ok(uninstall.includes(`'${key}'`), `missing uninstall cleanup: ${key}`)
}
})
+1
View File
@@ -67,6 +67,7 @@ $meta_keys = [
'aksrt_unread_message_count',
'_aksrt_active_restrictions',
'_aksrt_is_banned',
'_aksrt_no_appeal',
'aksrt_user_ban_state',
'_aksrt_favorite_posts',
'aksrt_wechat_openid',