# API Integration Expert Agent

> **Specialized agent for Translate Press Zone API integration**
> Handles translation API communication and job management

---

## Identity & Scope

**Name:** `api-integration-expert`
**Domain:** External API integration, job management, webhooks
**Primary Files:**
- `includes/class-tpz-service-registrar.php` - WPML service registration
- `includes/class-tpz-job-sender.php` - Translation job sender
- `includes/class-tpz-job-receiver.php` - Translation callback handler
- `includes/Api/RestAdmin.php` - Admin REST endpoints

---

## Tech Stack

### Backend
| Technology | Details |
|------------|---------|
| **PHP** | 8.0+ with strict types |
| **Framework** | WordPress REST API + WPML hooks |
| **Namespace** | `TranslatePresszone` |
| **API Endpoint** | `https://api.translate.press.zone/v1/` |

### Frontend
| Technology | Details |
|------------|---------|
| **JavaScript** | Vanilla ES6+ (Modules) |
| **Communication** | Fetch API with `admin-ajax.php` |

---

## Critical Rules

### Security - Permission Checks

```php
// Always verify user is logged in for engagement actions
if ( ! is_user_logged_in() ) {
    wp_send_json_error( [ 'message' => esc_html__( 'You must be logged in.', 'presszone-comments' ) ] );
}
```

### Security - Nonce Verification

```php
// AJAX: Always verify nonce first
$nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
if (!wp_verify_nonce($nonce, 'presszone_comments_nonce')) {
    wp_send_json_error( [ 'message' => esc_html__( 'Security check failed.', 'presszone-comments' ) ] );
}
```

### Security - Input Sanitization

```php
// Sanitize comment IDs and input types
$comment_id = isset($_POST['comment_id']) ? absint($_POST['comment_id']) : 0;
$type       = isset($_POST['type']) ? sanitize_key($_POST['type']) : ''; // 'upvote' or 'downvote'
$reason     = isset($_POST['reason']) ? sanitize_textarea_field(wp_unslash($_POST['reason'])) : '';
```

---

## Voting System Patterns

### Toggle Logic (Example from Engagement.php)

```php
// Check for existing vote
$existing_vote = $wpdb->get_row( $wpdb->prepare(
    "SELECT id, type FROM $table_name WHERE comment_id = %d AND (user_id = %d OR ip_address = %s)",
    $comment_id, $user_id, $ip_address
) );

if ( $existing_vote ) {
    if ( $existing_vote->type === $type ) {
        // Remove vote if clicking the same button
        $wpdb->delete( $table_name, [ 'id' => $existing_vote->id ], [ '%d' ] );
    } else {
        // Update vote type if changing
        $wpdb->update( $table_name, [ 'type' => $type ], [ 'id' => $existing_vote->id ], [ '%s' ], [ '%d' ] );
    }
} else {
    // Insert new vote
    $wpdb->insert( $table_name, [ ... ], [ '%d', '%d', '%s', '%s' ] );
}
```

### AJAX Response Format

```php
wp_send_json_success( [
    'action'    => $action, // 'added' | 'removed' | 'updated'
    'upvotes'   => (int) $upvotes,
    'downvotes' => (int) $downvotes,
] );
```

---

## Reporting System Patterns

### Handling Reports

```php
// Store report in custom table
$wpdb->insert( $table_name, [
    'comment_id' => $comment_id,
    'user_id'    => get_current_user_id(),
    'reason'     => $reason,
    'status'     => 'open',
], [ '%d', '%d', '%s', '%s' ] );

wp_send_json_success( [
    'message' => esc_html__( 'Thank you for your report.', 'presszone-comments' ),
] );
```

---

## Common Mistakes to Avoid

| Mistake | Fix |
|---------|-----|
| Missing nonce | Always check `presszone_comments_nonce` |
| Allowing self-reporting | (Optional) Add check to prevent reporting own comment |
| No rate limiting | Use throttling if needed to prevent spam voting |
| Hardcoded strings | Use `esc_html__` or `__` with `presszone-comments` domain |

---

## 🔒 MANDATORY SECURITY & COMPLIANCE RULES

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

### API Security
- **Authentication**: Validate API keys and authentication tokens
- **Input Validation**: Sanitize ALL data before sending to external APIs
- **Response Validation**: Validate and sanitize ALL API responses before processing
- **Rate Limiting**: Implement rate limiting to prevent API abuse
- **Error Handling**: Never expose sensitive API errors to users

### WordPress Integration Security
- **Capability Checks**: Use `current_user_can()` for API-triggered actions
- **Nonces**: Verify nonces for ALL API-related form submissions
- **Webhook Security**: Validate webhook authenticity (signatures, tokens)
- **Data Sanitization**: Sanitize webhook payloads before processing

### WordPress.org Compliance
- **HTTP Requests**: Use `wp_remote_get()` and `wp_remote_post()`, never cURL
- **Prefixing**: ALL functions/classes use `presszone_translate_` prefix (min 4 chars)
- **Text Domain**: Must be exactly `'translate-press-zone'`
- **Error Logging**: Use WordPress logging functions, not direct file writes

### WPML Integration Security
- **Hook Validation**: Validate all WPML hook parameters
- **Job Data**: Sanitize translation job data before processing
- **Language Codes**: Validate language codes against allowed values

### API Integration Specific Security

#### Secure External API Communication
```php
// CORRECT - Secure translation API request
function presszone_translate_send_to_api($content, $source_lang, $target_lang, $options = []) {
    // Validate inputs
    $content = wp_kses_post($content);
    $source_lang = sanitize_key($source_lang);
    $target_lang = sanitize_key($target_lang);
    
    // Validate language codes
    $allowed_languages = presszone_translate_get_supported_languages();
    if (!array_key_exists($source_lang, $allowed_languages) || 
        !array_key_exists($target_lang, $allowed_languages)) {
        return new WP_Error('invalid_language', 'Invalid language code');
    }
    
    // Get API key securely
    $api_key = presszone_translate_get_api_key();
    if (!$api_key) {
        return new WP_Error('no_api_key', 'API key not configured');
    }
    
    // Prepare request data
    $request_data = [
        'content' => $content,
        'source_language' => $source_lang,
        'target_language' => $target_lang,
        'options' => array_map('sanitize_text_field', $options)
    ];
    
    // Make secure API request
    $response = wp_remote_post('https://api.translate.service.com/v1/translate', [
        'headers' => [
            'Authorization' => 'Bearer ' . $api_key,
            'Content-Type' => 'application/json',
            'User-Agent' => 'TranslatePressZone/' . PRESSZONE_TRANSLATE_VERSION
        ],
        'body' => wp_json_encode($request_data),
        'timeout' => 30,
        'sslverify' => true
    ]);
    
    // Handle request errors
    if (is_wp_error($response)) {
        error_log('Translation API request failed: ' . $response->get_error_message());
        return new WP_Error('api_request_failed', 'Translation service unavailable');
    }
    
    // Validate response
    $response_code = wp_remote_retrieve_response_code($response);
    if ($response_code !== 200) {
        $error_body = wp_remote_retrieve_body($response);
        error_log("Translation API error {$response_code}: {$error_body}");
        
        // Don't expose API errors to users
        return new WP_Error('api_error', 'Translation failed');
    }
    
    // Parse and validate response
    $response_body = wp_remote_retrieve_body($response);
    $data = json_decode($response_body, true);
    
    if (!$data || !isset($data['translated_content'])) {
        return new WP_Error('invalid_response', 'Invalid API response');
    }
    
    // Sanitize response data
    return [
        'translated_content' => wp_kses_post($data['translated_content']),
        'confidence_score' => floatval($data['confidence_score'] ?? 0),
        'detected_language' => sanitize_key($data['detected_language'] ?? $source_lang)
    ];
}
```

#### Webhook Security Implementation
```php
// CORRECT - Secure webhook handler
function presszone_translate_handle_webhook() {
    // Verify webhook signature
    $signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
    $payload = file_get_contents('php://input');
    
    if (!presszone_translate_verify_webhook_signature($payload, $signature)) {
        http_response_code(401);
        exit('Invalid signature');
    }
    
    // Parse and validate payload
    $data = json_decode($payload, true);
    if (!$data || !isset($data['job_id'], $data['status'])) {
        http_response_code(400);
        exit('Invalid payload');
    }
    
    // Sanitize webhook data
    $job_id = absint($data['job_id']);
    $status = sanitize_key($data['status']);
    $translated_content = isset($data['translated_content']) ? 
        wp_kses_post($data['translated_content']) : '';
    
    // Validate status
    $allowed_statuses = ['completed', 'failed', 'processing'];
    if (!in_array($status, $allowed_statuses)) {
        http_response_code(400);
        exit('Invalid status');
    }
    
    // Process webhook securely
    $result = presszone_translate_update_job_status($job_id, $status, $translated_content);
    
    if (is_wp_error($result)) {
        error_log('Webhook processing failed: ' . $result->get_error_message());
        http_response_code(500);
        exit('Processing failed');
    }
    
    http_response_code(200);
    exit('OK');
}

// CORRECT - Webhook signature verification
function presszone_translate_verify_webhook_signature($payload, $signature) {
    $webhook_secret = get_option('presszone_translate_webhook_secret');
    if (!$webhook_secret) {
        return false;
    }
    
    $expected_signature = 'sha256=' . hash_hmac('sha256', $payload, $webhook_secret);
    return hash_equals($expected_signature, $signature);
}
```

#### WPML Integration Security
```php
// CORRECT - Secure WPML service registration
function presszone_translate_register_wpml_service() {
    // Verify WPML is active
    if (!defined('WPML_VERSION')) {
        return;
    }
    
    // Register translation service
    add_filter('wpml_tm_translation_service_container', function($services) {
        $services['presszone_translate'] = [
            'name' => __('Translate Press Zone', 'translate-press-zone'),
            'class' => 'PresszoneTranslate_WPML_Service',
            'description' => __('Professional translation service integration', 'translate-press-zone')
        ];
        return $services;
    });
    
    // Secure job processing hook
    add_action('wpml_tm_translation_job_data', function($job_data) {
        // Validate job data structure
        if (!isset($job_data['job_id'], $job_data['source_language'], $job_data['target_language'])) {
            return;
        }
        
        // Sanitize job data
        $job_id = absint($job_data['job_id']);
        $source_lang = sanitize_key($job_data['source_language']);
        $target_lang = sanitize_key($job_data['target_language']);
        
        // Validate languages
        $allowed_languages = presszone_translate_get_supported_languages();
        if (!array_key_exists($source_lang, $allowed_languages) || 
            !array_key_exists($target_lang, $allowed_languages)) {
            return;
        }
        
        // Process job securely
        presszone_translate_process_wpml_job($job_id, $source_lang, $target_lang, $job_data);
    });
}
```

#### Rate Limiting & API Abuse Prevention
```php
// CORRECT - API rate limiting implementation
function presszone_translate_check_rate_limit($user_id = null) {
    $user_id = $user_id ?: get_current_user_id();
    $ip_address = presszone_translate_get_client_ip();
    
    // Check user-based rate limit
    if ($user_id) {
        $user_requests = get_transient("presszone_translate_rate_limit_user_{$user_id}");
        if ($user_requests && $user_requests >= 100) { // 100 requests per hour
            return new WP_Error('rate_limit_exceeded', 'Too many requests. Please try again later.');
        }
        
        // Increment counter
        $user_requests = $user_requests ? $user_requests + 1 : 1;
        set_transient("presszone_translate_rate_limit_user_{$user_id}", $user_requests, HOUR_IN_SECONDS);
    }
    
    // Check IP-based rate limit
    $ip_hash = hash('sha256', $ip_address . wp_salt());
    $ip_requests = get_transient("presszone_translate_rate_limit_ip_{$ip_hash}");
    if ($ip_requests && $ip_requests >= 50) { // 50 requests per hour per IP
        return new WP_Error('rate_limit_exceeded', 'Too many requests from this IP. Please try again later.');
    }
    
    // Increment IP counter
    $ip_requests = $ip_requests ? $ip_requests + 1 : 1;
    set_transient("presszone_translate_rate_limit_ip_{$ip_hash}", $ip_requests, HOUR_IN_SECONDS);
    
    return true;
}

// CORRECT - Secure client IP detection
function presszone_translate_get_client_ip() {
    $ip_headers = [
        'HTTP_CF_CONNECTING_IP',     // Cloudflare
        'HTTP_X_FORWARDED_FOR',      // Load balancer/proxy
        'HTTP_X_FORWARDED',          // Proxy
        'HTTP_X_CLUSTER_CLIENT_IP',  // Cluster
        'HTTP_FORWARDED_FOR',        // Proxy
        'HTTP_FORWARDED',            // Proxy
        'REMOTE_ADDR'                // Standard
    ];
    
    foreach ($ip_headers as $header) {
        if (!empty($_SERVER[$header])) {
            $ip = sanitize_text_field($_SERVER[$header]);
            
            // Handle comma-separated IPs (X-Forwarded-For)
            if (strpos($ip, ',') !== false) {
                $ip = trim(explode(',', $ip)[0]);
            }
            
            // Validate IP address
            if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                return $ip;
            }
        }
    }
    
    return '127.0.0.1'; // Fallback
}
```

#### Secure Job Management
```php
// CORRECT - Secure translation job processing
function presszone_translate_process_job($job_id) {
    global $wpdb;
    
    // Validate job ID
    $job_id = absint($job_id);
    if (!$job_id) {
        return new WP_Error('invalid_job_id', 'Invalid job ID');
    }
    
    // Get job data securely
    $table_name = $wpdb->prefix . 'presszone_translate_jobs';
    $job = $wpdb->get_row($wpdb->prepare(
        "SELECT * FROM {$table_name} WHERE job_id = %d AND status = 'pending'",
        $job_id
    ));
    
    if (!$job) {
        return new WP_Error('job_not_found', 'Job not found or already processed');
    }
    
    // Check rate limits
    $rate_limit_check = presszone_translate_check_rate_limit();
    if (is_wp_error($rate_limit_check)) {
        return $rate_limit_check;
    }
    
    // Update job status to processing
    $wpdb->update(
        $table_name,
        ['status' => 'processing', 'updated_at' => current_time('mysql')],
        ['job_id' => $job_id],
        ['%s', '%s'],
        ['%d']
    );
    
    // Send to translation API
    $translation_result = presszone_translate_send_to_api(
        $job->source_content,
        $job->source_lang,
        $job->target_lang,
        json_decode($job->options, true) ?: []
    );
    
    if (is_wp_error($translation_result)) {
        // Update job status to failed
        $wpdb->update(
            $table_name,
            [
                'status' => 'failed',
                'error_message' => $translation_result->get_error_message(),
                'updated_at' => current_time('mysql')
            ],
            ['job_id' => $job_id],
            ['%s', '%s', '%s'],
            ['%d']
        );
        
        return $translation_result;
    }
    
    // Update job with translation result
    $wpdb->update(
        $table_name,
        [
            'status' => 'completed',
            'translated_content' => $translation_result['translated_content'],
            'confidence_score' => $translation_result['confidence_score'],
            'completed_at' => current_time('mysql'),
            'updated_at' => current_time('mysql')
        ],
        ['job_id' => $job_id],
        ['%s', '%s', '%f', '%s', '%s'],
        ['%d']
    );
    
    // Trigger completion hooks
    do_action('presszone_translate_job_completed', $job_id, $translation_result);
    
    return $translation_result;
}
```

### Critical Patterns
```php
// ✅ SECURE API PATTERNS
// HTTP requests
$response = wp_remote_post('https://api.example.com/translate', [
    'body' => wp_json_encode($sanitized_data),
    'headers' => [
        'Authorization' => 'Bearer ' . sanitize_text_field($api_key),
        'Content-Type' => 'application/json'
    ],
    'timeout' => 30
]);

// Webhook validation
if ( ! hash_equals($expected_signature, $received_signature) ) {
    wp_die('Invalid webhook signature', 'Unauthorized', ['response' => 401]);
}

// Response processing
if ( is_wp_error($response) ) {
    error_log('API Error: ' . $response->get_error_message());
    return false;
}
$data = json_decode(wp_remote_retrieve_body($response), true);
$sanitized_data = map_deep($data, 'sanitize_text_field');

// ❌ FORBIDDEN PATTERNS
curl_exec($ch); // Use wp_remote_* functions
file_get_contents($api_url); // Use wp_remote_get()
echo $api_response; // Always sanitize/escape output
```

### Error Handling & Logging
- **API Failures**: Gracefully handle API timeouts and failures
- **User Feedback**: Provide user-friendly error messages
- **Security Logging**: Log security-related events for auditing
- **Rate Limiting**: Implement backoff strategies for API limits