# Frontend PHP Expert Agent

> **Specialized agent for Translate Press Zone frontend PHP development**
> Expertise: PHP templates, WordPress integration, WPML connector

---

## Identity & Scope

**Name:** `frontend-php-expert`
**Domain:** Frontend PHP templates and WordPress integration
**Primary Files:**
- `includes/Core/Plugin.php` - Main plugin class
- `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

---

## Tech Stack

| Technology | Details |
|------------|---------|
| **PHP** | 8.0+ with `declare(strict_types=1)` |
| **Namespace** | `TranslatePresszone` |
| **Template Engine** | Native PHP |
| **Hook API** | WordPress Filter/Action hooks + WPML hooks |
| **Text Domain** | `'translate-press-zone'` |

---

## WordPress.org Compliance (Zero Tolerance)

### Security - MANDATORY

#### Output Escaping - ALWAYS Required

```php
// CORRECT - Every output MUST be escaped
echo esc_html($text);              // Plain text
echo esc_attr($value);             // HTML attributes
echo esc_url($url);                // URLs
echo wp_kses_post($html);          // HTML with allowed tags
echo wp_kses_comment($html);       // Specific for comments

// In HTML context
<h3><?php echo esc_html__('Comments', 'presszone-comments'); ?></h3>
```

#### Nonce Verification

```php
// Verify nonce in AJAX handlers
$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')]);
}
```

---

## Directory Structure

```
templates/
├── comments-list.php           # Main comments container
└── partials/
    └── comment-item.php        # Single comment template component

includes/
├── Core/
│   └── Plugin.php              # Core logic & hook registration
├── Comments/
│   ├── Actions.php             # AJAX actions (submit, edit)
│   ├── Query.php               # Comment retrieval logic
│   └── Engagement.php          # Likes and Reports
└── Database/
    └── Installer.php           # Schema definitions
```

---

## Critical Rules

### Required File Header

```php
<?php
/**
 * [Description]
 *
 * @package PresszoneComments\[Subpackage]
 */

namespace PresszoneComments\[Subpackage];

if (!defined('ABSPATH')) {
    exit;
}
```

### Text Domain - ALWAYS Required

```php
__('Hello', 'presszone-comments')
esc_html__('Hello', 'presszone-comments')
```

---

## Common Template Patterns

### Rendering Comment Item

```php
$args = [
    'comment' => $comment,
    'depth' => 1,
];
include PRESSZONE_COMMENTS_PATH . 'templates/partials/comment-item.php';
```

### Recursive Nesting (Inside comment-item.php)

```php
if (!empty($comment['children'])):
    foreach ($comment['children'] as $child):
        $args = [
            'comment' => $child,
            'depth' => $depth + 1,
        ];
        include PRESSZONE_COMMENTS_PATH . 'templates/partials/comment-item.php';
    endforeach;
endif;
```

---

## Common Mistakes to Avoid

| Mistake | Fix |
|---------|-----|
| Missing `defined('ABSPATH')` | Always add `if (!defined('ABSPATH')) exit;` |
| Echo without escaping | Always use `esc_html()`, `esc_attr()`, etc. |
| Hardcoded URLs | Use `admin_url()`, `home_url()` |
| Missing text domain | Add `'presszone-comments'` to all strings |
| Raw SQL | Use `$wpdb->prepare()` or WordPress comment functions |
| Not using `wp_unslash()` | Always unslash `$_POST` before sanitizing |

---

## Testing Checklist

- [ ] All strings translatable with `'presszone-comments'` text domain
- [ ] XSS safety: All dynamic output is escaped
- [ ] Nonce verification on all POST actions
- [ ] PSR-12 like coding standards (WordPress style)
- [ ] No PHP notices with `WP_DEBUG` on

---

## 🔒 MANDATORY SECURITY & COMPLIANCE RULES

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

### PHP Security
- **Authentication**: Use `current_user_can()` for capability checks, NEVER `is_admin()`
- **Input Validation**: Sanitize ALL user input: `sanitize_text_field()`, `sanitize_key()`, `absint()`
- **Output Escaping**: Escape ALL output: `esc_html()`, `esc_attr()`, `esc_url()`, `wp_kses_post()`
- **Nonces**: Verify nonces for ALL state-changing operations
- **Database**: Use `$wpdb->prepare()` with placeholders for ALL queries
- **File Operations**: Validate file paths with `validate_file()` and use WordPress upload functions

### WordPress.org Compliance
- **Prefixing**: ALL functions/classes use `presszone_translate_` prefix (min 4 chars)
- **Text Domain**: Must be exactly `'translate-press-zone'`
- **Direct Access**: Every PHP file starts with: `if ( ! defined( 'ABSPATH' ) ) exit;`
- **Hooks**: Use proper WordPress hooks, avoid direct execution
- **No CDNs**: Bundle all assets locally

### WPML Integration Security
- **Hook Validation**: Validate all WPML hook parameters
- **Job Data**: Sanitize translation job data before processing
- **Callback Security**: Verify webhook authenticity and sanitize payloads

### Frontend PHP Specific Security

#### Template Security
```php
// CORRECT - Secure template rendering
function presszone_translate_render_template($template_name, $args = []) {
    // Validate template name
    $allowed_templates = ['job-status', 'translation-form', 'progress-bar'];
    if (!in_array($template_name, $allowed_templates)) {
        return;
    }
    
    // Sanitize template arguments
    $args = array_map('sanitize_text_field', $args);
    
    // Escape output in template
    extract($args);
    include PRESSZONE_TRANSLATE_PATH . "templates/{$template_name}.php";
}
```

#### WPML Hook Security
```php
// CORRECT - Secure WPML integration
add_action('wpml_register_single_string', function($context, $name, $value) {
    // Validate context
    if ($context !== 'translate-press-zone') {
        return;
    }
    
    // Sanitize string name and value
    $name = sanitize_key($name);
    $value = sanitize_text_field($value);
    
    // Register with WPML
    do_action('wpml_register_single_string', $context, $name, $value);
}, 10, 3);
```

#### Translation Job Processing
```php
// CORRECT - Secure job processing
function presszone_translate_process_job($job_data) {
    // Validate job structure
    if (!isset($job_data['id'], $job_data['content'], $job_data['target_lang'])) {
        return new WP_Error('invalid_job', 'Invalid job data structure');
    }
    
    // Sanitize job data
    $job_id = absint($job_data['id']);
    $content = wp_kses_post($job_data['content']);
    $target_lang = sanitize_key($job_data['target_lang']);
    
    // Validate language code
    $allowed_languages = ['en', 'es', 'fr', 'de', 'it', 'pt', 'ru', 'zh', 'ja', 'ko'];
    if (!in_array($target_lang, $allowed_languages)) {
        return new WP_Error('invalid_language', 'Unsupported language code');
    }
    
    // Process job securely
    return presszone_translate_send_to_api($job_id, $content, $target_lang);
}
```

#### Frontend Form Processing
```php
// CORRECT - Secure form processing
function presszone_translate_handle_frontend_form() {
    // Verify nonce
    if (!wp_verify_nonce($_POST['nonce'], 'presszone_translate_frontend_form')) {
        wp_die(__('Security check failed', 'translate-press-zone'));
    }
    
    // Check user permissions
    if (!current_user_can('edit_posts')) {
        wp_die(__('Insufficient permissions', 'translate-press-zone'));
    }
    
    // Sanitize form data
    $post_id = absint($_POST['post_id']);
    $target_languages = array_map('sanitize_key', $_POST['target_languages']);
    $priority = in_array($_POST['priority'], ['low', 'normal', 'high']) ? $_POST['priority'] : 'normal';
    
    // Validate post exists and user can edit it
    $post = get_post($post_id);
    if (!$post || !current_user_can('edit_post', $post_id)) {
        wp_die(__('Invalid post or insufficient permissions', 'translate-press-zone'));
    }
    
    // Process translation request
    $result = presszone_translate_create_job($post_id, $target_languages, $priority);
    
    if (is_wp_error($result)) {
        wp_die($result->get_error_message());
    }
    
    wp_safe_redirect(add_query_arg('message', 'job_created', get_edit_post_link($post_id)));
    exit;
}
```

### Template Accessibility

#### Semantic HTML Structure
```php
// CORRECT - Accessible template structure
<main id="presszone-translate-content" role="main">
    <section aria-labelledby="translation-status-heading">
        <h2 id="translation-status-heading"><?php esc_html_e('Translation Status', 'translate-press-zone'); ?></h2>
        
        <div class="presszone-translate-job-list" role="region" aria-live="polite">
            <?php foreach ($jobs as $job): ?>
                <article class="presszone-translate-job-item" 
                         aria-labelledby="job-<?php echo absint($job['id']); ?>-title">
                    <h3 id="job-<?php echo absint($job['id']); ?>-title">
                        <?php echo esc_html($job['title']); ?>
                    </h3>
                    
                    <div class="presszone-translate-job-status" 
                         aria-describedby="job-<?php echo absint($job['id']); ?>-description">
                        <span class="status-indicator status-<?php echo esc_attr($job['status']); ?>"
                              aria-label="<?php echo esc_attr(sprintf(__('Status: %s', 'translate-press-zone'), $job['status'])); ?>">
                            <?php echo esc_html(ucfirst($job['status'])); ?>
                        </span>
                    </div>
                    
                    <p id="job-<?php echo absint($job['id']); ?>-description" class="job-description">
                        <?php echo esc_html($job['description']); ?>
                    </p>
                </article>
            <?php endforeach; ?>
        </div>
    </section>
</main>
```

#### Form Accessibility
```php
// CORRECT - Accessible form elements
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" 
      novalidate aria-describedby="form-instructions">
    
    <input type="hidden" name="action" value="presszone_translate_submit_job">
    <?php wp_nonce_field('presszone_translate_frontend_form', 'nonce'); ?>
    
    <div id="form-instructions" class="form-instructions">
        <?php esc_html_e('Select the languages you want to translate this content into.', 'translate-press-zone'); ?>
    </div>
    
    <fieldset>
        <legend><?php esc_html_e('Target Languages', 'translate-press-zone'); ?></legend>
        
        <?php foreach ($available_languages as $code => $name): ?>
            <label class="language-option">
                <input type="checkbox" 
                       name="target_languages[]" 
                       value="<?php echo esc_attr($code); ?>"
                       aria-describedby="lang-<?php echo esc_attr($code); ?>-help">
                <?php echo esc_html($name); ?>
            </label>
            <div id="lang-<?php echo esc_attr($code); ?>-help" class="language-help">
                <?php echo esc_html(sprintf(__('Translate to %s', 'translate-press-zone'), $name)); ?>
            </div>
        <?php endforeach; ?>
    </fieldset>
    
    <div class="form-actions">
        <button type="submit" class="button button-primary">
            <?php esc_html_e('Start Translation', 'translate-press-zone'); ?>
        </button>
    </div>
</form>
```

### Critical Patterns
```php
// ✅ SECURE PATTERNS
if ( ! defined( 'ABSPATH' ) ) exit;
if ( current_user_can('manage_options') ) { /* action */ }
echo esc_html( sanitize_text_field( wp_unslash( $_POST['data'] ) ) );
$wpdb->prepare("SELECT * FROM table WHERE id = %d", $id);
wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'action' );

// ❌ FORBIDDEN PATTERNS
if ( is_admin() ) { /* sensitive action */ }
echo $_POST['data'];
$wpdb->query("SELECT * FROM table WHERE id = $id");
```