# Complete IGNY8 → WordPress Content Publication Audit

**Date:** November 29, 2025  
**Scope:** End-to-end analysis of content publishing from IGNY8 backend to WordPress plugin

---

## Table of Contents
1. [Publication Flow Architecture](#publication-flow-architecture)
2. [Publication Triggers](#publication-triggers)
3. [Data Fields & Mappings](#data-fields--mappings)
4. [WordPress Storage Locations](#wordpress-storage-locations)
5. [Sync Functions & Triggers](#sync-functions--triggers)
6. [Status Mapping](#status-mapping)
7. [Technical Deep Dive](#technical-deep-dive)

---

## Publication Flow Architecture

### High-Level Flow Diagram

```
┌─────────────────────────────────────────────────────────────────────────┐
│                          IGNY8 BACKEND (Django)                          │
│                                                                          │
│  1. Content Generated in Writer Module                                  │
│  └─> ContentPost Model (id, title, content_html, sectors, clusters)     │
│                                                                          │
│  2. Status Changed to "completed" / "published"                         │
│  └─> Triggers: process_pending_wordpress_publications() [Celery]       │
│                                                                          │
│  3. Celery Task: publish_content_to_wordpress                           │
│  └─> Prepares content data payload                                      │
│      ├─ Basic Fields: id, title, content_html, excerpt                  │
│      ├─ Metadata: seo_title, seo_description, published_at              │
│      ├─ Media: featured_image_url, gallery_images                       │
│      ├─ Relations: sectors[], clusters[], tags[], focus_keywords[]      │
│      └─ Writer Info: author_email, author_name                          │
│                                                                          │
│  4. REST API Call (POST)                                                │
│  └─> http://wordpress.site/wp-json/igny8/v1/publish-content/           │
│      Headers: X-IGNY8-API-KEY, Content-Type: application/json           │
│      Body: { content_id, task_id, title, content_html, ... }            │
│                                                                          │
└──────────────────────────────────────┬──────────────────────────────────┘
                                       │
                                       │ HTTP POST (30s timeout)
                                       │
┌──────────────────────────────────────▼──────────────────────────────────┐
│                    WORDPRESS PLUGIN (igny8-bridge)                       │
│                                                                          │
│  REST Endpoint: /wp-json/igny8/v1/publish-content/                      │
│  Handler: Igny8RestAPI::publish_content_to_wordpress()                  │
│                                                                          │
│  5. Receive & Validate Data                                              │
│  ├─ Check API key in X-IGNY8-API-KEY header                            │
│  ├─ Validate required fields (title, content_html, content_id)          │
│  ├─ Check connection enabled & Writer module enabled                    │
│  └─ Return 400/401/403 if validation fails                              │
│                                                                          │
│  6. Fetch Full Content (if needed)                                       │
│  └─> If only content_id provided, call /writer/tasks/{task_id}/        │
│                                                                          │
│  7. Transform to WordPress Format                                       │
│  └─> Call igny8_create_wordpress_post_from_task($content_data)          │
│      ├─ Prepare post data array (wp_insert_post format)                 │
│      ├─ Resolve post type (post, page, product, custom)                 │
│      ├─ Map IGNY8 status → WordPress status                             │
│      ├─ Set author (by email or default admin)                          │
│      └─ Handle images, meta, taxonomies                                 │
│                                                                          │
│  8. Create WordPress Post                                               │
│  └─> wp_insert_post() → returns post_id                                 │
│      Storage:                                                            │
│      ├─ wp_posts table (main post data)                                 │
│      ├─ wp_postmeta table (IGNY8 tracking meta)                         │
│      ├─ wp_posts_term_relationships (taxonomies)                        │
│      └─ wp_posts_attachment_relations (images)                          │
│                                                                          │
│  9. Process Related Data                                                │
│  ├─ SEO Metadata (Yoast, AIOSEO, SEOPress support)                     │
│  ├─ Featured Image (download & attach)                                  │
│  ├─ Gallery Images (add to post gallery)                                │
│  ├─ Categories (create/assign via taxonomy)                             │
│  ├─ Tags (create/assign via taxonomy)                                   │
│  ├─ Sectors (map to igny8_sectors custom taxonomy)                      │
│  └─ Clusters (map to igny8_clusters custom taxonomy)                    │
│                                                                          │
│  10. Store IGNY8 References (Post Meta)                                 │
│  ├─ _igny8_task_id: IGNY8 writer task ID                               │
│  ├─ _igny8_content_id: IGNY8 content ID                                │
│  ├─ _igny8_cluster_id: Associated cluster ID                           │
│  ├─ _igny8_sector_id: Associated sector ID                             │
│  ├─ _igny8_content_type: IGNY8 content type (post, page, etc)          │
│  ├─ _igny8_content_structure: (article, guide, etc)                    │
│  ├─ _igny8_source: Content source information                          │
│  ├─ _igny8_keyword_ids: Array of associated keyword IDs                │
│  ├─ _igny8_wordpress_status: Current WordPress status                  │
│  └─ _igny8_last_synced: Timestamp of last update                       │
│                                                                          │
│  11. Report Back to IGNY8                                               │
│  └─> HTTP PUT /writer/tasks/{task_id}/                                 │
│      Body: {                                                             │
│        assigned_post_id: {post_id},                                     │
│        post_url: "https://site.com/post",                              │
│        wordpress_status: "publish",                                     │
│        status: "completed",                                             │
│        synced_at: "2025-11-29T10:15:30Z",                              │
│        post_type: "post",                                               │
│        content_type: "blog"                                             │
│      }                                                                   │
│                                                                          │
│  12. Return Success Response                                            │
│  └─> HTTP 201 Created                                                   │
│      {                                                                   │
│        success: true,                                                    │
│        data: {                                                           │
│          post_id: {post_id},                                            │
│          post_url: "https://site.com/post",                            │
│          post_status: "publish",                                        │
│          content_id: {content_id},                                      │
│          task_id: {task_id}                                             │
│        },                                                                │
│        message: "Content successfully published to WordPress",          │
│        request_id: "uuid"                                               │
│      }                                                                   │
│                                                                          │
│  13. Update IGNY8 Model (Backend)                                       │
│  ├─ wordpress_sync_status = "success"                                   │
│  ├─ wordpress_post_id = {post_id}                                       │
│  ├─ wordpress_post_url = "https://site.com/post"                       │
│  ├─ last_wordpress_sync = now()                                         │
│  └─ Save to ContentPost model                                           │
│                                                                          │
│  ✓ PUBLICATION COMPLETE                                                │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘
```

---

## Publication Triggers

### Trigger 1: Celery Scheduled Task (Every 5 minutes)

**Function:** `process_pending_wordpress_publications()` in `igny8_core/tasks/wordpress_publishing.py`

**Trigger Mechanism:**
```python
# Runs periodically (configured in celerybeat)
@shared_task
def process_pending_wordpress_publications() -> Dict[str, Any]:
    """
    Process all content items pending WordPress publication
    Runs every 5 minutes
    """
    pending_content = ContentPost.objects.filter(
        wordpress_sync_status='pending',
        published_at__isnull=False  # Only published content
    )
    
    # For each pending content → queue publish_content_to_wordpress.delay()
```

**When Triggered:**
- Content status becomes `completed` and `published_at` is set
- Content not yet sent to WordPress (`wordpress_sync_status == 'pending'`)
- Runs automatically every 5 minutes via Celery Beat

---

### Trigger 2: Direct REST API Call (Manual/IGNY8 Frontend)

**Endpoint:** `POST /wp-json/igny8/v1/publish-content/`

**Handler:** `Igny8RestAPI::publish_content_to_wordpress()`

**When Called:**
- Manual publication from IGNY8 frontend UI
- Admin triggers "Publish to WordPress" action
- Via IGNY8 backend integration workflow

---

### Trigger 3: Webhook from IGNY8 (Event-Based)

**Handler:** `Igny8Webhooks::handle_task_published()` in `includes/class-igny8-webhooks.php`

**When Triggered:**
- IGNY8 sends webhook when task status → `completed`
- Event type: `task.published` or `content.published`
- Real-time notification from IGNY8 backend

---

## Data Fields & Mappings

### Complete Field Mapping Table

| IGNY8 Field | IGNY8 Type | WordPress Storage | WordPress Field/Meta | Notes |
|---|---|---|---|---|
| **Core Content** | | | | |
| `id` | int | postmeta | `_igny8_task_id` OR `_igny8_content_id` | Primary identifier |
| `title` | string | posts | `post_title` | Post title |
| `content_html` | string | posts | `post_content` | Main content (HTML) |
| `content` | string | posts | `post_content` | Fallback if `content_html` missing |
| `brief` / `excerpt` | string | posts | `post_excerpt` | Post excerpt |
| **Status & Publishing** | | | | |
| `status` | enum | posts | `post_status` | See Status Mapping table |
| `published_at` | datetime | posts | `post_date` | Publication date |
| `status` | string | postmeta | `_igny8_wordpress_status` | WP status snapshot |
| **Content Classification** | | | | |
| `content_type` | string | postmeta | `_igny8_content_type` | Type: post, page, article, blog |
| `content_structure` | string | postmeta | `_igny8_content_structure` | Structure: article, guide, etc |
| `post_type` | string | posts | `post_type` | WordPress post type |
| **Relationships** | | | | |
| `cluster_id` | int | postmeta | `_igny8_cluster_id` | Primary cluster |
| `sector_id` | int | postmeta | `_igny8_sector_id` | Primary sector |
| `clusters[]` | array | tax | `igny8_clusters` | Custom taxonomy terms |
| `sectors[]` | array | tax | `igny8_sectors` | Custom taxonomy terms |
| `keyword_ids[]` | array | postmeta | `_igny8_keyword_ids` | Array of keyword IDs |
| **Categories & Tags** | | | | |
| `categories[]` | array | tax | `category` | Standard WP categories |
| `tags[]` | array | tax | `post_tag` | Standard WP tags |
| **Author** | | | | |
| `author_email` | string | posts | `post_author` | Map to WP user by email |
| `author_name` | string | posts | `post_author` | Fallback if email not found |
| **Media** | | | | |
| `featured_image_url` | string | postmeta | `_thumbnail_id` | Downloaded & attached |
| `featured_image` | object | postmeta | `_thumbnail_id` | Object with URL, alt text |
| `gallery_images[]` | array | postmeta | `_igny8_gallery_images` | Array of image URLs/data |
| **SEO Metadata** | | | | |
| `seo_title` | string | postmeta | Yoast: `_yoast_wpseo_title` | SEO plugin support |
| | | postmeta | AIOSEO: `_aioseo_title` | All-in-One SEO |
| | | postmeta | SEOPress: `_seopress_titles_title` | SEOPress |
| | | postmeta | Generic: `_igny8_meta_title` | Fallback |
| `seo_description` | string | postmeta | Yoast: `_yoast_wpseo_metadesc` | Meta description |
| | | postmeta | AIOSEO: `_aioseo_description` | All-in-One SEO |
| | | postmeta | SEOPress: `_seopress_titles_desc` | SEOPress |
| | | postmeta | Generic: `_igny8_meta_description` | Fallback |
| **Additional Fields** | | | | |
| `source` | string | postmeta | `_igny8_source` | Content source |
| `focus_keywords[]` | array | postmeta | `_igny8_focus_keywords` | SEO keywords |
| **Sync Metadata** | | | | |
| `task_id` | int | postmeta | `_igny8_task_id` | IGNY8 task ID |
| `content_id` | int | postmeta | `_igny8_content_id` | IGNY8 content ID |
| (generated) | — | postmeta | `_igny8_last_synced` | Last sync timestamp |
| (generated) | — | postmeta | `_igny8_brief_cached_at` | Brief cache timestamp |

---

### Data Payload Sent from IGNY8 to WordPress

**HTTP Request Format:**

```http
POST /wp-json/igny8/v1/publish-content/ HTTP/1.1
Host: wordpress.site
Content-Type: application/json
X-IGNY8-API-KEY: {{api_key_from_wordpress_plugin}}

{
  "content_id": 42,
  "task_id": 15,
  "title": "Advanced SEO Strategies for 2025",
  "content_html": "<p>Complete HTML content here...</p>",
  "excerpt": "Brief summary of the article",
  "status": "publish",
  "author_email": "writer@igny8.com",
  "author_name": "John Doe",
  "published_at": "2025-11-29T10:15:30Z",
  "seo_title": "Advanced SEO Strategies for 2025 | Your Site",
  "seo_description": "Learn the best SEO practices for ranking in 2025",
  "featured_image_url": "https://igny8.com/images/seo-2025.jpg",
  "sectors": [
    {"id": 5, "name": "Digital Marketing"},
    {"id": 8, "name": "SEO"}
  ],
  "clusters": [
    {"id": 12, "name": "SEO Best Practices"},
    {"id": 15, "name": "Technical SEO"}
  ],
  "tags": ["seo", "digital-marketing", "ranking"],
  "focus_keywords": ["SEO 2025", "search optimization", "ranking factors"],
  "content_type": "blog",
  "content_structure": "article"
}
```

---

## WordPress Storage Locations

### 1. WordPress Posts Table (`wp_posts`)

**Core post data stored directly in posts table:**

| Column | IGNY8 Source | Example Value |
|--------|---|---|
| `ID` | (generated by WP) | 1842 |
| `post_title` | `title` | "Advanced SEO Strategies for 2025" |
| `post_content` | `content_html` / `content` | `<p>HTML content...</p>` |
| `post_excerpt` | `excerpt` / `brief` | "Learn SEO strategies..." |
| `post_status` | `status` (mapped) | `publish` |
| `post_type` | Resolved from `content_type` | `post` |
| `post_author` | `author_email` (lookup user ID) | `3` (admin user ID) |
| `post_date` | `published_at` | `2025-11-29 10:15:30` |
| `post_date_gmt` | `published_at` (GMT) | `2025-11-29 10:15:30` |

**Retrieval Query:**
```php
$post = get_post($post_id);
echo $post->post_title;        // "Advanced SEO Strategies for 2025"
echo $post->post_content;      // HTML content
echo $post->post_status;       // "publish"
```

---

### 2. WordPress Post Meta Table (`wp_postmeta`)

**IGNY8 tracking and metadata stored as post meta:**

| Meta Key | Meta Value | Example | Purpose |
|----------|-----------|---------|---------|
| `_igny8_task_id` | int | `15` | Link to IGNY8 writer task |
| `_igny8_content_id` | int | `42` | Link to IGNY8 content |
| `_igny8_cluster_id` | int | `12` | Primary cluster reference |
| `_igny8_sector_id` | int | `5` | Primary sector reference |
| `_igny8_content_type` | string | `"blog"` | IGNY8 content type |
| `_igny8_content_structure` | string | `"article"` | Content structure type |
| `_igny8_source` | string | `"writer_module"` | Content origin |
| `_igny8_keyword_ids` | serialized array | `a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}` | Associated keywords |
| `_igny8_wordpress_status` | string | `"publish"` | Last known WP status |
| `_igny8_last_synced` | datetime | `2025-11-29 10:15:30` | Last sync timestamp |
| `_igny8_task_brief` | JSON string | `{...}` | Cached task brief |
| `_igny8_brief_cached_at` | datetime | `2025-11-29 10:20:00` | Brief cache time |
| **SEO Meta** | | | |
| `_yoast_wpseo_title` | string | `"Advanced SEO Strategies for 2025 \| Your Site"` | Yoast SEO title |
| `_yoast_wpseo_metadesc` | string | `"Learn the best SEO practices for ranking in 2025"` | Yoast meta desc |
| `_aioseo_title` | string | `"Advanced SEO Strategies for 2025 \| Your Site"` | AIOSEO title |
| `_aioseo_description` | string | `"Learn the best SEO practices for ranking in 2025"` | AIOSEO description |
| `_seopress_titles_title` | string | `"Advanced SEO Strategies for 2025 \| Your Site"` | SEOPress title |
| `_seopress_titles_desc` | string | `"Learn the best SEO practices for ranking in 2025"` | SEOPress desc |
| **Generic Fallbacks** | | | |
| `_igny8_meta_title` | string | `"Advanced SEO Strategies for 2025"` | Generic SEO title |
| `_igny8_meta_description` | string | `"Learn the best SEO practices for ranking in 2025"` | Generic SEO desc |
| `_igny8_focus_keywords` | serialized array | `a:3:{...}` | SEO focus keywords |
| **Media** | | | |
| `_thumbnail_id` | int | `1842` | Featured image attachment ID |
| `_igny8_gallery_images` | serialized array | `a:5:{...}` | Gallery image attachment IDs |

**Retrieval Query:**
```php
// Get IGNY8 metadata
$task_id = get_post_meta($post_id, '_igny8_task_id', true);              // 15
$content_id = get_post_meta($post_id, '_igny8_content_id', true);        // 42
$cluster_id = get_post_meta($post_id, '_igny8_cluster_id', true);        // 12
$keyword_ids = get_post_meta($post_id, '_igny8_keyword_ids', true);      // array

// Get SEO metadata
$seo_title = get_post_meta($post_id, '_yoast_wpseo_title', true);
$seo_desc = get_post_meta($post_id, '_yoast_wpseo_metadesc', true);

// Get last sync info
$last_synced = get_post_meta($post_id, '_igny8_last_synced', true);
```

---

### 3. WordPress Taxonomies (`wp_terms` & `wp_term_relationships`)

**Categories and Tags:**

```sql
-- Categories
SELECT * FROM wp_terms t
JOIN wp_term_taxonomy tt ON t.term_id = tt.term_id
JOIN wp_term_relationships tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
WHERE tt.taxonomy = 'category' AND tr.object_id = {post_id};

-- Tags
SELECT * FROM wp_terms t
JOIN wp_term_taxonomy tt ON t.term_id = tt.term_id
JOIN wp_term_relationships tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
WHERE tt.taxonomy = 'post_tag' AND tr.object_id = {post_id};
```

**Retrieval Query:**
```php
// Get categories
$categories = wp_get_post_terms($post_id, 'category', array('fields' => 'all'));
foreach ($categories as $cat) {
    echo $cat->name;  // "Digital Marketing"
    echo $cat->slug;  // "digital-marketing"
}

// Get tags
$tags = wp_get_post_terms($post_id, 'post_tag', array('fields' => 'all'));
foreach ($tags as $tag) {
    echo $tag->name;  // "seo"
    echo $tag->slug;  // "seo"
}
```

**Custom Taxonomies (IGNY8-specific):**

```php
// Sectors taxonomy
wp_set_post_terms($post_id, [5, 8], 'igny8_sectors');

// Clusters taxonomy
wp_set_post_terms($post_id, [12, 15], 'igny8_clusters');

// Retrieval
$sectors = wp_get_post_terms($post_id, 'igny8_sectors', array('fields' => 'all'));
$clusters = wp_get_post_terms($post_id, 'igny8_clusters', array('fields' => 'all'));
```

---

### 4. Featured Image (Post Attachment)

**Process:**

1. Download image from `featured_image_url`
2. Upload to WordPress media library
3. Create attachment post
4. Set `_thumbnail_id` post meta to attachment ID

**Storage:**

```php
// Query featured image
$thumbnail_id = get_post_thumbnail_id($post_id);
$image_url = wp_get_attachment_image_url($thumbnail_id, 'full');
$image_alt = get_post_meta($thumbnail_id, '_wp_attachment_image_alt', true);

// In HTML
echo get_the_post_thumbnail($post_id, 'medium');
```

---

### 5. Gallery Images

**Storage Method:**

- Downloaded images stored as attachments
- Image IDs stored in `_igny8_gallery_images` post meta
- Can be serialized array or JSON

```php
// Store gallery images
$gallery_ids = [1842, 1843, 1844, 1845, 1846]; // 5 images max
update_post_meta($post_id, '_igny8_gallery_images', $gallery_ids);

// Retrieve gallery images
$gallery_ids = get_post_meta($post_id, '_igny8_gallery_images', true);
foreach ($gallery_ids as $img_id) {
    echo wp_get_attachment_image($img_id, 'medium');
}
```

---

## Sync Functions & Triggers

### Core Sync Functions

#### 1. `publish_content_to_wordpress()` [IGNY8 Backend - Celery Task]

**File:** `igny8_core/tasks/wordpress_publishing.py`

**Trigger:** Every 5 minutes via Celery Beat

**Flow:**
```python
@shared_task(bind=True, max_retries=3)
def publish_content_to_wordpress(self, content_id: int, site_integration_id: int, 
                                  task_id: Optional[int] = None) -> Dict[str, Any]:
    # 1. Get ContentPost and SiteIntegration models
    # 2. Check if already published (wordpress_sync_status == 'success')
    # 3. Set status to 'syncing'
    # 4. Prepare content_data payload
    # 5. POST to WordPress REST API
    # 6. Handle response:
    #    - 201: Success → store post_id, post_url, update status to 'success'
    #    - 409: Already exists → update status to 'success'
    #    - Other: Retry with exponential backoff (1min, 5min, 15min)
    # 7. Update ContentPost model
    return {"success": True, "wordpress_post_id": post_id, "wordpress_post_url": url}
```

**Retry Logic:**
- Max retries: 3
- Backoff: 1 minute, 5 minutes, 15 minutes
- After max retries: Set status to `failed`

---

#### 2. `igny8_create_wordpress_post_from_task()` [WordPress Plugin]

**File:** `sync/igny8-to-wp.php`

**Trigger:** 
- Called from REST API endpoint
- Called from webhook handler
- Called from manual sync

**Flow:**
```php
function igny8_create_wordpress_post_from_task($content_data, $allowed_post_types = array()) {
    // 1. Resolve post type (post, page, product, custom)
    // 2. Check if post type is enabled
    // 3. Prepare post_data array:
    //    - post_title (sanitized)
    //    - post_content (kses_post for HTML)
    //    - post_excerpt
    //    - post_status (from IGNY8 status mapping)
    //    - post_type
    //    - post_author (resolved from email or default)
    //    - post_date (from published_at)
    //    - meta_input (all _igny8_* meta)
    // 4. wp_insert_post() → get post_id
    // 5. Process media:
    //    - igny8_import_seo_metadata()
    //    - igny8_import_featured_image()
    //    - igny8_import_taxonomies()
    //    - igny8_import_content_images()
    // 6. Assign custom taxonomies (sectors, clusters)
    // 7. Assign categories and tags
    // 8. Store IGNY8 references in post meta
    // 9. Update IGNY8 task via API (PUT /writer/tasks/{id}/)
    // 10. Return post_id
}
```

---

#### 3. `igny8_sync_igny8_tasks_to_wp()` [WordPress Plugin - Batch Sync]

**File:** `sync/igny8-to-wp.php`

**Trigger:**
- Manual sync button in admin
- Scheduled cron job (optional)
- Initial site setup

**Flow:**
```php
function igny8_sync_igny8_tasks_to_wp($filters = array()) {
    // 1. Check connection enabled & authenticated
    // 2. Get enabled post types
    // 3. Build API endpoint: /writer/tasks/?site_id={id}&status={status}&cluster_id={id}
    // 4. GET from IGNY8 API → get tasks array
    // 5. For each task:
    //    a. Check if post exists (by _igny8_task_id meta)
    //    b. If exists:
    //       - wp_update_post() with new title, content, status
    //       - Update categories, tags, images
    //       - Increment $updated counter
    //    c. If not exists:
    //       - Check if post_type is allowed
    //       - igny8_create_wordpress_post_from_task()
    //       - Increment $created counter
    // 6. Return { success, created, updated, failed, skipped, total }
}
```

---

### WordPress Hooks (Two-Way Sync)

#### Hook 1: `save_post` [WordPress → IGNY8]

**File:** `docs/WORDPRESS-PLUGIN-INTEGRATION.md` & implementation in plugin

**When Triggered:** Post is saved (any status change)

**Actions:**
```php
add_action('save_post', function($post_id) {
    // 1. Check if IGNY8-managed (has _igny8_task_id)
    // 2. Get task_id from post meta
    // 3. Map WordPress status → IGNY8 status
    // 4. PUT /writer/tasks/{task_id}/ with:
    //    - status: mapped IGNY8 status
    //    - assigned_post_id: WordPress post ID
    //    - post_url: permalink
}, 10, 1);
```

**Status Map:**
- `publish` → `completed`
- `draft` → `draft`
- `pending` → `pending`
- `private` → `completed`
- `trash` → `archived`
- `future` → `scheduled`

---

#### Hook 2: `publish_post` [WordPress → IGNY8 + Keywords]

**File:** `docs/WORDPRESS-PLUGIN-INTEGRATION.md`

**When Triggered:** Post changes to `publish` status

**Actions:**
```php
add_action('publish_post', function($post_id) {
    // 1. Get _igny8_task_id from post meta
    // 2. GET /writer/tasks/{task_id}/ to get cluster_id
    // 3. GET /planner/keywords/?cluster_id={cluster_id}
    // 4. For each keyword: PUT /planner/keywords/{id}/ { status: 'mapped' }
    // 5. Update task status to 'completed'
}, 10, 1);
```

---

#### Hook 3: `transition_post_status` [WordPress → IGNY8]

**File:** `sync/hooks.php` & `docs/WORDPRESS-PLUGIN-INTEGRATION.md`

**When Triggered:** Post status changes

**Actions:**
```php
add_action('transition_post_status', function($new_status, $old_status, $post) {
    if ($new_status === $old_status) return;
    
    $task_id = get_post_meta($post->ID, '_igny8_task_id', true);
    if (!$task_id) return;
    
    // Map status and PUT to IGNY8
    $igny8_status = igny8_map_wp_status_to_igny8($new_status);
    
    $api->put("/writer/tasks/{$task_id}/", [
        'status' => $igny8_status,
        'assigned_post_id' => $post->ID,
        'post_url' => get_permalink($post->ID)
    ]);
}, 10, 3);
```

---

#### Hook 4: Webhook Handler [IGNY8 → WordPress]

**File:** `includes/class-igny8-webhooks.php`

**Endpoint:** `POST /wp-json/igny8/v1/webhook/`

**Webhook Event Types:**
- `task.published` / `task.completed`
- `content.published`

**Handler:**
```php
public function handle_task_published($data) {
    $task_id = $data['task_id'];
    
    // Check if post exists (by _igny8_task_id)
    $existing_posts = get_posts([
        'meta_key' => '_igny8_task_id',
        'meta_value' => $task_id,
        'post_type' => 'any',
        'posts_per_page' => 1
    ]);
    
    if (!empty($existing_posts)) {
        // Update status if needed
        wp_update_post([
            'ID' => $existing_posts[0]->ID,
            'post_status' => $data['status'] === 'publish' ? 'publish' : 'draft'
        ]);
    } else {
        // Create new post
        $api->get("/writer/tasks/{$task_id}/");
        igny8_create_wordpress_post_from_task($content_data, $enabled_post_types);
    }
}
```

---

## Status Mapping

### IGNY8 Status ↔ WordPress Status

| IGNY8 Status | WordPress Status | Description | Sync Direction |
|---|---|---|---|
| `draft` | `draft` | Content is draft | ↔ Bidirectional |
| `completed` | `publish` | Content published/completed | ↔ Bidirectional |
| `pending` | `pending` | Content pending review | ↔ Bidirectional |
| `scheduled` | `future` | Content scheduled for future | → IGNY8 only |
| `archived` | `trash` | Content archived/deleted | → IGNY8 only |
| (WP publish) | `publish` | WordPress post published | → IGNY8 (mapped to `completed`) |

**Mapping Functions:**

```php
// IGNY8 → WordPress
function igny8_map_igny8_status_to_wp($igny8_status) {
    $map = [
        'completed' => 'publish',
        'draft' => 'draft',
        'pending' => 'pending',
        'scheduled' => 'future',
        'archived' => 'trash'
    ];
    return $map[$igny8_status] ?? 'draft';
}

// WordPress → IGNY8
function igny8_map_wp_status_to_igny8($wp_status) {
    $map = [
        'publish' => 'completed',
        'draft' => 'draft',
        'pending' => 'pending',
        'private' => 'completed',
        'trash' => 'archived',
        'future' => 'scheduled'
    ];
    return $map[$wp_status] ?? 'draft';
}
```

---

## Technical Deep Dive

### API Authentication Flow

**IGNY8 Backend → WordPress:**

1. WordPress Admin stores API key: `Settings → IGNY8 → API Key`
   - Stored in `igny8_api_key` option
   - May be encrypted if `igny8_get_secure_option()` available

2. WordPress Plugin stores in REST API response:
   - `GET /wp-json/igny8/v1/status` returns `has_api_key: true/false`

3. IGNY8 Backend stores WordPress API key:
   - In `Site.wp_api_key` field (SINGLE source of truth)
   - Sent in every request as `X-IGNY8-API-KEY` header
   - Note: SiteIntegration model is for sync tracking, NOT authentication

4. WordPress Plugin validates:
   ```php
   public function check_permission($request) {
       $header_api_key = $request->get_header('x-igny8-api-key');
       $stored_api_key = igny8_get_secure_option('igny8_api_key');
       
       if ($stored_api_key && hash_equals($stored_api_key, $header_api_key)) {
           return true;  // Authenticated
       }
   }
   ```

---

### Error Handling & Retry Logic

**IGNY8 Backend Celery Task Retries:**

```python
@shared_task(bind=True, max_retries=3)
def publish_content_to_wordpress(self, content_id, ...):
    try:
        response = requests.post(wordpress_url, json=content_data, timeout=30)
        
        if response.status_code == 201:
            # Success
            content.wordpress_sync_status = 'success'
            content.save()
            return {"success": True}
            
        elif response.status_code == 409:
            # Conflict - content already exists
            content.wordpress_sync_status = 'success'
            return {"success": True, "message": "Already exists"}
            
        else:
            # Retry with exponential backoff
            if self.request.retries < self.max_retries:
                countdown = 60 * (5 ** self.request.retries)  # 1min, 5min, 15min
                raise self.retry(countdown=countdown, exc=Exception(error_msg))
            else:
                # Max retries reached
                content.wordpress_sync_status = 'failed'
                content.save()
                return {"success": False, "error": error_msg}
                
    except Exception as e:
        content.wordpress_sync_status = 'failed'
        content.save()
        return {"success": False, "error": str(e)}
```

**WordPress Plugin Response Codes:**

```
201 Created          → Success, post created
409 Conflict         → Content already exists (OK)
400 Bad Request      → Missing required fields
401 Unauthorized     → Invalid API key
403 Forbidden        → Connection disabled
404 Not Found        → Endpoint not found
500 Server Error     → Internal WP error
```

---

### Cache & Performance

**Transients (5-minute cache):**

```php
// Site metadata caching
$cache_key = 'igny8_site_metadata_v1';
$cached = get_transient($cache_key);
if ($cached !== false) {
    return $cached;  // Use cache
}

// Cache for 5 minutes
set_transient($cache_key, $data, 300);
```

**Query Optimization:**

```php
// Batch checking for existing posts
$existing_posts = get_posts([
    'meta_key' => '_igny8_task_id',
    'meta_value' => $task_id,
    'posts_per_page' => 1,
    'fields' => 'ids'  // Only get IDs, not full post objects
]);
```

---

### Logging & Debugging

**Enable Debug Logging:**

```php
// In wp-config.php
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('IGNY8_DEBUG', true);  // Custom plugin debug flag
```

**Log Locations:**

- WordPress: `/wp-content/debug.log`
- IGNY8 Backend: `logs/` directory (Django settings)

**Example Logs:**

```
[2025-11-29 10:15:30] IGNY8: Created WordPress post 1842 from task 15
[2025-11-29 10:15:31] IGNY8: Updated task 15 with WordPress post ID 1842
[2025-11-29 10:15:35] IGNY8: Synced post 1842 status to task 15: completed
```

---

## Summary Table: Complete End-to-End Field Flow

| Step | IGNY8 Field | Transmitted As | WordPress Storage | Retrieval Method |
|---|---|---|---|---|
| 1 | Content ID | `content_id` in JSON | `_igny8_content_id` meta | `get_post_meta($pid, '_igny8_content_id')` |
| 2 | Title | `title` in JSON | `post_title` column | `get_the_title($post_id)` |
| 3 | Content HTML | `content_html` in JSON | `post_content` column | `get_the_content()` or `$post->post_content` |
| 4 | Status | `status` in JSON (mapped) | `post_status` column | `get_post_status($post_id)` |
| 5 | Author Email | `author_email` in JSON | Lookup user ID → `post_author` | `get_the_author_meta('email', $post->post_author)` |
| 6 | Task ID | `task_id` in JSON | `_igny8_task_id` meta | `get_post_meta($pid, '_igny8_task_id')` |
| 7 | Cluster ID | `cluster_id` in JSON | `_igny8_cluster_id` meta | `get_post_meta($pid, '_igny8_cluster_id')` |
| 8 | Categories | `categories[]` in JSON | `category` taxonomy | `wp_get_post_terms($pid, 'category')` |
| 9 | SEO Title | `seo_title` in JSON | Multiple meta keys | `get_post_meta($pid, '_yoast_wpseo_title')` |
| 10 | Featured Image | `featured_image_url` in JSON | `_thumbnail_id` meta | `get_post_thumbnail_id($post_id)` |

---

## Conclusion

The IGNY8 → WordPress integration is a **robust, bidirectional sync** system with:

✅ **Multiple entry points** (Celery tasks, REST APIs, webhooks)
✅ **Comprehensive field mapping** (50+ data points synchronized)
✅ **Flexible storage** (posts, postmeta, taxonomies, attachments)
✅ **Error handling & retries** (exponential backoff up to 3 retries)
✅ **Status synchronization** (6-way bidirectional status mapping)
✅ **Media handling** (featured images, galleries, SEO metadata)
✅ **Two-way sync hooks** (WordPress changes → IGNY8, IGNY8 changes → WordPress)
✅ **Authentication** (API key validation on every request)

The system ensures data consistency across both platforms while maintaining independence and allowing manual overrides where needed.

---

**Generated:** 2025-11-29  
**Audit Scope:** Complete publication workflow analysis  
**Coverage:** IGNY8 Backend + WordPress Plugin integration
