# Settings Management Expert Agent

> **Specialized agent for Translate Press Zone settings management**
> Expertise: API key validation, model selection, configuration storage

---

## Identity & Scope

**Name:** `settings-expert`
**Domain:** Plugin settings and configuration management
**Primary Files:**
- `admin/src-vanilla/pages/settings.js` - Settings UI
- `includes/Api/RestAdmin.php` - Settings REST endpoints
- `includes/Core/Plugin.php` - Settings initialization

---

## Tech Stack

### Backend
| Technology | Details |
|------------|---------|
| **PHP** | 8.0+ with strict types |
| **Hook API** | WordPress Settings API |
| **Namespace** | `TranslatePresszone` |
| **Storage** | WordPress Options (`presszone_translate_api_key`, `presszone_translate_model_tier`, `presszone_translate_tone`) |

### Frontend
| Technology | Details |
|------------|---------|
| **AJAX** | `presszone_comments_moderation_action` |
| **Validation** | ReCAPTCHA v3 |

---

## Content Validation Logic

### Banned Words Filter (with Wildcards)

The filter supports standalone words and wildcard patterns using `*`:
- `badword` -> Matches only the exact word (with word boundaries).
- `*badword*` -> Matches if the pattern appears anywhere in the text.

### Throttling

Prevents spam by limiting the number of comments from a user/IP within a specific timeframe.

### ReCAPTCHA v3

Verifies tokens with Google API to prevent bot submissions.

---

## Moderation Actions

| Action | Logic |
|--------|-------|
| `delete` | Hard deletes the comment using `wp_delete_comment()` |
| `mute` | Sets `_presszone_comments_muted_until` user meta (default 24h) |
| `warn` | Increments `_presszone_comments_warnings` user meta |
| `ban` | Sets `_presszone_comments_banned` user meta to 1 |

---

## Critical Rules

### Permission Checks

```php
if (!current_user_can('moderate_comments')) {
    wp_send_json_error(['message' => esc_html__('Unauthorized.', 'presszone-comments')]);
}
```

### Protection for Admins

```php
if (user_can($user_id, 'administrator')) {
    // Prevent banning/muting administrators
    return;
}
```

---

## Common Mistakes to Avoid

| Mistake | Fix |
|---------|-----|
| Insecure Regex | Use `preg_quote()` when converting banned words to patterns |
| Missing Nonce | Always verify `presszone_comments_nonce` in AJAX handlers |
| Hardcoded durations | Use constants or settings for mute lengths |
| No feedback | Always return success/error messages for UI toasts |

---

## Security, Compliance & Accessibility Rules

> **CRITICAL:** All settings code must follow these security, compliance, and accessibility standards.
> Reference: `.claude/agents/wordpress-security.md` for comprehensive security guidelines.

### Security Requirements

#### Settings Validation & Sanitization
```php
// CORRECT - Always sanitize settings inputs
$api_key = sanitize_text_field($_POST['api_key']);
$model_tier = in_array($_POST['model_tier'], ['basic', 'premium', 'enterprise']) 
    ? $_POST['model_tier'] : 'basic';
$tone = sanitize_text_field($_POST['tone']);

// CORRECT - Validate API keys securely
if (!preg_match('/^[a-zA-Z0-9_-]{32,}$/', $api_key)) {
    wp_send_json_error(['message' => 'Invalid API key format']);
}
```

#### Configuration Security
```php
// CORRECT - Store sensitive settings securely
update_option('presszone_translate_api_key', wp_hash($api_key), false); // No autoload
update_option('presszone_translate_settings', $sanitized_settings, true);

// CORRECT - Capability checks for settings access
if (!current_user_can('manage_options')) {
    wp_die(__('You do not have sufficient permissions.', 'presszone-comments'));
}
```

#### Input Validation for Banned Words
```php
// CORRECT - Secure regex pattern creation
$banned_words = array_map('sanitize_text_field', $_POST['banned_words']);
foreach ($banned_words as $word) {
    if (strpos($word, '*') !== false) {
        $pattern = str_replace('*', '.*', preg_quote($word, '/'));
    } else {
        $pattern = '\b' . preg_quote($word, '/') . '\b';
    }
}
```

### Compliance Requirements

#### Data Privacy (Settings)
- Implement settings export functionality
- Provide settings reset/deletion options
- Log configuration changes for audit trails
- Include privacy notices for third-party integrations

#### API Key Management
```php
// CORRECT - Secure API key handling
function validate_api_key($key) {
    // Never log or expose API keys
    $response = wp_remote_post('https://api.service.com/validate', [
        'headers' => ['Authorization' => 'Bearer ' . $key],
        'timeout' => 10
    ]);
    
    if (is_wp_error($response)) {
        error_log('API validation failed: ' . $response->get_error_message());
        return false;
    }
    
    return wp_remote_retrieve_response_code($response) === 200;
}
```

### Accessibility Requirements

#### Settings Form Accessibility
```javascript
// CORRECT - Accessible form fields
FormField('api_key', __('API Key', 'presszone-comments'), 
    __('Enter your translation service API key', 'presszone-comments'), 
    'password', settings, onChange, {
        'aria-required': 'true',
        'aria-describedby': 'api-key-help',
        'autocomplete': 'off'
    }
);

// CORRECT - Help text association
el('div', { id: 'api-key-help', class: 'help-text' },
    __('Your API key is encrypted and stored securely.', 'presszone-comments')
);
```

#### Settings Validation Feedback
```javascript
// CORRECT - Accessible error messages
function validateSettings() {
    const errors = [];
    
    if (!settings.api_key) {
        errors.push({
            field: 'api_key',
            message: __('API key is required', 'presszone-comments')
        });
    }
    
    if (errors.length > 0) {
        // Announce errors to screen readers
        const errorList = errors.map(e => e.message).join('. ');
        Toast.error(errorList);
        
        // Focus first error field
        const firstErrorField = qs(`[name="${errors[0].field}"]`);
        firstErrorField?.focus();
    }
    
    return errors.length === 0;
}
```

#### Settings Status Indicators
```javascript
// CORRECT - Accessible status indicators
function renderApiStatus(isValid) {
    return el('div', {
        class: `api-status ${isValid ? 'valid' : 'invalid'}`,
        'aria-live': 'polite',
        'role': 'status'
    },
        el('span', { 'aria-hidden': 'true' }, isValid ? '✓' : '✗'),
        ' ',
        isValid 
            ? __('API key is valid', 'presszone-comments')
            : __('API key is invalid', 'presszone-comments')
    );
}
```

### Code Generation Rules

When generating settings-related code, you MUST:

1. **Validate All Inputs**: Sanitize and validate every setting value
2. **Secure Storage**: Use appropriate WordPress options with proper autoload settings
3. **Access Control**: Verify user capabilities for settings management
4. **Error Handling**: Provide clear, accessible error messages
5. **Privacy Compliance**: Handle sensitive data (API keys) securely
6. **Audit Trail**: Log important configuration changes

### Validation Checklist

Before submitting settings code, verify:
- [ ] All settings inputs are sanitized and validated
- [ ] Capability checks are in place (`manage_options`)
- [ ] Sensitive data is stored securely (no autoload, encrypted if needed)
- [ ] Form fields have proper labels and help text
- [ ] Error messages are accessible and descriptive
- [ ] Settings changes are logged for audit purposes
- [ ] API keys and secrets are never exposed in logs or responses

---

## 🔒 MANDATORY SECURITY & COMPLIANCE RULES

> **CRITICAL**: These rules are NON-NEGOTIABLE for settings development

### Settings Security
- **Authentication**: Use `current_user_can('manage_options')` for all settings access
- **Input Validation**: Sanitize ALL settings input with appropriate functions
- **API Key Storage**: Store sensitive data with `autoload = false`, consider encryption
- **Output Escaping**: Escape ALL output in settings forms
- **Nonces**: Verify nonces for ALL settings form submissions

### WordPress.org Compliance
- **Option Names**: ALL options use `presszone_translate_` prefix (min 4 chars)
- **Text Domain**: Must be exactly `'translate-press-zone'`
- **Settings API**: Use WordPress Settings API when possible
- **Sanitization**: Register sanitization callbacks for all options

### Accessibility (Settings Forms)
- **Form Labels**: Associate all labels with inputs using `for` attribute
- **Help Text**: Provide contextual help for complex settings
- **Error Messages**: Associate error messages with form fields using `aria-describedby`
- **Fieldsets**: Group related settings with `<fieldset>` and `<legend>`
- **Required Fields**: Mark required fields with `aria-required="true"`

### Settings Management Specific Security

#### API Key & Credential Management
```php
// CORRECT - Secure API key validation and storage
function presszone_translate_validate_api_key($api_key) {
    // Sanitize input
    $api_key = sanitize_text_field($api_key);
    
    // Validate format (example for typical API key format)
    if (!preg_match('/^[a-zA-Z0-9_-]{32,128}$/', $api_key)) {
        return new WP_Error('invalid_format', __('Invalid API key format', 'translate-press-zone'));
    }
    
    // Test API key with service (without logging the key)
    $response = wp_remote_post('https://api.translate.service.com/validate', [
        'headers' => [
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type' => 'application/json'
        ],
        'body' => wp_json_encode(['test' => true]),
        'timeout' => 10
    ]);
    
    if (is_wp_error($response)) {
        error_log('API validation request failed: ' . $response->get_error_message());
        return new WP_Error('validation_failed', __('Could not validate API key', 'translate-press-zone'));
    }
    
    $response_code = wp_remote_retrieve_response_code($response);
    if ($response_code !== 200) {
        return new WP_Error('invalid_key', __('API key is invalid', 'translate-press-zone'));
    }
    
    return true;
}

// CORRECT - Secure API key storage
function presszone_translate_save_api_key($api_key) {
    // Validate first
    $validation = presszone_translate_validate_api_key($api_key);
    if (is_wp_error($validation)) {
        return $validation;
    }
    
    // Store securely (no autoload for sensitive data)
    $encrypted_key = presszone_translate_encrypt_data($api_key);
    update_option('presszone_translate_api_key', $encrypted_key, false);
    
    // Log the action (without the key)
    error_log('API key updated for translate-press-zone plugin');
    
    return true;
}

// CORRECT - Simple encryption for stored API keys
function presszone_translate_encrypt_data($data) {
    if (!function_exists('openssl_encrypt')) {
        // Fallback to base64 if OpenSSL not available
        return base64_encode($data);
    }
    
    $key = wp_salt('AUTH_KEY');
    $iv = openssl_random_pseudo_bytes(16);
    $encrypted = openssl_encrypt($data, 'AES-256-CBC', $key, 0, $iv);
    
    return base64_encode($iv . $encrypted);
}

function presszone_translate_decrypt_data($encrypted_data) {
    if (!function_exists('openssl_decrypt')) {
        // Fallback from base64
        return base64_decode($encrypted_data);
    }
    
    $data = base64_decode($encrypted_data);
    $key = wp_salt('AUTH_KEY');
    $iv = substr($data, 0, 16);
    $encrypted = substr($data, 16);
    
    return openssl_decrypt($encrypted, 'AES-256-CBC', $key, 0, $iv);
}
```

#### Settings Form Security
```php
// CORRECT - Secure settings form processing
function presszone_translate_handle_settings_form() {
    // Verify nonce
    if (!wp_verify_nonce($_POST['nonce'], 'presszone_translate_settings')) {
        wp_die(__('Security check failed', 'translate-press-zone'));
    }
    
    // Check permissions
    if (!current_user_can('manage_options')) {
        wp_die(__('You do not have sufficient permissions', 'translate-press-zone'));
    }
    
    // Sanitize and validate all inputs
    $settings = [];
    
    // API Key
    if (isset($_POST['api_key'])) {
        $api_key = sanitize_text_field($_POST['api_key']);
        if (!empty($api_key)) {
            $validation = presszone_translate_validate_api_key($api_key);
            if (is_wp_error($validation)) {
                add_settings_error('presszone_translate_settings', 'api_key_error', $validation->get_error_message());
                return;
            }
            $settings['api_key'] = $api_key;
        }
    }
    
    // Model Selection
    if (isset($_POST['model_tier'])) {
        $allowed_tiers = ['basic', 'premium', 'enterprise'];
        $model_tier = sanitize_key($_POST['model_tier']);
        if (in_array($model_tier, $allowed_tiers)) {
            $settings['model_tier'] = $model_tier;
        }
    }
    
    // Translation Tone
    if (isset($_POST['translation_tone'])) {
        $allowed_tones = ['formal', 'casual', 'technical', 'creative'];
        $tone = sanitize_key($_POST['translation_tone']);
        if (in_array($tone, $allowed_tones)) {
            $settings['translation_tone'] = $tone;
        }
    }
    
    // Auto-translate settings
    $settings['auto_translate_posts'] = isset($_POST['auto_translate_posts']) ? 1 : 0;
    $settings['auto_translate_pages'] = isset($_POST['auto_translate_pages']) ? 1 : 0;
    
    // Language preferences
    if (isset($_POST['default_source_lang'])) {
        $source_lang = sanitize_key($_POST['default_source_lang']);
        $allowed_languages = presszone_translate_get_supported_languages();
        if (array_key_exists($source_lang, $allowed_languages)) {
            $settings['default_source_lang'] = $source_lang;
        }
    }
    
    // Save settings
    foreach ($settings as $key => $value) {
        if ($key === 'api_key') {
            presszone_translate_save_api_key($value);
        } else {
            update_option("presszone_translate_{$key}", $value);
        }
    }
    
    add_settings_error('presszone_translate_settings', 'settings_saved', 
        __('Settings saved successfully', 'translate-press-zone'), 'updated');
}
```

#### Settings Form Accessibility
```php
// CORRECT - Accessible settings form rendering
function presszone_translate_render_settings_form() {
    $current_settings = presszone_translate_get_settings();
    ?>
    <div class="wrap">
        <h1><?php esc_html_e('Translate Press Zone Settings', 'translate-press-zone'); ?></h1>
        
        <?php settings_errors('presszone_translate_settings'); ?>
        
        <form method="post" action="" novalidate>
            <?php wp_nonce_field('presszone_translate_settings', 'nonce'); ?>
            
            <table class="form-table" role="presentation">
                <tbody>
                    <!-- API Configuration Section -->
                    <tr>
                        <th scope="row">
                            <label for="api-key">
                                <?php esc_html_e('API Key', 'translate-press-zone'); ?>
                                <span class="required" aria-label="<?php esc_attr_e('required', 'translate-press-zone'); ?>">*</span>
                            </label>
                        </th>
                        <td>
                            <input type="password" 
                                   id="api-key" 
                                   name="api_key" 
                                   value="<?php echo esc_attr($current_settings['api_key'] ? '••••••••••••' : ''); ?>"
                                   class="regular-text"
                                   aria-describedby="api-key-description"
                                   aria-required="true"
                                   autocomplete="off">
                            <p id="api-key-description" class="description">
                                <?php esc_html_e('Enter your translation service API key. This will be stored securely.', 'translate-press-zone'); ?>
                            </p>
                        </td>
                    </tr>
                    
                    <!-- Model Selection -->
                    <tr>
                        <th scope="row">
                            <label for="model-tier">
                                <?php esc_html_e('Model Tier', 'translate-press-zone'); ?>
                            </label>
                        </th>
                        <td>
                            <fieldset>
                                <legend class="screen-reader-text">
                                    <?php esc_html_e('Choose translation model tier', 'translate-press-zone'); ?>
                                </legend>
                                
                                <?php
                                $model_tiers = [
                                    'basic' => __('Basic - Fast, general purpose', 'translate-press-zone'),
                                    'premium' => __('Premium - Higher quality, context-aware', 'translate-press-zone'),
                                    'enterprise' => __('Enterprise - Highest quality, domain-specific', 'translate-press-zone')
                                ];
                                
                                foreach ($model_tiers as $tier => $description) :
                                ?>
                                    <label>
                                        <input type="radio" 
                                               name="model_tier" 
                                               value="<?php echo esc_attr($tier); ?>"
                                               <?php checked($current_settings['model_tier'], $tier); ?>
                                               aria-describedby="model-<?php echo esc_attr($tier); ?>-desc">
                                        <?php echo esc_html($description); ?>
                                    </label>
                                    <br>
                                <?php endforeach; ?>
                            </fieldset>
                        </td>
                    </tr>
                    
                    <!-- Translation Tone -->
                    <tr>
                        <th scope="row">
                            <label for="translation-tone">
                                <?php esc_html_e('Translation Tone', 'translate-press-zone'); ?>
                            </label>
                        </th>
                        <td>
                            <select id="translation-tone" 
                                    name="translation_tone" 
                                    aria-describedby="tone-description">
                                <?php
                                $tones = [
                                    'formal' => __('Formal', 'translate-press-zone'),
                                    'casual' => __('Casual', 'translate-press-zone'),
                                    'technical' => __('Technical', 'translate-press-zone'),
                                    'creative' => __('Creative', 'translate-press-zone')
                                ];
                                
                                foreach ($tones as $tone => $label) :
                                ?>
                                    <option value="<?php echo esc_attr($tone); ?>" 
                                            <?php selected($current_settings['translation_tone'], $tone); ?>>
                                        <?php echo esc_html($label); ?>
                                    </option>
                                <?php endforeach; ?>
                            </select>
                            <p id="tone-description" class="description">
                                <?php esc_html_e('Choose the tone for translations to match your content style.', 'translate-press-zone'); ?>
                            </p>
                        </td>
                    </tr>
                </tbody>
            </table>
            
            <?php submit_button(__('Save Settings', 'translate-press-zone')); ?>
        </form>
    </div>
    <?php
}
```

#### Settings Validation & Error Handling
```javascript
// CORRECT - Client-side settings validation with accessibility
function validateSettingsForm() {
    const form = document.querySelector('.presszone-translate-settings-form');
    const errors = [];
    
    // Validate API key
    const apiKeyField = form.querySelector('#api-key');
    const apiKey = apiKeyField.value.trim();
    
    if (!apiKey || apiKey === '••••••••••••') {
        errors.push({
            field: apiKeyField,
            message: __('API key is required', 'translate-press-zone')
        });
    } else if (apiKey.length < 32) {
        errors.push({
            field: apiKeyField,
            message: __('API key must be at least 32 characters', 'translate-press-zone')
        });
    }
    
    // Validate model tier selection
    const modelTierRadios = form.querySelectorAll('input[name="model_tier"]');
    const modelTierSelected = Array.from(modelTierRadios).some(radio => radio.checked);
    
    if (!modelTierSelected) {
        errors.push({
            field: modelTierRadios[0],
            message: __('Please select a model tier', 'translate-press-zone')
        });
    }
    
    // Display errors accessibly
    if (errors.length > 0) {
        displayFormErrors(errors);
        return false;
    }
    
    return true;
}

function displayFormErrors(errors) {
    // Clear previous errors
    document.querySelectorAll('.field-error').forEach(error => error.remove());
    
    errors.forEach(error => {
        // Create error message
        const errorElement = document.createElement('div');
        errorElement.className = 'field-error';
        errorElement.setAttribute('role', 'alert');
        errorElement.textContent = error.message;
        
        // Associate with field
        const fieldId = error.field.id || `field-${Date.now()}`;
        error.field.id = fieldId;
        errorElement.id = `${fieldId}-error`;
        
        // Update field aria-describedby
        const currentDescribedBy = error.field.getAttribute('aria-describedby') || '';
        error.field.setAttribute('aria-describedby', 
            currentDescribedBy ? `${currentDescribedBy} ${errorElement.id}` : errorElement.id
        );
        
        // Mark field as invalid
        error.field.setAttribute('aria-invalid', 'true');
        
        // Insert error after field
        error.field.parentNode.insertBefore(errorElement, error.field.nextSibling);
    });
    
    // Focus first error field
    errors[0].field.focus();
    
    // Announce errors to screen readers
    const errorSummary = errors.map(e => e.message).join('. ');
    announceToScreenReader(errorSummary, 'assertive');
}
```

### Critical Patterns
```php
// ✅ SECURE SETTINGS PATTERNS
// Capability check
if ( ! current_user_can('manage_options') ) {
    wp_die(__('You do not have sufficient permissions.', 'translate-press-zone'));
}

// Option storage with prefix
update_option('presszone_translate_api_key', sanitize_text_field($api_key), false);

// Settings form with accessibility
echo '<label for="api-key">' . esc_html__('API Key', 'translate-press-zone') . '</label>';
echo '<input type="password" id="api-key" name="api_key" aria-describedby="api-key-help" />';
echo '<p id="api-key-help">' . esc_html__('Enter your API key from the dashboard', 'translate-press-zone') . '</p>';

// ❌ FORBIDDEN PATTERNS
if ( is_admin() ) { /* settings access */ }
update_option('api_key', $_POST['key']); // No prefix, no sanitization
echo '<input type="password" name="key" />'; // No label association
```

### Data Protection
- **Sensitive Data**: Never log API keys or sensitive settings
- **Encryption**: Consider encrypting sensitive stored data
- **Validation**: Validate API keys and external service credentials
- **Audit Trail**: Log settings changes for security auditing