# ContentManager

## Overview

The ContentManager handles translation linking, content retrieval, and change detection for multilingual content in the Multilingual Press Zone plugin.

## Architecture

```
ContentManager
├── Translation Group Management (P1-19)
│   ├── createTranslationGroup()
│   ├── addToTranslationGroup()
│   ├── getTranslationGroup()
│   ├── getTranslationGroupId()
│   ├── getLinkedTranslations()
│   ├── linkTranslations()
│   ├── unlinkTranslation()
│   ├── isTranslationOf()
│   └── getTranslationForLanguage()
│
├── Translation Retrieval (P1-20)
│   ├── getTranslation()
│   ├── getTranslations()
│   ├── getTranslatedPostId()
│   ├── hasTranslation()
│   ├── getTranslationStatus()
│   ├── getMultipleTranslations()
│   ├── getTranslationCounts()
│   └── getUntranslatedPosts()
│
└── Content Hash Generation (P1-21)
    ├── generateContentHash()
    ├── hasContentChanged()
    ├── updateContentHash()
    └── markTranslationsAsOutdated()
```

## Features

### 1. Translation Group Management

Translation groups link related content across languages. Each group has a unique ID (auto-increment integer).

#### Creating a Translation Group

```php
use MultilingualPressZone\Core\ContentManager;

$manager = new ContentManager();

// Create a new translation group
$post_id = 123; // English post
$language_id = 1; // English language ID
$group_id = $manager->createTranslationGroup($post_id, $language_id);

// Add translations to the group
$spanish_post_id = 456;
$spanish_language_id = 2;
$manager->addToTranslationGroup($group_id, $spanish_post_id, $spanish_language_id);

$french_post_id = 789;
$french_language_id = 3;
$manager->addToTranslationGroup($group_id, $french_post_id, $french_language_id);
```

#### Getting Translation Groups

```php
// Get all translations in a group
$translations = $manager->getTranslationGroup($group_id);
// Returns: [Translation, Translation, ...]

// Get group ID for a post
$group_id = $manager->getTranslationGroupId($post_id);

// Get all linked translations (excluding source)
$linked = $manager->getLinkedTranslations($post_id);
```

#### Linking Translations

```php
// Link two posts as translations
$source_id = 123; // English post
$translation_id = 456; // Spanish post
$language_id = 2; // Spanish

$manager->linkTranslations($source_id, $translation_id, $language_id);

// Check if posts are linked
if ($manager->isTranslationOf($translation_id, $source_id)) {
    echo 'Post is a translation';
}

// Unlink a translation
$manager->unlinkTranslation($translation_id);
```

#### Getting Translation by Language

```php
// Get Spanish translation of a post
$spanish_language_id = 2;
$spanish_post_id = $manager->getTranslationForLanguage($source_id, $spanish_language_id);
```

### 2. Translation Retrieval

Efficient retrieval with multi-layer caching (memory → object cache → transient → database).

#### Getting Translations

```php
// Get translation entity for specific language
$translation = $manager->getTranslation($post_id, $language_id);
// Returns: Translation entity or null

// Get all translations for a post
$translations = $manager->getTranslations($post_id);
// Returns: [Translation, Translation, ...]

// Get translated post ID by locale
$locale = 'es_ES';
$translated_post_id = $manager->getTranslatedPostId($post_id, $locale);
```

#### Checking Translation Status

```php
// Check if translation exists
if ($manager->hasTranslation($post_id, $language_id)) {
    echo 'Translation exists';
}

// Get translation status
$status = $manager->getTranslationStatus($post_id, $language_id);
// Returns: 'original', 'translated', 'needs_update', 'draft', or null
```

#### Batch Operations

```php
// Get multiple translations in one query (efficient)
$post_ids = [123, 456, 789];
$language_id = 2;
$translations = $manager->getMultipleTranslations($post_ids, $language_id);
// Returns: [123 => Translation, 456 => Translation, ...]

// Get translation counts by language
$counts = $manager->getTranslationCounts($post_id);
// Returns: ['en' => 1, 'es' => 1, 'fr' => 1]

// Get posts missing translations
$language_id = 2; // Spanish
$limit = 100;
$untranslated = $manager->getUntranslatedPosts($language_id, $limit);
// Returns: [123, 456, 789] (post IDs)
```

### 3. Content Hash Generation

Automatic change detection using SHA256 hashing of title + content + excerpt.

#### Generating Hashes

```php
// Generate hash for a post
$hash = $manager->generateContentHash($post_id);
// Returns: '5d41402abc4b2a76b9719d911017c592...' (SHA256)

// Check if content has changed
$old_hash = '5d41402abc4b2a76b9719d911017c592...';
if ($manager->hasContentChanged($post_id, $old_hash)) {
    echo 'Content has changed';
}
```

#### Updating Hashes

```php
// Update stored hash after content change
$manager->updateContentHash($post_id);

// Mark all translations as outdated when source changes
$marked = $manager->markTranslationsAsOutdated($source_post_id);
echo "Marked {$marked} translations as needing update";
```

#### Automatic Change Detection

When you update source content, the ContentManager can automatically detect changes and mark translations:

```php
// User updates source post
$post_id = 123;
wp_update_post([
    'ID' => $post_id,
    'post_content' => 'Updated content',
]);

// Detect and mark translations
$translation = $manager->getTranslation($post_id, $default_language_id);
if ($translation && $manager->hasContentChanged($post_id, $translation->getContentHash())) {
    $marked = $manager->markTranslationsAsOutdated($post_id);
}
```

## Caching Strategy

ContentManager uses a 4-layer caching system via CacheManager:

1. **Memory Cache**: PHP array (fastest, per-request)
2. **Object Cache**: Redis/Memcached (if available)
3. **Transient Cache**: WordPress transients (database)
4. **Database**: MySQL queries (slowest)

### Cache Keys

```
translation:{post_id}:{language_code}  # Individual translation
translations:{post_id}                 # All translations for post
group:{group_id}                       # Translation group
record:{post_id}                       # Translation record
language_id:{language_id}              # Language by ID
language_locale:{locale}               # Language by locale
default_language                       # Default language
```

### Cache Invalidation

Caches are automatically invalidated on:

- Translation creation/update/deletion
- Content hash updates
- Post unlinking
- Translation status changes

Manual invalidation:

```php
$cache = new CacheManager();
$cache->flush_group(CacheManager::CACHE_GROUP_TRANSLATIONS);
```

## Database Schema

### Translations Table

```sql
CREATE TABLE wp_mpz_translations (
    id BIGINT(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    translation_group_id BIGINT(20) UNSIGNED NOT NULL,
    element_type VARCHAR(50) NOT NULL,
    element_id BIGINT(20) UNSIGNED NOT NULL,
    language_code VARCHAR(10) NOT NULL,
    source_element_id BIGINT(20) UNSIGNED DEFAULT NULL,
    translation_status ENUM('original', 'translated', 'needs_update', 'draft'),
    content_hash VARCHAR(64) DEFAULT NULL,
    created_at DATETIME NOT NULL,
    updated_at DATETIME NOT NULL,

    UNIQUE KEY idx_element (element_type, element_id, language_code),
    KEY idx_group (translation_group_id),
    KEY idx_language (language_code),
    KEY idx_source (source_element_id),
    KEY idx_status (translation_status)
);
```

## Translation Workflow

### 1. Create Original Content

```php
// Create post in default language (English)
$post_id = wp_insert_post([
    'post_title' => 'Hello World',
    'post_content' => 'This is the original content.',
    'post_status' => 'publish',
]);

// Create translation group
$manager = new ContentManager();
$group_id = $manager->createTranslationGroup($post_id, 1); // 1 = English
```

### 2. Add Translations

```php
// Create Spanish translation
$spanish_post_id = wp_insert_post([
    'post_title' => 'Hola Mundo',
    'post_content' => 'Este es el contenido original.',
    'post_status' => 'publish',
]);

// Link to translation group
$manager->addToTranslationGroup($group_id, $spanish_post_id, 2); // 2 = Spanish
```

### 3. Detect Changes

```php
// User updates original content
wp_update_post([
    'ID' => $post_id,
    'post_content' => 'Updated content',
]);

// Detect and mark translations
$marked = $manager->markTranslationsAsOutdated($post_id);
// Spanish translation now has status 'needs_update'
```

### 4. Update Translations

```php
// Get posts needing translation
$untranslated = $manager->getUntranslatedPosts($spanish_language_id);

foreach ($untranslated as $source_id) {
    // Translate content (manual or automated)
    $translated_content = translate_content($source_id, 'es');

    // Create or update translation post
    // ...

    // Update translation status
    $translation = $manager->getTranslation($translated_post_id, $spanish_language_id);
    if ($translation) {
        global $wpdb;
        $table_name = $database->get_table_name('translations');
        $wpdb->update(
            $table_name,
            ['translation_status' => 'translated'],
            ['id' => $translation->getId()],
            ['%s'],
            ['%d']
        );
    }
}
```

## Error Handling

```php
try {
    $group_id = $manager->createTranslationGroup($post_id, $language_id);
} catch (\InvalidArgumentException $e) {
    // Invalid post ID or language ID
    error_log('ContentManager error: ' . $e->getMessage());
} catch (\RuntimeException $e) {
    // Post already in a translation group
    error_log('ContentManager error: ' . $e->getMessage());
}
```

## Performance Optimization

### 1. Use Batch Operations

```php
// ✗ Bad: N+1 queries
foreach ($post_ids as $post_id) {
    $translation = $manager->getTranslation($post_id, $language_id);
}

// ✓ Good: Single batch query
$translations = $manager->getMultipleTranslations($post_ids, $language_id);
```

### 2. Warm Cache on Critical Pages

```php
// On language switcher render
$cache = new CacheManager();
$cache->warm_language_cache();

// On translation list page
foreach ($post_ids as $post_id) {
    $cache->warm_translation_cache($post_id, 'post');
}
```

### 3. Use Appropriate Cache TTL

```php
// Default: 1 hour (3600 seconds)
ContentManager::CACHE_TTL

// For frequently changing data, use shorter TTL
$cache->set($key, $value, 300); // 5 minutes
```

## Testing

Run unit tests:

```bash
cd /path/to/plugin
vendor/bin/phpunit tests/unit/Core/ContentManagerTest.php
```

Test coverage:
- ✓ Translation group management
- ✓ Translation retrieval (cached)
- ✓ Content hash generation
- ✓ Change detection
- ✓ Cache invalidation
- ✓ Batch operations
- ✓ Error handling

## Integration

### WordPress Hooks

```php
// Auto-detect changes on post save
add_action('save_post', function($post_id) {
    $manager = new ContentManager();
    $translation = $manager->getTranslationRecord($post_id);

    if ($translation && $translation->isOriginal()) {
        // Check if content changed
        if ($manager->hasContentChanged($post_id, $translation->getContentHash())) {
            // Mark translations as outdated
            $manager->markTranslationsAsOutdated($post_id);
        }
    }
}, 10, 1);

// Auto-link translated posts
add_action('wp_insert_post', function($post_id, $post, $update) {
    if ($update || $post->post_status !== 'publish') {
        return;
    }

    // Check if post should be linked to translation group
    $source_post_id = get_post_meta($post_id, '_mpz_source_post_id', true);
    $target_language_id = get_post_meta($post_id, '_mpz_language_id', true);

    if ($source_post_id && $target_language_id) {
        $manager = new ContentManager();
        $manager->linkTranslations($source_post_id, $post_id, (int) $target_language_id);
    }
}, 10, 3);
```

## Best Practices

### 1. Always Check Return Values

```php
// ✓ Good
$translation = $manager->getTranslation($post_id, $language_id);
if ($translation === null) {
    // Handle missing translation
}

// ✗ Bad
$translation = $manager->getTranslation($post_id, $language_id);
echo $translation->getElementId(); // Fatal error if null
```

### 2. Use Type-Safe Methods

```php
// ✓ Good: Uses Translation entity
$translation = $manager->getTranslation($post_id, $language_id);
$status = $translation->getTranslationStatus();

// ✗ Bad: Direct database access
global $wpdb;
$status = $wpdb->get_var("SELECT translation_status FROM ...");
```

### 3. Invalidate Cache After Manual Updates

```php
// If you update database directly, invalidate cache
global $wpdb;
$wpdb->update($table_name, $data, $where);

// Invalidate cache
$cache = new CacheManager();
$cache->invalidate_translation_cache($post_id, 'post');
```

### 4. Use Appropriate Element Types

```php
// Supported element types
'post'           // Blog posts
'page'           // Pages
'product'        // WooCommerce products
'term'           // Categories, tags
'attachment'     // Media files
'nav_menu_item'  // Menu items
```

## Troubleshooting

### Translation Not Found

```php
// Check if post exists
$post = get_post($post_id);
if (!$post) {
    echo 'Post does not exist';
}

// Check if translation exists
$translation = $manager->getTranslation($post_id, $language_id);
if (!$translation) {
    echo 'Translation not found';
}

// Check if post is in a group
$group_id = $manager->getTranslationGroupId($post_id);
if (!$group_id) {
    echo 'Post not in a translation group';
}
```

### Cache Not Invalidating

```php
// Manually flush cache
$cache = new CacheManager();
$cache->flush_group(CacheManager::CACHE_GROUP_TRANSLATIONS);

// Or flush all caches
$cache->invalidate_all();
```

### Slow Performance

```php
// Enable debug mode to see cache statistics
$cache = new CacheManager();
$cache->enable_debug_mode();

// Get statistics
$stats = $cache->get_stats();
print_r($stats);

// Check hit ratio
if ($stats['totals']['hit_ratio'] < 80) {
    echo 'Cache hit ratio is low, consider optimizing';
}
```

## See Also

- [Translation Entity](../Entities/Translation.php)
- [CacheManager](CacheManager.php)
- [Database Schema](Database.php)
- [LanguageManager](LanguageManager.php)
