# WordPress Publishing Complete Field Mapping **Last Updated**: 2025-12-01 **Purpose**: Complete documentation of IGNY8 β†’ WordPress publishing workflow with all field mappings --- ## πŸ”„ Publishing Workflow Overview ``` Frontend (Review.tsx) ↓ POST /v1/publisher/publish/ Backend (PublisherViewSet) ↓ PublisherService.publish() WordPressAdapter._publish_via_api_key() ↓ POST {site_url}/wp-json/igny8/v1/publish WordPress Plugin (class-igny8-rest-api.php) ↓ publish_content_to_wordpress() WordPress Plugin (igny8-to-wp.php) ↓ igny8_create_wordpress_post_from_task() WordPress Core ↓ wp_insert_post() + post_meta WordPress Database (wp_posts, wp_postmeta, wp_term_relationships) ``` --- ## πŸ“Š Complete Field Mapping Table | # | IGNY8 Field | DB Column | Backend Publisher | WordPress Plugin Endpoint | WordPress Plugin Function | WordPress Field | Status | Notes | |---|-------------|-----------|-------------------|---------------------------|--------------------------|----------------|--------|-------| | 1 | **id** | `id` | `content_id` | `content_id` (required) | `_igny8_content_id` (post_meta) | `wp_postmeta` | βœ… Published | Tracking field | | 2 | **title** | `title` | `title` | `title` (required) | `post_title` | `wp_posts.post_title` | βœ… Published | Core field | | 3 | **content_html** | `content_html` | `content_html` | `content_html` (required) | `post_content` | `wp_posts.post_content` | βœ… Published | Core field, sanitized with `wp_kses_post()` | | 4 | **meta_title** | `meta_title` | `seo_title` | `seo_title` OR `meta_title` | `_yoast_wpseo_title`, `_seopress_titles_title`, `_aioseo_title`, `_igny8_meta_title` | `wp_postmeta` (SEO plugins) | βœ… Published | SEO title for Yoast/SEOPress/AIOSEO | | 5 | **meta_description** | `meta_description` | `seo_description` | `seo_description` OR `meta_description` | `_yoast_wpseo_metadesc`, `_seopress_titles_desc`, `_aioseo_description`, `_igny8_meta_description` | `wp_postmeta` (SEO plugins) | βœ… Published | SEO description for plugins | | 6 | **primary_keyword** | `primary_keyword` | `primary_keyword` | `primary_keyword` | Added to tags | `wp_terms` (post_tag) | βœ… Published | Auto-added as tag if not exists | | 7 | **secondary_keywords** | `secondary_keywords` | `secondary_keywords` (JSON array) | `secondary_keywords` (JSON array) | Added to tags | `wp_terms` (post_tag) | βœ… Published | Each keyword becomes a tag | | 8 | **taxonomy_terms (category)** | `taxonomy_terms` (M2M) | `categories` (array of names) | `categories` (array) | `category_ids` β†’ `wp_set_post_terms()` | `wp_term_relationships` (category) | βœ… Published | Retrieved via `taxonomy_terms.filter(taxonomy_type='category')` | | 9 | **taxonomy_terms (tag)** | `taxonomy_terms` (M2M) | `tags` (array of names) | `tags` (array) | `tag_ids` β†’ `wp_set_post_terms()` | `wp_term_relationships` (post_tag) | βœ… Published | Retrieved via `taxonomy_terms.filter(taxonomy_type='tag')` | | 10 | **cluster** | `cluster_id` (FK) | `cluster_id`, `categories` (fallback) | `cluster_id` | `_igny8_cluster_id` (post_meta), `igny8_clusters` (taxonomy) | `wp_postmeta`, `wp_term_relationships` | βœ… Published | Used as category fallback if no taxonomy_terms exist | | 11 | **sector** | `sector_id` (FK) | `sector_id` | `sector_id` | `_igny8_sector_id` (post_meta), `igny8_sectors` (taxonomy) | `wp_postmeta`, `wp_term_relationships` | βœ… Published | Custom taxonomy | | 12 | **Images (featured)** | `Images` model (FK) | `featured_image_url` | `featured_image_url` OR `featured_image` | `_thumbnail_id` (post_meta) | `wp_postmeta`, `wp_posts` (attachment) | βœ… Published | Via `igny8_set_featured_image()` - downloads/attaches image | | 13 | **Images (gallery)** | `Images` model (FK) | `gallery_images` (array) | `gallery_images` (array) | Gallery processing | `wp_postmeta`, `wp_posts` (attachments) | βœ… Published | Via `igny8_set_gallery_images()` - downloads/attaches images | | 14 | **status** | `status` | `status` | `status` | `post_status` | `wp_posts.post_status` | βœ… Published | Mapped via `igny8_map_igny8_status_to_wp()` (draft/published/reviewβ†’publish/draft) | | 15 | **published_at** | `published_at` | NOT SENT | `published_at` (optional) | `post_date`, `post_date_gmt` | `wp_posts.post_date`, `wp_posts.post_date_gmt` | ⚠️ Available | Not currently sent by backend | | 16 | **excerpt** | N/A | `excerpt` (generated from content_html) | `excerpt` | `post_excerpt` | `wp_posts.post_excerpt` | βœ… Published | Auto-generated: first 150 chars of stripped HTML | | 17 | **content_type** | `content_type` | `content_type` (optional) | `content_type` (optional) | `_igny8_content_type` (post_meta) | `wp_postmeta` | βœ… Published | Tracking field (e.g., "blog_post", "product_page") | | 18 | **content_structure** | `content_structure` | `content_structure` (optional) | `content_structure` (optional) | `_igny8_content_structure` (post_meta) | `wp_postmeta` | βœ… Published | Tracking field | | 19 | **source** | `source` | NOT SENT | `source` (optional) | `_igny8_source` (post_meta) | `wp_postmeta` | ⚠️ Available | Not currently sent by backend | | 20 | **author** | `user_id` (FK) | NOT SENT | `author` (optional) | `post_author` | `wp_posts.post_author` | ⚠️ Available | Not sent - WordPress uses fallback author mapping | | 21 | **word_count** | `word_count` | NOT SENT | N/A | N/A | N/A | ❌ Not Published | Could be sent as post_meta | | 22 | **optimization_scores** | `optimization_scores` | NOT SENT | N/A | N/A | N/A | ❌ Not Published | JSON field - could be sent as post_meta | | 23 | **metadata** | `metadata` | NOT SENT | N/A | N/A | N/A | ❌ Not Published | JSON field - could be sent as post_meta | | 24 | **internal_links** | `internal_links` | NOT SENT | N/A | N/A | N/A | ❌ Not Published | JSON field - could be sent as post_meta | | 25 | **external_id** | `external_id` | NOT SENT (return value) | N/A | `post_id` (return) | `wp_posts.ID` | βœ… Return Value | WordPress returns this to IGNY8, saved to content.external_id | | 26 | **external_url** | `external_url` | NOT SENT (return value) | N/A | `post_url` (return) | `get_permalink()` | βœ… Return Value | WordPress returns this to IGNY8, saved to content.external_url | | 27 | **external_metadata** | `external_metadata` | NOT SENT | N/A | N/A | N/A | ❌ Not Published | JSON field - not sent | | 28 | **task_id** | N/A | NOT SENT | `task_id` (optional) | `_igny8_task_id` (post_meta) | `wp_postmeta` | ⚠️ Available | Not sent by backend, but WordPress accepts it | | 29 | **brief** | `brief` | NOT SENT | `brief` (optional) | Used for excerpt fallback | `wp_posts.post_excerpt` | ⚠️ Available | Not sent, but would be used as excerpt if sent | | 30 | **slug** | `slug` | NOT SENT | N/A | N/A | `wp_posts.post_name` | ❌ Not Published | WordPress auto-generates slug | | 31 | **ai_content_brief** | `ai_content_brief` | NOT SENT | N/A | N/A | N/A | ❌ Not Published | IGNY8-internal field | | 32 | **ai_response_raw** | `ai_response_raw` | NOT SENT | N/A | N/A | N/A | ❌ Not Published | IGNY8-internal field | --- ## πŸ“‹ Status Legend - βœ… **Published** - Field is actively sent and saved to WordPress - ⚠️ **Available** - Field is accepted by WordPress but not currently sent by IGNY8 backend - ❌ **Not Published** - Field is not sent and not used in publishing workflow --- ## πŸ”‘ Authentication & Endpoints ### Backend API Endpoint ``` POST /v1/publisher/publish/ Authorization: Token Request Body: { "destination_id": 123, "content_ids": [456] } ``` ### WordPress Plugin Endpoint ``` POST {site_url}/wp-json/igny8/v1/publish Headers: X-IGNY8-API-KEY: Content-Type: application/json Request Body: { "content_id": 456, "title": "...", "content_html": "...", "seo_title": "...", "seo_description": "...", "categories": ["Category 1", "Category 2"], "tags": ["tag1", "tag2", "keyword"], "featured_image_url": "https://...", "gallery_images": [{"url": "...", "alt": "...", "caption": "..."}], "status": "publish", "primary_keyword": "...", "secondary_keywords": ["...", "..."], "cluster_id": 789, "sector_id": 101, "content_type": "blog_post", "content_structure": "..." } ``` --- ## πŸ—‚οΈ WordPress Database Mapping ### Core Post Data (`wp_posts`) - `post_title` ← title - `post_content` ← content_html (sanitized) - `post_excerpt` ← excerpt (auto-generated) - `post_status` ← status (mapped: draftβ†’draft, publishedβ†’publish, reviewβ†’publish) - `post_type` ← resolved from content_type (default: "post") - `post_author` ← mapped from WordPress author settings - `post_date` ← published_at (if provided) - `post_date_gmt` ← published_at (converted to GMT) ### Post Meta (`wp_postmeta`) - `_igny8_content_id` ← content_id - `_igny8_cluster_id` ← cluster_id - `_igny8_sector_id` ← sector_id - `_igny8_content_type` ← content_type - `_igny8_content_structure` ← content_structure - `_igny8_meta_title` ← meta_title - `_igny8_meta_description` ← meta_description - `_yoast_wpseo_title` ← meta_title (Yoast SEO) - `_yoast_wpseo_metadesc` ← meta_description (Yoast SEO) - `_seopress_titles_title` ← meta_title (SEOPress) - `_seopress_titles_desc` ← meta_description (SEOPress) - `_aioseo_title` ← meta_title (AIOSEO) - `_aioseo_description` ← meta_description (AIOSEO) - `_thumbnail_id` ← featured image attachment ID ### Taxonomies (`wp_term_relationships`, `wp_terms`) - `category` ← categories array + cluster.name (fallback) - `post_tag` ← tags array + primary_keyword + secondary_keywords - `igny8_clusters` ← cluster_id (custom taxonomy) - `igny8_sectors` ← sector_id (custom taxonomy) ### Attachments (`wp_posts` type=attachment) - Featured image ← downloaded from featured_image_url - Gallery images ← downloaded from gallery_images[].url --- ## πŸ”§ Backend Code Locations ### Publisher Service **File**: `backend/igny8_core/business/publishing/services/publisher_service.py` - `PublisherService._publish_to_destination()` - Main orchestration - Updates `content.external_id`, `content.external_url`, `content.status='published'` after success ### WordPress Adapter **File**: `backend/igny8_core/business/publishing/services/adapters/wordpress_adapter.py` - `WordPressAdapter.publish()` - Entry point - `WordPressAdapter._publish_via_api_key()` - Prepares payload and sends to WordPress - **Line 147-160**: Extracts optional fields (meta_title, meta_description, keywords, cluster, sector) - **Line 162-178**: Gets categories from `taxonomy_terms.filter(taxonomy_type='category')` - **Line 180-212**: Gets tags from `taxonomy_terms.filter(taxonomy_type='tag')` + primary_keyword + secondary_keywords - **Line 214-253**: Gets images (featured + gallery) from `Images` model ### Publisher ViewSet **File**: `backend/igny8_core/modules/writer/views.py` - `PublisherViewSet.publish()` - API endpoint handler --- ## 🎯 WordPress Plugin Code Locations ### REST API Endpoint **File**: `igny8-wp-plugin/includes/class-igny8-rest-api.php` - **Line 90**: Registers `/wp-json/igny8/v1/publish` endpoint - **Line 490-631**: `publish_content_to_wordpress()` - Validates request, logs data, calls creation function ### Post Creation Function **File**: `igny8-wp-plugin/sync/igny8-to-wp.php` - **Line 73-300+**: `igny8_create_wordpress_post_from_task()` - Main creation function - **Line 106**: Prepares `content_html` (accepts `content_html` or legacy `content`) - **Line 112**: Maps author via `igny8_map_content_author()` - **Line 118-131**: Creates post data array with title, content, excerpt, status, type, author - **Line 133-138**: Sets publication date if provided - **Line 141-175**: Adds IGNY8 metadata (_igny8_content_id, _igny8_cluster_id, etc.) - **Line 178**: Creates post with `wp_insert_post()` - **Line 186**: Imports SEO metadata via `igny8_import_seo_metadata()` - **Line 189**: Imports featured image via `igny8_import_featured_image()` - **Line 192**: Imports taxonomies via `igny8_import_taxonomies()` - **Line 195**: Imports/processes content images via `igny8_import_content_images()` - **Line 209-238**: Processes and assigns categories via `igny8_process_categories()` - **Line 241-258**: Processes and assigns tags via `igny8_process_tags()` - **Line 261-273**: Sets featured image from URL - **Line 274-299**: Sets SEO meta fields for Yoast/SEOPress/AIOSEO --- ## πŸ“ˆ Field Usage Statistics ### Currently Published: 19 fields - Core content: title, content_html, excerpt, status - SEO: meta_title, meta_description, primary_keyword, secondary_keywords - Taxonomies: categories (via taxonomy_terms), tags (via taxonomy_terms), cluster (fallback), sector - Images: featured_image_url, gallery_images - Tracking: content_id, cluster_id, sector_id, content_type, content_structure ### Available But Unused: 4 fields - published_at (WordPress accepts, backend doesn't send) - source (WordPress accepts, backend doesn't send) - author (WordPress has fallback mapping, backend doesn't send) - task_id (WordPress accepts, backend doesn't send) ### Not Published: 9 fields - word_count, optimization_scores, metadata, internal_links - slug (WordPress auto-generates) - ai_content_brief, ai_response_raw (IGNY8-internal) - external_id, external_url (return values from WordPress) - external_metadata (not sent) --- ## πŸš€ Recent Enhancements (2025-12-01) ### Content Status Update After successful WordPress publish, IGNY8 backend now updates: ```python content.status = 'published' content.save() ``` **File**: `publisher_service.py` line 184 ### Error Handling Improvements Frontend now shows proper error messages instead of "undefined": ```typescript errorMessage = "Publishing failed" ``` **File**: `frontend/src/pages/Writer/Review.tsx` ### Taxonomy Integration Fixed Categories and tags now properly extracted from `ContentTaxonomy` M2M relationship: ```python categories = [term.name for term in content.taxonomy_terms.filter(taxonomy_type='category')] tags = [term.name for term in content.taxonomy_terms.filter(taxonomy_type='tag')] ``` **File**: `wordpress_adapter.py` lines 162-178, 180-212 --- ## πŸ” Verification & Testing ### Check WordPress Logs WordPress plugin logs to `/wp-content/uploads/igny8-logs/`: ```bash tail -f /path/to/wordpress/wp-content/uploads/igny8-logs/igny8-YYYY-MM-DD.log ``` ### Check IGNY8 Celery Logs ```bash docker logs -f igny8_celery_worker ``` ### Verify Published Post After publishing, IGNY8 saves: - `content.external_id` = WordPress post_id - `content.external_url` = WordPress post URL - `content.status` = 'published' ### Database Queries **IGNY8 Backend**: ```sql -- Check content with taxonomies SELECT c.id, c.title, c.status, c.external_id, c.external_url, COUNT(DISTINCT ctr.id) as taxonomy_count FROM igny8_content c LEFT JOIN igny8_content_taxonomy_relations ctr ON c.id = ctr.content_id WHERE c.id = 456 GROUP BY c.id; -- Check taxonomy terms for content SELECT ct.name, ct.taxonomy_type, ct.slug FROM igny8_content_taxonomy_terms ct JOIN igny8_content_taxonomy_relations ctr ON ct.id = ctr.taxonomy_id WHERE ctr.content_id = 456; ``` **WordPress**: ```sql -- Check post and meta SELECT p.ID, p.post_title, p.post_status, pm.meta_key, pm.meta_value FROM wp_posts p LEFT JOIN wp_postmeta pm ON p.ID = pm.post_id WHERE pm.meta_key = '_igny8_content_id' AND pm.meta_value = '456'; -- Check categories and tags SELECT t.name, tt.taxonomy 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 tr.object_id = ; ``` --- ## πŸ’‘ Recommendations ### Potentially Useful Fields to Publish 1. **published_at** - Allow scheduled publishing 2. **author** - Map IGNY8 user to WordPress author 3. **word_count** - Store as post_meta for analytics 4. **optimization_scores** - Store as post_meta for content quality tracking ### Fields to Keep Internal - `ai_content_brief`, `ai_response_raw` - IGNY8-specific, no value in WordPress - `external_metadata` - Already used for other destinations (Shopify, Sites) - `metadata` - Generic field, may contain IGNY8-specific data --- ## πŸ“š Related Documentation - [WordPress Integration Fixes 2025-11-30](./WORDPRESS-INTEGRATION-FIXES-2025-11-30.md) - [WordPress Integration Fixes Implementation 2025-12-01](./WORDPRESS-INTEGRATION-FIXES-IMPLEMENTATION-2025-12-01.md) - [WordPress Bidirectional Sync Reference](./04-WORDPRESS-BIDIRECTIONAL-SYNC-REFERENCE.md) - [WordPress Plugin API Integration Guide](./03-WORDPRESS-PLUGIN-API-INTEGRATION-GUIDE.md)