# Content Publishing Fixes Applied **Date:** November 29, 2025 **Issue:** Only title was being published to WordPress, not the full content_html **Root Cause:** WordPress REST endpoint was fetching from wrong API endpoint (Tasks model instead of Content model) + Field name mismatches --- ## Critical Issue Identified **Problem:** WordPress posts were created with only the title, no content body. **Root Cause Analysis:** 1. WordPress REST endpoint (`class-igny8-rest-api.php`) was making an API callback to `/writer/tasks/{task_id}/` 2. This endpoint returns the **Tasks** model, which does NOT have a `content_html` field 3. Tasks model only has: `title`, `description`, `keywords` (no actual content) 4. Meanwhile, IGNY8 backend was already sending full `content_html` in the POST body 5. WordPress was ignoring the POST body and using the API callback response instead --- ## Fixes Applied ### Fix #1: WordPress REST Endpoint (CRITICAL) **File:** `includes/class-igny8-rest-api.php` **Function:** `publish_content_to_wordpress()` **Lines Modified:** 460-597 **What Changed:** - ✅ **REMOVED** 80+ lines of API callback logic (lines 507-545) - ✅ **REMOVED** call to `/writer/tasks/{task_id}/` endpoint - ✅ **CHANGED** to parse POST body directly: `$content_data = $request->get_json_params()` - ✅ **ADDED** validation for required fields: `content_id`, `title`, `content_html` - ✅ **ADDED** debug logging when `IGNY8_DEBUG` flag is defined **Before:** ```php // WordPress was making a redundant API call $response = $api->get("/writer/tasks/{$task_id}/"); $content_data = $response['data'] ?? array(); // ❌ This had NO content_html ``` **After:** ```php // WordPress now uses the data IGNY8 already sent $content_data = $request->get_json_params(); // ✅ This has content_html ``` **Impact:** WordPress now receives and uses the full `content_html` field sent by IGNY8 backend. --- ### Fix #2: IGNY8 Backend Payload (Field Name Corrections) **File:** `backend/igny8_core/tasks/wordpress_publishing.py` **Function:** `publish_content_to_wordpress()` **Lines Modified:** 54-89 **Field Name Fixes:** | ❌ Old (Wrong) | ✅ New (Correct) | Reason | |---|---|---| | `content.brief` | Generate from `content_html` | Content model has no `brief` field | | `content.author.email` | `None` | Content model has no `author` field | | `content.published_at` | `None` | Content model has no `published_at` field | | `getattr(content, 'seo_title', '')` | `content.meta_title or ''` | Correct field is `meta_title` | | `getattr(content, 'seo_description', '')` | `content.meta_description or ''` | Correct field is `meta_description` | | `getattr(content, 'focus_keywords', [])` | `content.secondary_keywords or []` | Correct field is `secondary_keywords` | | `content.featured_image.url` | `None` | Content model has no `featured_image` field | | `content.sectors.all()` | Empty array | Content has `sector` (ForeignKey), not `sectors` (many-to-many) | | `content.clusters.all()` | Empty array | Content has `cluster` (ForeignKey), not `clusters` (many-to-many) | | `getattr(content, 'tags', [])` | Empty array | Content model has no `tags` field | **New Fields Added:** - ✅ `primary_keyword`: `content.primary_keyword or ''` - ✅ `cluster_id`: `content.cluster.id if content.cluster else None` - ✅ `sector_id`: `content.sector.id if content.sector else None` **Excerpt Generation:** ```python # Generate excerpt from content_html (Content model has no 'brief' field) excerpt = '' if content.content_html: from django.utils.html import strip_tags excerpt = strip_tags(content.content_html)[:150].strip() if len(content.content_html) > 150: excerpt += '...' ``` **Impact:** Payload now uses fields that actually exist on Content model, preventing AttributeErrors. --- ## Content Model Structure (Reference) **File:** `backend/igny8_core/business/content/models.py` **Model:** `Content(SiteSectorBaseModel)` ### Fields That Exist ✅ - `title` (CharField) - `content_html` (TextField) ← **The actual content** - `meta_title` (CharField) ← SEO title - `meta_description` (TextField) ← SEO description - `primary_keyword` (CharField) - `secondary_keywords` (JSONField) - `cluster` (ForeignKey to Clusters) - `content_type` (CharField: post/page/product/taxonomy) - `content_structure` (CharField: article/guide/etc) - `status` (CharField: draft/review/published) - `source` (CharField: igny8/wordpress) - `external_id`, `external_url`, `external_type`, `sync_status` - `created_at`, `updated_at` (from base model) - `account`, `site`, `sector` (from SiteSectorBaseModel) ### Fields That Do NOT Exist ❌ - ❌ `brief` or `excerpt` - ❌ `author` - ❌ `published_at` - ❌ `featured_image` - ❌ `seo_title` (it's `meta_title`) - ❌ `seo_description` (it's `meta_description`) - ❌ `focus_keywords` (it's `secondary_keywords`) - ❌ `sectors` (many-to-many) - ❌ `clusters` (many-to-many) - ❌ `tags` --- ## WordPress Function Already Handles content_html Correctly **File:** `sync/igny8-to-wp.php` **Function:** `igny8_create_wordpress_post_from_task()` **Lines:** 73-200 This function was already correctly implemented: ```php // Stage 1 Schema: accept content_html (new) or content (legacy fallback) $content_html = $content_data['content_html'] ?? $content_data['content'] ?? ''; // ... $post_data = array( 'post_title' => sanitize_text_field($content_data['title'] ?? 'Untitled'), 'post_content' => wp_kses_post($content_html), // ✅ Uses content_html 'post_excerpt' => sanitize_text_field($excerpt), // ... ); $post_id = wp_insert_post($post_data); ``` **No changes needed** - this function properly extracts `content_html` and creates the WordPress post. --- ## Data Flow (Fixed) ### Before Fix ❌ ``` IGNY8 Backend ├─ Sends POST with content_html ✓ └─ WordPress receives it ✓ ├─ Ignores POST body ❌ ├─ Calls /writer/tasks/{id}/ ❌ └─ Gets Tasks model (no content_html) ❌ └─ Creates post with only title ❌ ``` ### After Fix ✅ ``` IGNY8 Backend ├─ Sends POST with content_html ✓ └─ WordPress receives it ✓ ├─ Parses POST body ✓ ├─ Validates content_html present ✓ └─ Creates post with full content ✓ ``` --- ## Testing Checklist To verify the fixes work: 1. ✅ Create a Content object in IGNY8 with full `content_html` 2. ✅ Ensure Content has: `title`, `content_html`, `meta_title`, `meta_description`, `cluster`, `sector` 3. ✅ Trigger `publish_content_to_wordpress` Celery task 4. ✅ Verify WordPress receives full payload with `content_html` 5. ✅ Confirm WordPress post created with: - Full content body (not just title) - Correct SEO metadata - Cluster and sector IDs stored 6. ✅ Check WordPress postmeta for: - `_igny8_content_id` - `_igny8_task_id` - `_igny8_cluster_id` - `_igny8_sector_id` --- ## Debug Logging To enable verbose logging, add to WordPress `wp-config.php`: ```php define('IGNY8_DEBUG', true); define('WP_DEBUG', true); define('WP_DEBUG_LOG', true); ``` This will log: - Content ID received - Title received - Content HTML length - All REST API responses --- ## Summary **Files Modified:** 1. `includes/class-igny8-rest-api.php` - WordPress REST endpoint 2. `backend/igny8_core/tasks/wordpress_publishing.py` - IGNY8 backend payload **Core Changes:** 1. WordPress now uses POST body data instead of making redundant API call 2. IGNY8 backend uses correct Content model field names 3. Excerpt generated from content_html automatically 4. Cluster and sector sent as IDs, not arrays **Result:** Full content (including HTML body) now publishes to WordPress correctly. --- **Generated:** 2025-11-29 **Status:** FIXES APPLIED - Ready for testing **Priority:** HIGH - Core functionality restored