feat(users): enforce real bans from report processing and fix appeal lifting

- apply_report_action now writes aksrt_user_restriction via Restriction::restrict
  and lifts via new lift_user_restriction(), replacing the display-only
  aksrt_user_ban_state meta dual-track
- ban type restrictions destroy the target user's sessions on apply
- appeal approval lifts active ban/suspend/mute restrictions instead of
  updating a nonexistent status column
- UsersBan.vue posts to /users/{id}/restrict with valid types (warn/mute/
  suspend/ban), scope and expires_at
- add comment report entry to the comment actions dropdown
- add duplicate pending report guard on submit
- remove dead ReportCenter ajax handlers, option migration, REST alias
  routes and write-only restriction meta
- fix report admin mail link to the real aksrt_user_ban page
- add report-ban contract tests
This commit is contained in:
2026-09-19 21:38:56 +08:00
parent 4dafe96c4b
commit 835e0f983d
12 changed files with 291 additions and 214 deletions
+33 -13
View File
@@ -75,8 +75,11 @@ class Restriction {
return ['success' => false, 'message' => 'Database error.'];
}
// Update user meta for quick lookup
$this->sync_user_restriction_meta($user_id);
// 封禁立即生效:销毁目标用户全部登录会话
if ($type === self::TYPE_BAN && $user_id > 0) {
\WP_Session_Tokens::get_instance($user_id)->destroy_all();
}
do_action('aksrt_user_restriction_changed', $user_id, $type, 'applied', (string) $data['reason']);
return ['success' => true, 'id' => $wpdb->insert_id];
@@ -92,12 +95,39 @@ class Restriction {
$result = $wpdb->delete($this->table(), ['id' => $restriction_id]);
if ($result) {
$this->sync_user_restriction_meta((int) $restriction['user_id']);
do_action('aksrt_user_restriction_changed', (int) $restriction['user_id'], (string) ($restriction['type'] ?? ''), 'removed', '');
}
return (bool) $result;
}
/**
* 解除用户生效中的限制(可选按类型过滤),用于申诉通过或举报工单"解除限制"。
*
* @param int $user_id 用户 ID
* @param array $types 要解除的类型,默认解除 ban/suspend/mute(保留 warn 警告记录)
* @return int 解除的记录数
*/
public function lift_user_restriction(int $user_id, array $types = [self::TYPE_BAN, self::TYPE_SUSPEND, self::TYPE_MUTE]): int {
global $wpdb;
$types = array_values(array_intersect($types, self::TYPES));
if ($user_id <= 0 || empty($types)) {
return 0;
}
$placeholders = implode(',', array_fill(0, count($types), '%s'));
$deleted = $wpdb->query($wpdb->prepare(
"DELETE FROM {$this->table()} WHERE user_id = %d AND (expires_at IS NULL OR expires_at > %s) AND type IN ({$placeholders})",
array_merge([$user_id, current_time('mysql', true)], $types)
));
if ($deleted > 0) {
do_action('aksrt_user_restriction_changed', $user_id, implode(',', $types), 'lifted', '');
}
return (int) $deleted;
}
/**
* Get a single restriction by ID.
*/
@@ -194,16 +224,6 @@ class Restriction {
return $post_id;
}
/**
* Sync restriction meta for quick lookup.
*/
private function sync_user_restriction_meta(int $user_id): void {
$active = $this->get_user_restrictions($user_id, true);
$types = array_unique(array_column($active, 'type'));
update_user_meta($user_id, '_aksrt_active_restrictions', $types);
update_user_meta($user_id, '_aksrt_is_banned', in_array(self::TYPE_BAN, $types, true) ? 1 : 0);
}
/**
* Get restriction stats for admin overview.
*/