# 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
