# Moderation System Skill

> **Domain:** Content moderation, user restrictions, spam filtering, and anti-abuse

---

## Purpose

This skill covers moderation features including user bans, mutes, warnings, banned words filtering, throttling, and ReCAPTCHA integration for the Comments Press Zone plugin.

---

## User Restriction Types

| Type | Storage | Duration | Effect |
|------|---------|----------|--------|
| **Ban** | User meta: `_presszone_comments_banned` | Permanent (until removed) | Cannot comment at all |
| **Mute** | User meta: `_presszone_comments_muted_until` | Temporary (timestamp) | Cannot comment until expiration |
| **Warning** | User meta: `_presszone_comments_warnings` | Permanent record | Counter for escalation |

---

## Banning Users

### Set Ban

```php
public function ban_user(int $user_id, string $reason = ''): bool {
    // Protect administrators
    if (user_can($user_id, 'administrator')) {
        return false;
    }
    
    update_user_meta($user_id, '_presszone_comments_banned', 1);
    update_user_meta($user_id, '_presszone_comments_ban_reason', $reason);
    update_user_meta($user_id, '_presszone_comments_banned_at', time());
    
    // Log action
    $this->log_moderation_action($user_id, 'ban', $reason);
    
    return true;
}
```

### Check Ban Status

```php
public function is_user_banned(int $user_id): bool {
    return (bool) get_user_meta($user_id, '_presszone_comments_banned', true);
}
```

### Unban User

```php
public function unban_user(int $user_id): bool {
    delete_user_meta($user_id, '_presszone_comments_banned');
    delete_user_meta($user_id, '_presszone_comments_ban_reason');
    delete_user_meta($user_id, '_presszone_comments_banned_at');
    
    $this->log_moderation_action($user_id, 'unban', 'Ban lifted');
    
    return true;
}
```

---

## Muting Users

### Set Mute (Temporary)

```php
public function mute_user(int $user_id, int $duration_hours = 24, string $reason = ''): bool {
    // Protect administrators
    if (user_can($user_id, 'administrator')) {
        return false;
    }
    
    $expires_at = time() + ($duration_hours * HOUR_IN_SECONDS);
    
    update_user_meta($user_id, '_presszone_comments_muted_until', $expires_at);
    update_user_meta($user_id, '_presszone_comments_mute_reason', $reason);
    
    $this->log_moderation_action($user_id, 'mute', sprintf('Muted for %d hours: %s', $duration_hours, $reason));
    
    return true;
}
```

### Check Mute Status

```php
public function is_user_muted(int $user_id): bool {
    $muted_until = get_user_meta($user_id, '_presszone_comments_muted_until', true);
    
    if (empty($muted_until)) {
        return false;
    }
    
    // Check if mute has expired
    if (time() >= $muted_until) {
        delete_user_meta($user_id, '_presszone_comments_muted_until');
        delete_user_meta($user_id, '_presszone_comments_mute_reason');
        return false;
    }
    
    return true;
}
```

---

## Warnings System

### Add Warning

```php
public function add_warning(int $user_id, string $reason = ''): int {
    $warnings = (int) get_user_meta($user_id, '_presszone_comments_warnings', true);
    $warnings++;
    
    update_user_meta($user_id, '_presszone_comments_warnings', $warnings);
    
    // Store warning details in custom table
    global $wpdb;
    $table = $wpdb->prefix . 'presszone_comments_warnings';
    $wpdb->insert($table, [
        'user_id' => $user_id,
        'warning_type' => 'manual',
        'reason' => $reason,
        'moderator_id' => get_current_user_id(),
        'is_active' => 1,
    ], ['%d', '%s', '%s', '%d', '%d']);
    
    $this->log_moderation_action($user_id, 'warning', $reason);
    
    // Auto-escalate if threshold reached
    if ($warnings >= 3) {
        $this->mute_user($user_id, 24, 'Auto-mute: 3 warnings');
    }
    
    return $warnings;
}
```

---

## Banned Words Filter

### Filter Pattern (with Wildcards)

```php
public function contains_banned_words(string $content): bool {
    $settings = get_option('presszone_comments_settings', []);
    $banned_words = $settings['banned_words'] ?? '';
    
    if (empty($banned_words)) {
        return false;
    }
    
    $words = array_filter(array_map('trim', explode("\n", $banned_words)));
    
    foreach ($words as $word) {
        if (strpos($word, '*') !== false) {
            // Wildcard pattern: *badword*
            $pattern = '/' . str_replace('*', '.*', preg_quote($word, '/')) . '/i';
            if (preg_match($pattern, $content)) {
                return true;
            }
        } else {
            // Exact word match with word boundaries
            $pattern = '/\b' . preg_quote($word, '/') . '\b/i';
            if (preg_match($pattern, $content)) {
                return true;
            }
        }
    }
    
    return false;
}
```

### Apply Filter (preprocess_comment hook)

```php
public function validate_comment(array $commentdata): array {
    $content = $commentdata['comment_content'];
    
    // Check banned words
    if ($this->contains_banned_words($content)) {
        wp_die(
            esc_html__('Your comment contains inappropriate content.', 'comments-press-zone'),
            esc_html__('Comment Rejected', 'comments-press-zone'),
            ['response' => 403, 'back_link' => true]
        );
    }
    
    return $commentdata;
}
```

---

## Throttling (Rate Limiting)

### Check Comment Rate

```php
public function is_throttled(int $user_id): bool {
    $settings = get_option('presszone_comments_settings', []);
    $throttle_enabled = $settings['enable_throttle'] ?? false;
    $throttle_limit = $settings['throttle_limit'] ?? 5;
    $throttle_minutes = $settings['throttle_minutes'] ?? 10;
    
    if (!$throttle_enabled) {
        return false;
    }
    
    global $wpdb;
    
    $since = gmdate('Y-m-d H:i:s', strtotime("-{$throttle_minutes} minutes"));
    
    $count = $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(*) FROM {$wpdb->comments} 
         WHERE user_id = %d AND comment_date >= %s",
        $user_id,
        $since
    ));
    
    return $count >= $throttle_limit;
}
```

---

## ReCAPTCHA v3 Integration

### Verify Token

```php
public function verify_recaptcha(string $token): bool {
    $settings = get_option('presszone_comments_settings', []);
    $secret_key = $settings['recaptcha_secret_key'] ?? '';
    
    if (empty($secret_key)) {
        return true; // Skip if not configured
    }
    
    $response = wp_remote_post('https://www.google.com/recaptcha/api/siteverify', [
        'body' => [
            'secret' => $secret_key,
            'response' => $token,
        ],
    ]);
    
    if (is_wp_error($response)) {
        error_log('ReCAPTCHA verification failed: ' . $response->get_error_message());
        return false;
    }
    
    $result = json_decode(wp_remote_retrieve_body($response), true);
    
    // Check score (0.0 - 1.0, higher is better)
    $min_score = $settings['recaptcha_min_score'] ?? 0.5;
    
    return isset($result['success']) && $result['success'] && 
           isset($result['score']) && $result['score'] >= $min_score;
}
```

---

## Moderation Actions (Admin)

### AJAX Handler

```php
public function handle_moderation_action(): void {
    check_ajax_referer('presszone_comments_nonce', 'nonce');
    
    if (!current_user_can('moderate_comments')) {
        wp_send_json_error(['message' => esc_html__('Unauthorized.', 'comments-press-zone')]);
    }
    
    $action = isset($_POST['moderation_action']) ? sanitize_key($_POST['moderation_action']) : '';
    $user_id = isset($_POST['user_id']) ? absint($_POST['user_id']) : 0;
    $reason = isset($_POST['reason']) ? sanitize_textarea_field(wp_unslash($_POST['reason'])) : '';
    
    switch ($action) {
        case 'ban':
            $this->ban_user($user_id, $reason);
            break;
        case 'mute':
            $duration = isset($_POST['duration']) ? absint($_POST['duration']) : 24;
            $this->mute_user($user_id, $duration, $reason);
            break;
        case 'warn':
            $this->add_warning($user_id, $reason);
            break;
        case 'unban':
            $this->unban_user($user_id);
            break;
        default:
            wp_send_json_error(['message' => esc_html__('Invalid action.', 'comments-press-zone')]);
    }
    
    wp_send_json_success([
        'message' => esc_html__('Action completed.', 'comments-press-zone'),
    ]);
}
```

---

## Audit Logging

```php
private function log_moderation_action(int $user_id, string $action, string $reason): void {
    global $wpdb;
    $table = $wpdb->prefix . 'presszone_comments_audit_log';
    
    $wpdb->insert($table, [
        'user_id' => $user_id,
        'action' => $action,
        'moderator_id' => get_current_user_id(),
        'reason' => $reason,
    ], ['%d', '%s', '%d', '%s']);
}
```

---

## Common Patterns

### Pre-Comment Validation

```php
add_filter('preprocess_comment', [$this, 'validate_comment']);

public function validate_comment(array $commentdata): array {
    $user_id = $commentdata['user_id'] ?? 0;
    
    // Check ban
    if ($this->is_user_banned($user_id)) {
        wp_die(esc_html__('You are banned from commenting.', 'comments-press-zone'));
    }
    
    // Check mute
    if ($this->is_user_muted($user_id)) {
        wp_die(esc_html__('You are temporarily muted.', 'comments-press-zone'));
    }
    
    // Check throttle
    if ($this->is_throttled($user_id)) {
        wp_die(esc_html__('You are commenting too frequently.', 'comments-press-zone'));
    }
    
    // Check banned words
    if ($this->contains_banned_words($commentdata['comment_content'])) {
        wp_die(esc_html__('Your comment contains inappropriate content.', 'comments-press-zone'));
    }
    
    return $commentdata;
}
```

---

## Common Mistakes to Avoid

| Mistake | Fix |
|---------|-----|
| Not protecting administrators | Always check `user_can($user_id, 'administrator')` |
| Not using `preg_quote()` for patterns | Escape special regex characters |
| No audit logging | Always log moderation actions |
| Hardcoded durations | Use settings or constants |
| Not checking mute expiration | Auto-remove expired mutes |

---

## Testing Checklist

- [ ] Administrators cannot be banned/muted
- [ ] Banned users cannot comment
- [ ] Muted users cannot comment until expiration
- [ ] Banned words filter works with wildcards
- [ ] Throttling limits excessive comments
- [ ] ReCAPTCHA verification works
- [ ] All moderation actions logged
- [ ] Audit trail accessible to admins
