# 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": "
Complete HTML content here...
", "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` | `HTML content...
` | | `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