335 lines
16 KiB
Markdown
335 lines
16 KiB
Markdown
# 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 <user_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: <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 = <post_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)
|