Files
igny8/plugins/wordpress/source/igny8-wp-bridge/docs/COMPLETE-PUBLICATION-AUDIT.md
2026-01-13 09:23:54 +00:00

36 KiB

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
  2. Publication Triggers
  3. Data Fields & Mappings
  4. WordPress Storage Locations
  5. Sync Functions & Triggers
  6. Status Mapping
  7. 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:

# 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:

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:

$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:

// 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:

-- 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:

// 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):

// 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:

// 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');

Storage Method:

  • Downloaded images stored as attachments
  • Image IDs stored in _igny8_gallery_images post meta
  • Can be serialized array or JSON
// 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:

@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:

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:

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:

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:

  • publishcompleted
  • draftdraft
  • pendingpending
  • privatecompleted
  • trasharchived
  • futurescheduled

Hook 2: publish_post [WordPress → IGNY8 + Keywords]

File: docs/WORDPRESS-PLUGIN-INTEGRATION.md

When Triggered: Post changes to publish status

Actions:

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:

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:

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:

// 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:

    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:

@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):

// 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:

// 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:

// 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