1368 lines
54 KiB
Markdown
1368 lines
54 KiB
Markdown
# IGNY8 WordPress Integration Reference
|
|
|
|
**Version:** 1.0.0
|
|
**Last Updated:** November 29, 2025
|
|
**Purpose:** Complete reference for the bidirectional WordPress ↔ IGNY8 integration, covering architecture, data flows, sync mechanisms, and troubleshooting.
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Integration Overview](#integration-overview)
|
|
2. [Architecture](#architecture)
|
|
3. [WordPress Plugin Structure](#wordpress-plugin-structure)
|
|
4. [IGNY8 Backend Integration Points](#igny8-backend-integration-points)
|
|
5. [Authentication & Security](#authentication--security)
|
|
6. [Bidirectional Sync: Data Elements](#bidirectional-sync-data-elements)
|
|
7. [Publishing Flow (IGNY8 → WordPress)](#publishing-flow-igny8--wordpress)
|
|
8. [Sync Flow (WordPress → IGNY8)](#sync-flow-wordpress--igny8)
|
|
9. [REST API Endpoints](#rest-api-endpoints)
|
|
10. [Data Models & Meta Fields](#data-models--meta-fields)
|
|
11. [Status Mapping](#status-mapping)
|
|
12. [Troubleshooting Guide](#troubleshooting-guide)
|
|
13. [Developer Quick Reference](#developer-quick-reference)
|
|
|
|
---
|
|
|
|
## Integration Overview
|
|
|
|
The IGNY8 WordPress Bridge enables **bidirectional synchronization** between IGNY8 (SaaS platform) and WordPress sites, allowing content created in IGNY8 to be published to WordPress and WordPress content/metadata to be synced back to IGNY8.
|
|
|
|
### Core Capabilities
|
|
|
|
| Feature | Direction | Status |
|
|
|---------|-----------|--------|
|
|
| **Content Publishing** | IGNY8 → WP | ✅ Live |
|
|
| **Post Status Sync** | WP → IGNY8 | ✅ Live |
|
|
| **Taxonomy Sync** | WP ↔ IGNY8 | ✅ Live |
|
|
| **SEO Metadata** | IGNY8 → WP | ✅ Live |
|
|
| **Featured Images** | IGNY8 → WP | ✅ Live |
|
|
| **Site Discovery** | WP → IGNY8 | ✅ Live |
|
|
| **Keyword Tracking** | IGNY8 → WP | ✅ Live |
|
|
| **Internal Linking** | IGNY8 → WP | 🔄 Pending |
|
|
| **Content Optimization** | WP ↔ IGNY8 | 🔄 Pending |
|
|
|
|
### Integration Model
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ IGNY8 SaaS Platform │
|
|
│ ┌──────────────────────────────────────────────────────┐ │
|
|
│ │ Django Backend (api.igny8.com) │ │
|
|
│ │ - Publisher Service │ │
|
|
│ │ - WordPress Adapter │ │
|
|
│ │ - Content Models │ │
|
|
│ │ - Integration Models │ │
|
|
│ │ - Celery Background Tasks │ │
|
|
│ └──────────────────────────────────────────────────────┘ │
|
|
└───────────────────────┬─────────────────────────────────────┘
|
|
│
|
|
│ REST API (JSON)
|
|
│ - JWT Auth + API Key
|
|
│ - Unified Response Format
|
|
│
|
|
┌───────────────────────▼─────────────────────────────────────┐
|
|
│ WordPress Site (client.com) │
|
|
│ ┌──────────────────────────────────────────────────────┐ │
|
|
│ │ IGNY8 Bridge Plugin │ │
|
|
│ │ - API Client (class-igny8-api.php) │ │
|
|
│ │ - REST Endpoints (/wp-json/igny8/v1/) │ │
|
|
│ │ - Sync Handlers (sync/*.php) │ │
|
|
│ │ - Post Meta Storage (_igny8_* fields) │ │
|
|
│ │ - Custom Taxonomies (igny8_clusters, igny8_sectors)│ │
|
|
│ └──────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
### High-Level Data Flow
|
|
|
|
```
|
|
IGNY8 → WordPress Publishing Flow:
|
|
┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
│ Frontend │ -> │ Publisher │ -> │ Publisher │ -> │ WordPress │ -> │ WordPress │
|
|
│ (Review.tsx)│ │ ViewSet │ │ Service │ │ Adapter │ │ REST API │
|
|
│ Publish │ │ /publish/ │ │publish_content()│ │ publish() │ │ /igny8/v1/ │
|
|
│ Button │ │ │ │ │ │ │ │publish-content│
|
|
└─────────────┘ └──────────────┘ └─────────────────┘ └──────────────┘ └──────────────┘
|
|
│
|
|
▼
|
|
┌──────────────┐
|
|
│ WordPress │
|
|
│ Post │
|
|
│ Created │
|
|
└──────────────┘
|
|
|
|
WordPress → IGNY8 Sync Flow:
|
|
┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ ┌──────────────┐
|
|
│ WordPress │ -> │ WP Hook │ -> │ Igny8API │ -> │ IGNY8 API │
|
|
│ save_post │ │ post-sync.php│ │ PUT/POST │ │ /writer/tasks│
|
|
│ Event │ │ │ │ │ │ │
|
|
└─────────────┘ └──────────────┘ └─────────────────┘ └──────────────┘
|
|
│
|
|
▼
|
|
┌──────────────┐
|
|
│ Content │
|
|
│ Updated │
|
|
└──────────────┘
|
|
```
|
|
|
|
### Component Interaction
|
|
|
|
**IGNY8 Backend Components:**
|
|
- **PublisherViewSet** (`igny8_core/modules/publisher/views.py`) - REST API endpoint
|
|
- **PublisherService** (`igny8_core/business/publishing/services/publisher_service.py`) - Business logic
|
|
- **WordPressAdapter** (`igny8_core/business/publishing/services/adapters/wordpress_adapter.py`) - WP integration
|
|
- **Celery Tasks** (`igny8_core/tasks/wordpress_publishing.py`) - Async background publishing
|
|
- **Content Model** (`igny8_core/business/content/models.py`) - Content data
|
|
- **SiteIntegration Model** (`igny8_core/business/integration/models.py`) - WordPress connection config
|
|
|
|
**WordPress Plugin Components:**
|
|
- **Igny8API** (`includes/class-igny8-api.php`) - API client for IGNY8
|
|
- **Igny8RestAPI** (`includes/class-igny8-rest-api.php`) - REST endpoints for IGNY8 to call
|
|
- **Post Sync** (`sync/post-sync.php`) - WP → IGNY8 synchronization
|
|
- **IGNY8 to WP** (`sync/igny8-to-wp.php`) - IGNY8 → WP content creation
|
|
- **Hooks** (`sync/hooks.php`) - WordPress action/filter hooks
|
|
- **Admin** (`admin/settings.php`) - Plugin settings UI
|
|
|
|
---
|
|
|
|
## WordPress Plugin Structure
|
|
|
|
```
|
|
igny8-wp-integration/
|
|
├── igny8-bridge.php # Main plugin file
|
|
│ - Plugin bootstrap
|
|
│ - Registers all classes
|
|
│ - Defines constants
|
|
│ - Version: 1.0.0
|
|
│
|
|
├── includes/ # Core functionality
|
|
│ ├── class-igny8-api.php # API Client
|
|
│ │ - login() - JWT authentication
|
|
│ │ - refresh_token() - Token refresh
|
|
│ │ - get/post/put/delete() - HTTP methods
|
|
│ │ - parse_response() - Unified response handling
|
|
│ │
|
|
│ ├── class-igny8-rest-api.php # WordPress REST Endpoints
|
|
│ │ Endpoints:
|
|
│ │ - GET /wp-json/igny8/v1/post-by-content-id/{id}
|
|
│ │ - GET /wp-json/igny8/v1/post-by-task-id/{id}
|
|
│ │ - GET /wp-json/igny8/v1/post-status/{content_id}
|
|
│ │ - GET /wp-json/igny8/v1/site-metadata/
|
|
│ │ - GET /wp-json/igny8/v1/status
|
|
│ │ - POST /wp-json/igny8/v1/publish-content/
|
|
│ │
|
|
│ ├── class-igny8-site.php # Site data collection
|
|
│ ├── class-igny8-webhooks.php # Webhook handlers
|
|
│ ├── class-igny8-link-queue.php # Link processing
|
|
│ └── functions.php # Helper functions
|
|
│
|
|
├── sync/ # Synchronization
|
|
│ ├── hooks.php # WP hooks registration
|
|
│ │ - save_post
|
|
│ │ - publish_post
|
|
│ │ - transition_post_status
|
|
│ │
|
|
│ ├── post-sync.php # WP → IGNY8 sync
|
|
│ │ - igny8_sync_post_status_to_igny8()
|
|
│ │ - igny8_update_keywords_on_post_publish()
|
|
│ │ - igny8_map_wp_status_to_igny8()
|
|
│ │
|
|
│ ├── taxonomy-sync.php # Taxonomy sync
|
|
│ │ - Clusters (igny8_clusters)
|
|
│ │ - Sectors (igny8_sectors)
|
|
│ │ - Categories/Tags mapping
|
|
│ │
|
|
│ └── igny8-to-wp.php # IGNY8 → WP content
|
|
│ - igny8_create_wordpress_post_from_task()
|
|
│ - igny8_import_seo_metadata()
|
|
│ - igny8_import_featured_image()
|
|
│ - igny8_import_taxonomies()
|
|
│
|
|
├── admin/ # Admin interface
|
|
│ ├── settings.php # Settings page UI
|
|
│ │ - API credentials
|
|
│ │ - Connection status
|
|
│ │ - Post type toggles
|
|
│ │
|
|
│ ├── class-admin.php # Settings controller
|
|
│ ├── class-admin-columns.php # Custom post columns
|
|
│ └── class-post-meta-boxes.php # Post editor meta boxes
|
|
│
|
|
├── data/ # Data collection
|
|
│ ├── site-collection.php # Full site scan
|
|
│ ├── semantic-mapping.php # Semantic metadata
|
|
│ ├── link-graph.php # Internal link graph
|
|
│ └── woocommerce.php # WooCommerce integration
|
|
│
|
|
└── docs/ # Documentation
|
|
├── WORDPRESS-PLUGIN-INTEGRATION.md
|
|
├── AUTHENTICATION-AUDIT.md
|
|
└── SYNC-DATA-FLOW-DIAGRAM.md
|
|
```
|
|
|
|
### Key Plugin Classes
|
|
|
|
**Igny8API**
|
|
- **Purpose:** Communication with IGNY8 REST API
|
|
- **Location:** `includes/class-igny8-api.php`
|
|
- **Key Methods:**
|
|
- `login($email, $password)` - Authenticate and get JWT tokens
|
|
- `refresh_token()` - Refresh expired access token
|
|
- `get($endpoint)` - GET request with auto-retry on 401
|
|
- `post($endpoint, $data)` - POST request
|
|
- `put($endpoint, $data)` - PUT request (for updates)
|
|
- `parse_response($response)` - Parse unified response format
|
|
|
|
**Igny8RestAPI**
|
|
- **Purpose:** Provide REST endpoints for IGNY8 to call
|
|
- **Location:** `includes/class-igny8-rest-api.php`
|
|
- **Authentication:** API key via `X-IGNY8-API-KEY` header or Bearer token
|
|
- **Response Format:** Unified `{success, data, message, request_id}`
|
|
|
|
---
|
|
|
|
## IGNY8 Backend Integration Points
|
|
|
|
### Database Models
|
|
|
|
**Content Model** (`igny8_core/business/content/models.py`)
|
|
```python
|
|
class Content(SiteSectorBaseModel):
|
|
"""Main content storage in IGNY8"""
|
|
|
|
# Core fields
|
|
title = CharField(max_length=255)
|
|
content_html = TextField() # Final HTML content
|
|
word_count = IntegerField()
|
|
|
|
# SEO fields
|
|
meta_title = CharField(max_length=255)
|
|
meta_description = TextField()
|
|
primary_keyword = CharField(max_length=255)
|
|
secondary_keywords = JSONField(default=list)
|
|
|
|
# Content classification
|
|
cluster = ForeignKey('planner.Clusters') # Required
|
|
content_type = CharField(choices=['post', 'page', 'product', 'taxonomy'])
|
|
content_structure = CharField(choices=['article', 'guide', 'comparison', ...])
|
|
|
|
# WordPress sync fields
|
|
external_id = CharField() # WordPress post ID
|
|
external_url = URLField() # WordPress post URL
|
|
external_type = CharField() # WordPress post type
|
|
sync_status = CharField() # Sync status
|
|
|
|
# Status
|
|
status = CharField(choices=['draft', 'review', 'published'])
|
|
source = CharField(choices=['igny8', 'wordpress'])
|
|
```
|
|
|
|
**SiteIntegration Model** (`igny8_core/business/integration/models.py`)
|
|
```python
|
|
class SiteIntegration(AccountBaseModel):
|
|
"""WordPress connection configuration"""
|
|
|
|
site = ForeignKey('Site')
|
|
platform = CharField(choices=['wordpress', 'shopify', 'custom'])
|
|
|
|
# Configuration
|
|
config_json = JSONField() # {site_url, endpoints, ...}
|
|
credentials_json = JSONField() # {api_key, username, password}
|
|
|
|
# Sync tracking
|
|
is_active = BooleanField(default=True)
|
|
sync_enabled = BooleanField(default=False)
|
|
last_sync_at = DateTimeField()
|
|
sync_status = CharField(choices=['success', 'failed', 'pending', 'syncing'])
|
|
```
|
|
|
|
**PublishingRecord Model** (`igny8_core/business/publishing/models.py`)
|
|
```python
|
|
class PublishingRecord(SiteSectorBaseModel):
|
|
"""Track content publishing to destinations"""
|
|
|
|
content = ForeignKey('writer.Content')
|
|
destination = CharField() # 'wordpress', 'sites', 'shopify'
|
|
destination_id = CharField() # External platform ID
|
|
destination_url = URLField() # Published URL
|
|
|
|
status = CharField(choices=['pending', 'publishing', 'published', 'failed'])
|
|
published_at = DateTimeField()
|
|
error_message = TextField()
|
|
metadata = JSONField()
|
|
```
|
|
|
|
### Backend Services
|
|
|
|
**PublisherService** (`igny8_core/business/publishing/services/publisher_service.py`)
|
|
- **Purpose:** Main publishing orchestrator
|
|
- **Key Method:** `publish_content(content_id, destinations, account)`
|
|
- **Flow:**
|
|
1. Lookup Content by ID
|
|
2. For each destination → call `_publish_to_destination()`
|
|
3. Create PublishingRecord
|
|
4. Get adapter (WordPressAdapter)
|
|
5. Get SiteIntegration config
|
|
6. Call adapter.publish()
|
|
7. Update PublishingRecord status
|
|
|
|
**WordPressAdapter** (`igny8_core/business/publishing/services/adapters/wordpress_adapter.py`)
|
|
- **Purpose:** WordPress-specific publishing logic
|
|
- **Authentication:** Auto-detects API key vs username/password
|
|
- **Key Methods:**
|
|
- `publish(content, destination_config)` - Main publish method
|
|
- `_publish_via_api_key()` - Uses `/wp-json/igny8/v1/publish-content/`
|
|
- `_publish_via_username_password()` - Uses standard WP REST API
|
|
- **Payload:**
|
|
```python
|
|
{
|
|
'content_id': content.id,
|
|
'title': content.title,
|
|
'content_html': content.content_html,
|
|
'content_type': content.content_type,
|
|
'content_structure': content.content_structure,
|
|
'meta_title': content.meta_title,
|
|
'meta_description': content.meta_description,
|
|
'status': content.status,
|
|
'cluster_id': content.cluster.id,
|
|
'sector_id': content.sector.id,
|
|
'featured_image': {...},
|
|
'categories': [...],
|
|
'tags': [...]
|
|
}
|
|
```
|
|
|
|
**Celery Tasks** (`igny8_core/tasks/wordpress_publishing.py`)
|
|
- **Purpose:** Async background publishing
|
|
- **Task:** `publish_content_to_wordpress(content_id, site_integration_id)`
|
|
- **Features:**
|
|
- Retry logic (3 retries, exponential backoff)
|
|
- Duplicate check (prevents re-publishing)
|
|
- Updates Content.external_id and external_url
|
|
- Comprehensive logging
|
|
|
|
---
|
|
|
|
## Authentication & Security
|
|
|
|
### IGNY8 → WordPress Authentication
|
|
|
|
**Method 1: API Key (Preferred)**
|
|
- **Storage:** `SiteIntegration.credentials_json['api_key']`
|
|
- **WordPress Storage:** `igny8_api_key` option (encrypted if available)
|
|
- **Header:** `X-IGNY8-API-KEY: {api_key}` or `Authorization: Bearer {api_key}`
|
|
- **Endpoint:** `/wp-json/igny8/v1/publish-content/`
|
|
- **Security:** API key validated in `Igny8RestAPI::check_permission()`
|
|
|
|
**Method 2: Username/Password (Legacy)**
|
|
- **Storage:** `SiteIntegration.credentials_json['username']` and `password`
|
|
- **Flow:** Uses standard WordPress REST API authentication
|
|
- **Limitation:** Requires Application Password or JWT plugin
|
|
|
|
### WordPress → IGNY8 Authentication
|
|
|
|
**JWT Token Flow**
|
|
1. **Login:** `POST /api/v1/auth/login/` with `{email, password}`
|
|
2. **Response:** `{access, refresh}` tokens
|
|
3. **Storage:** `igny8_access_token` and `igny8_refresh_token` WordPress options
|
|
4. **Usage:** `Authorization: Bearer {access_token}` header
|
|
5. **Refresh:** On 401, call `POST /api/v1/auth/refresh/` with `{refresh}`
|
|
6. **Auto-retry:** `Igny8API` class automatically refreshes and retries on 401
|
|
|
|
**Credentials Required (WordPress Settings)**
|
|
- **Email:** `igny8_email` option
|
|
- **Password:** Stored temporarily for token refresh
|
|
- **API Key:** `igny8_api_key` option (for incoming requests from IGNY8)
|
|
|
|
---
|
|
|
|
## Bidirectional Sync: Data Elements
|
|
|
|
### IGNY8 → WordPress (Publishing)
|
|
|
|
**Content Fields Synced:**
|
|
|
|
| IGNY8 Field | WordPress Destination | Notes |
|
|
|-------------|----------------------|-------|
|
|
| `title` | `post_title` | Sanitized |
|
|
| `content_html` | `post_content` | Passed through `wp_kses_post()` |
|
|
| `meta_title` | `_yoast_wpseo_title`<br>`_seopress_titles_title`<br>`_aioseo_title`<br>`_igny8_meta_title` | Multi-SEO plugin support |
|
|
| `meta_description` | `_yoast_wpseo_metadesc`<br>`_seopress_titles_desc`<br>`_aioseo_description`<br>`_igny8_meta_description` | Multi-SEO plugin support |
|
|
| `status` | `post_status` | Mapped via `igny8_map_igny8_status_to_wp()` |
|
|
| `content_type` | `post_type` | `post`, `page`, `product` |
|
|
| `content_structure` | `_igny8_content_structure` | Meta field for tracking |
|
|
| `cluster.id` | `_igny8_cluster_id`<br>`igny8_clusters` taxonomy | Both meta and taxonomy term |
|
|
| `sector.id` | `_igny8_sector_id`<br>`igny8_sectors` taxonomy | Both meta and taxonomy term |
|
|
| `task_id` | `_igny8_task_id` | Task reference |
|
|
| `id` (Content) | `_igny8_content_id` | Content reference |
|
|
| `primary_keyword` | `_igny8_primary_keyword` | SEO keyword |
|
|
| `secondary_keywords` | `_igny8_secondary_keywords` | JSON array |
|
|
| `featured_image.url` | Featured Image (attachment) | Downloaded and attached |
|
|
| `gallery_images[]` | `_igny8_gallery_images` | Image attachments |
|
|
| `categories[]` | `category` taxonomy | Standard WP categories |
|
|
| `tags[]` | `post_tag` taxonomy | Standard WP tags |
|
|
|
|
**Post Meta Fields Created:**
|
|
|
|
```php
|
|
// IGNY8 identification
|
|
_igny8_task_id // Task ID from IGNY8
|
|
_igny8_content_id // Content ID from IGNY8
|
|
_igny8_cluster_id // Cluster ID
|
|
_igny8_sector_id // Sector ID
|
|
_igny8_source // 'igny8' or 'wordpress'
|
|
|
|
// Content metadata
|
|
_igny8_content_type // 'post', 'page', 'product'
|
|
_igny8_content_structure // 'article', 'guide', etc.
|
|
|
|
// SEO fields (generic)
|
|
_igny8_meta_title // SEO title
|
|
_igny8_meta_description // SEO description
|
|
_igny8_primary_keyword // Primary keyword
|
|
_igny8_secondary_keywords // JSON array
|
|
|
|
// Sync tracking
|
|
_igny8_wordpress_status // WordPress post_status
|
|
_igny8_last_synced // Last sync timestamp
|
|
_igny8_brief_cached_at // Brief cache timestamp
|
|
_igny8_task_brief // Cached task brief
|
|
```
|
|
|
|
### WordPress → IGNY8 (Status Sync)
|
|
|
|
**Fields Synced on Post Save:**
|
|
|
|
| WordPress Field | IGNY8 Endpoint | IGNY8 Field | Trigger |
|
|
|----------------|----------------|-------------|---------|
|
|
| `post_status` | `PUT /writer/tasks/{id}/` | `status` (mapped)<br>`wordpress_status` (raw) | `save_post` hook |
|
|
| `ID` | `PUT /writer/tasks/{id}/` | `assigned_post_id` | `save_post` hook |
|
|
| `permalink` | `PUT /writer/tasks/{id}/` | `post_url` | `save_post` hook |
|
|
| `post_type` | `PUT /writer/tasks/{id}/` | `post_type` | `save_post` hook |
|
|
| Post updated | `PUT /writer/tasks/{id}/` | `synced_at` | `save_post` hook |
|
|
|
|
**Sync Conditions:**
|
|
- Only syncs if post has `_igny8_task_id` meta (IGNY8-managed posts)
|
|
- Skips autosaves and revisions
|
|
- Only runs if connection is enabled (`igny8_is_connection_enabled()`)
|
|
- Implemented in `sync/post-sync.php`
|
|
|
|
### Site Metadata Discovery (WordPress → IGNY8)
|
|
|
|
**Endpoint:** `GET /wp-json/igny8/v1/site-metadata/`
|
|
|
|
**Data Collected:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"site_info": {
|
|
"name": "Site Name",
|
|
"url": "https://example.com",
|
|
"description": "Site description",
|
|
"language": "en_US",
|
|
"timezone": "America/New_York"
|
|
},
|
|
"post_types": [
|
|
{
|
|
"name": "post",
|
|
"label": "Posts",
|
|
"count": 150,
|
|
"supports": ["title", "editor", "thumbnail", "excerpt"]
|
|
},
|
|
{
|
|
"name": "page",
|
|
"label": "Pages",
|
|
"count": 25
|
|
},
|
|
{
|
|
"name": "product",
|
|
"label": "Products",
|
|
"count": 80
|
|
}
|
|
],
|
|
"taxonomies": [
|
|
{
|
|
"name": "category",
|
|
"label": "Categories",
|
|
"count": 20,
|
|
"terms": [...]
|
|
},
|
|
{
|
|
"name": "post_tag",
|
|
"label": "Tags",
|
|
"count": 150
|
|
}
|
|
],
|
|
"plugins": {
|
|
"woocommerce": true,
|
|
"yoast_seo": true,
|
|
"advanced_custom_fields": false
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Cache:** Transient `igny8_site_metadata_v1` (5 minutes)
|
|
|
|
---
|
|
|
|
## Publishing Flow (IGNY8 → WordPress)
|
|
|
|
### Complete Flow (Frontend to WordPress)
|
|
|
|
**Step 1: User Clicks Publish Button**
|
|
- **File:** `frontend/src/pages/Writer/Review.tsx`
|
|
- **Method:** `handlePublishSingle()`
|
|
- **Action:** `POST /api/v1/publisher/publish/`
|
|
- **Payload:** `{content_id, destinations: ['wordpress']}`
|
|
|
|
**Step 2: Publisher API Endpoint**
|
|
- **File:** `igny8_core/modules/publisher/views.py`
|
|
- **Class:** `PublisherViewSet`
|
|
- **Method:** `publish()` action
|
|
- **Logging:** `🚀 Publish request received`
|
|
- **Action:** Call `PublisherService.publish_content()`
|
|
|
|
**Step 3: Publisher Service**
|
|
- **File:** `igny8_core/business/publishing/services/publisher_service.py`
|
|
- **Method:** `publish_content(content_id, destinations, account)`
|
|
- **Steps:**
|
|
1. Load Content from database
|
|
2. For each destination, call `_publish_to_destination()`
|
|
3. Create `PublishingRecord` (status='pending')
|
|
4. Get adapter for destination
|
|
5. Lookup `SiteIntegration` for site
|
|
6. Merge config: `{**config_json, **credentials_json, 'site_url': ...}`
|
|
7. Call `adapter.publish(content, config)`
|
|
8. Update `PublishingRecord` (status='published' or 'failed')
|
|
|
|
**Step 4: WordPress Adapter**
|
|
- **File:** `igny8_core/business/publishing/services/adapters/wordpress_adapter.py`
|
|
- **Method:** `publish(content, destination_config)`
|
|
- **Auto-detection:**
|
|
```python
|
|
if config.get('api_key'):
|
|
return _publish_via_api_key(...)
|
|
else:
|
|
return _publish_via_username_password(...)
|
|
```
|
|
|
|
**Step 4a: API Key Method (Preferred)**
|
|
- **Method:** `_publish_via_api_key()`
|
|
- **Endpoint:** `POST {site_url}/wp-json/igny8/v1/publish-content/`
|
|
- **Headers:** `X-IGNY8-API-KEY: {api_key}`
|
|
- **Payload:**
|
|
```json
|
|
{
|
|
"content_id": 123,
|
|
"title": "Post Title",
|
|
"content_html": "<h2>Introduction</h2><p>...</p>",
|
|
"content_type": "post",
|
|
"content_structure": "article",
|
|
"meta_title": "SEO Title",
|
|
"meta_description": "SEO Description",
|
|
"status": "published",
|
|
"cluster_id": 45,
|
|
"sector_id": 12,
|
|
"featured_image": {...},
|
|
"categories": [...],
|
|
"tags": [...]
|
|
}
|
|
```
|
|
- **Response:** `{success, data: {post_id, url}}`
|
|
|
|
**Step 5: WordPress REST Endpoint**
|
|
- **File:** `includes/class-igny8-rest-api.php`
|
|
- **Method:** `publish_content_to_wordpress()`
|
|
- **Endpoint:** `/wp-json/igny8/v1/publish-content/`
|
|
- **Permission:** `check_permission()` validates API key
|
|
- **Action:** Call `igny8_create_wordpress_post_from_task($content_data)`
|
|
|
|
**Step 6: WordPress Post Creation**
|
|
- **File:** `sync/igny8-to-wp.php`
|
|
- **Function:** `igny8_create_wordpress_post_from_task()`
|
|
- **Steps:**
|
|
1. Resolve post type from content_type
|
|
2. Map author (via `igny8_map_content_author()`)
|
|
3. Prepare post data
|
|
4. `wp_insert_post()` - Create WordPress post
|
|
5. Import SEO metadata (`igny8_import_seo_metadata()`)
|
|
6. Import featured image (`igny8_import_featured_image()`)
|
|
7. Import taxonomies (`igny8_import_taxonomies()`)
|
|
8. Set cluster/sector taxonomy terms
|
|
9. Set categories/tags
|
|
10. Store IGNY8 meta fields
|
|
11. Update IGNY8 task via API (`PUT /writer/tasks/{id}/`)
|
|
- **Return:** WordPress post ID or WP_Error
|
|
|
|
**Step 7: Response Propagation**
|
|
- WordPress → WordPress Adapter → Publisher Service → Publisher ViewSet → Frontend
|
|
- **Frontend Success:**
|
|
```javascript
|
|
console.log('[Review.handlePublishSingle] ✅ Publish successful:', response)
|
|
toast.success('Published to WordPress!')
|
|
```
|
|
|
|
### Alternative: Celery Background Task
|
|
|
|
**When:** If using async publishing (currently available but not default)
|
|
|
|
**Trigger:** `publish_content_to_wordpress.delay(content_id, site_integration_id)`
|
|
|
|
**File:** `igny8_core/tasks/wordpress_publishing.py`
|
|
|
|
**Benefits:**
|
|
- Non-blocking (user doesn't wait)
|
|
- Retry logic (3 attempts with exponential backoff)
|
|
- Duplicate check (prevents re-publishing same content)
|
|
|
|
**Flow:**
|
|
1. Load Content and SiteIntegration from DB
|
|
2. Check if already published (has external_id)
|
|
3. Prepare payload
|
|
4. POST to WordPress API
|
|
5. Update Content.external_id and external_url
|
|
6. Log success/failure
|
|
|
|
---
|
|
|
|
## Sync Flow (WordPress → IGNY8)
|
|
|
|
### Post Status Sync
|
|
|
|
**Trigger:** WordPress `save_post`, `publish_post`, `transition_post_status` hooks
|
|
|
|
**File:** `sync/post-sync.php`
|
|
|
|
**Function:** `igny8_sync_post_status_to_igny8($post_id, $post, $update)`
|
|
|
|
**Flow:**
|
|
1. Check if connection enabled
|
|
2. Skip autosaves and revisions
|
|
3. Check if IGNY8-managed (has `_igny8_task_id` meta)
|
|
4. Get `$task_id` from post meta
|
|
5. Get WordPress `post_status`
|
|
6. Map to IGNY8 status: `igny8_map_wp_status_to_igny8($post_status)`
|
|
7. Prepare update payload:
|
|
```php
|
|
[
|
|
'status' => $task_status, // IGNY8 mapped status
|
|
'assigned_post_id' => $post_id,
|
|
'post_url' => get_permalink($post_id),
|
|
'wordpress_status' => $post_status, // Raw WP status
|
|
'synced_at' => current_time('mysql'),
|
|
'content_id' => $content_id // If available
|
|
]
|
|
```
|
|
8. `PUT /writer/tasks/{task_id}/` via Igny8API
|
|
9. Update `_igny8_wordpress_status` and `_igny8_last_synced` meta
|
|
|
|
**Logged in WordPress:**
|
|
```
|
|
IGNY8: Synced post 123 status (publish) to task 456
|
|
```
|
|
|
|
### Keyword Status Update on Publish
|
|
|
|
**Trigger:** Post published
|
|
|
|
**Function:** `igny8_update_keywords_on_post_publish($post_id)`
|
|
|
|
**Flow:**
|
|
1. Get task_id from post meta
|
|
2. Get task details: `GET /writer/tasks/{task_id}/`
|
|
3. Extract cluster_id and keyword_ids
|
|
4. Update keyword statuses: `PUT /planner/keywords/bulk-update/`
|
|
5. Mark keywords as "published" or "in_content"
|
|
|
|
---
|
|
|
|
## REST API Endpoints
|
|
|
|
### IGNY8 Backend Endpoints (Called by WordPress)
|
|
|
|
| Method | Endpoint | Purpose | Request | Response |
|
|
|--------|----------|---------|---------|----------|
|
|
| POST | `/api/v1/auth/login/` | Get JWT tokens | `{email, password}` | `{access, refresh}` |
|
|
| POST | `/api/v1/auth/refresh/` | Refresh access token | `{refresh}` | `{access, refresh}` |
|
|
| GET | `/api/v1/writer/tasks/{id}/` | Get task details | - | Task object |
|
|
| PUT | `/api/v1/writer/tasks/{id}/` | Update task status | `{status, assigned_post_id, post_url, wordpress_status}` | Updated task |
|
|
| GET | `/api/v1/writer/tasks/{id}/brief/` | Get task brief | - | Brief content |
|
|
| PUT | `/api/v1/planner/keywords/bulk-update/` | Update keyword statuses | `{keyword_ids, status}` | Updated keywords |
|
|
| POST | `/api/v1/system/sites/{id}/import/` | Import site data | Site metadata payload | Import result |
|
|
|
|
### WordPress REST Endpoints (Called by IGNY8)
|
|
|
|
| Method | Endpoint | Purpose | Auth | Response |
|
|
|--------|----------|---------|------|----------|
|
|
| GET | `/wp-json/igny8/v1/post-by-content-id/{id}` | Get post by Content ID | API key | `{success, data: {post_id, title, status, url}}` |
|
|
| GET | `/wp-json/igny8/v1/post-by-task-id/{id}` | Get post by Task ID | API key | `{success, data: {post_id, title, status, url}}` |
|
|
| GET | `/wp-json/igny8/v1/post-status/{content_id}` | Get post status | API key | `{success, data: {wordpress_status, igny8_status}}` |
|
|
| GET | `/wp-json/igny8/v1/site-metadata/` | Get site structure | API key | `{success, data: {site_info, post_types, taxonomies}}` |
|
|
| GET | `/wp-json/igny8/v1/status` | Plugin health check | Public | `{success, data: {connected, api_key_present}}` |
|
|
| POST | `/wp-json/igny8/v1/publish-content/` | Publish content from IGNY8 | API key | `{success, data: {post_id, url}}` |
|
|
|
|
**Authentication:**
|
|
- **API Key:** `X-IGNY8-API-KEY` header or `Authorization: Bearer {api_key}`
|
|
- **Validation:** `Igny8RestAPI::check_permission()` compares with `igny8_api_key` option
|
|
|
|
---
|
|
|
|
## Data Models & Meta Fields
|
|
|
|
### WordPress Post Meta Taxonomy
|
|
|
|
**All IGNY8 meta fields use `_igny8_` prefix**
|
|
|
|
```php
|
|
// IGNY8 References
|
|
_igny8_task_id // INT - IGNY8 Task ID
|
|
_igny8_content_id // INT - IGNY8 Content ID
|
|
_igny8_cluster_id // INT - IGNY8 Cluster ID
|
|
_igny8_sector_id // INT - IGNY8 Sector ID
|
|
|
|
// Content Classification
|
|
_igny8_content_type // STRING - 'post', 'page', 'product', 'taxonomy'
|
|
_igny8_content_structure // STRING - 'article', 'guide', 'comparison', 'review', etc.
|
|
_igny8_source // STRING - 'igny8' or 'wordpress'
|
|
|
|
// SEO Fields (Generic - not plugin-specific)
|
|
_igny8_meta_title // STRING - SEO meta title
|
|
_igny8_meta_description // TEXT - SEO meta description
|
|
_igny8_primary_keyword // STRING - Primary SEO keyword
|
|
_igny8_secondary_keywords // JSON - ["keyword1", "keyword2"]
|
|
|
|
// Keywords
|
|
_igny8_keyword_ids // JSON - [1, 2, 3]
|
|
|
|
// Sync Tracking
|
|
_igny8_wordpress_status // STRING - WordPress post_status at last sync
|
|
_igny8_last_synced // DATETIME - Last sync timestamp
|
|
_igny8_brief_cached_at // DATETIME - Brief cache timestamp
|
|
_igny8_task_brief // JSON - Cached task brief data
|
|
|
|
// Images
|
|
_igny8_gallery_images // JSON - Gallery image IDs
|
|
```
|
|
|
|
### WordPress Custom Taxonomies
|
|
|
|
**Created by Plugin:**
|
|
|
|
```php
|
|
// IGNY8 Clusters
|
|
register_taxonomy('igny8_clusters', ['post', 'page'], [
|
|
'label' => 'IGNY8 Clusters',
|
|
'hierarchical' => true,
|
|
'show_in_rest' => true
|
|
]);
|
|
|
|
// IGNY8 Sectors
|
|
register_taxonomy('igny8_sectors', ['post', 'page'], [
|
|
'label' => 'IGNY8 Sectors',
|
|
'hierarchical' => true,
|
|
'show_in_rest' => true
|
|
]);
|
|
```
|
|
|
|
**Term Meta:**
|
|
- `_igny8_cluster_id` - IGNY8 cluster ID
|
|
- `_igny8_sector_id` - IGNY8 sector ID
|
|
|
|
### WordPress Options
|
|
|
|
```php
|
|
// Authentication
|
|
igny8_email // User email for IGNY8 account
|
|
igny8_access_token // JWT access token
|
|
igny8_refresh_token // JWT refresh token
|
|
igny8_api_key // API key (encrypted if available)
|
|
|
|
// Connection
|
|
igny8_connection_enabled // BOOL - Enable/disable sync
|
|
igny8_connection_status // STRING - 'connected', 'disconnected', 'error'
|
|
|
|
// Settings
|
|
igny8_allowed_post_types // JSON - ['post', 'page', 'product']
|
|
igny8_auto_import_enabled // BOOL - Auto-import content from IGNY8
|
|
igny8_sync_mode // STRING - 'manual', 'auto'
|
|
|
|
// Cache
|
|
igny8_site_metadata_v1 // Transient (5 min) - Cached site metadata
|
|
```
|
|
|
|
### IGNY8 Database Tables
|
|
|
|
**Content Table** (`igny8_content`)
|
|
- Primary table for all content
|
|
- Fields: title, content_html, meta_title, meta_description, cluster_id, sector_id, status, external_id, external_url
|
|
|
|
**Tasks Table** (`igny8_tasks`)
|
|
- Content generation tasks
|
|
- Fields: title, cluster_id, status, assigned_post_id, post_url
|
|
|
|
**SiteIntegration Table** (`igny8_site_integrations`)
|
|
- WordPress connection configs
|
|
- Fields: site_id, platform, config_json, credentials_json, is_active, sync_status
|
|
|
|
**PublishingRecord Table** (`igny8_publishing_records`)
|
|
- Track all publish operations
|
|
- Fields: content_id, destination, destination_id, destination_url, status, published_at
|
|
|
|
---
|
|
|
|
## Status Mapping
|
|
|
|
### IGNY8 Status → WordPress Status
|
|
|
|
**Function:** `igny8_map_igny8_status_to_wp($igny8_status)` (WordPress)
|
|
|
|
| IGNY8 Status | WordPress Status | Notes |
|
|
|--------------|------------------|-------|
|
|
| `completed` | `publish` | Published and live |
|
|
| `draft` | `draft` | Draft/unpublished |
|
|
| `pending` | `pending` | Pending review |
|
|
| `scheduled` | `future` | Scheduled for future |
|
|
| `archived` | `trash` | Trashed |
|
|
| *(other)* | `draft` | Default fallback |
|
|
|
|
### WordPress Status → IGNY8 Status
|
|
|
|
**Function:** `igny8_map_wp_status_to_igny8($wp_status)` (WordPress)
|
|
|
|
| WordPress Status | IGNY8 Status | Notes |
|
|
|-----------------|--------------|-------|
|
|
| `publish` | `completed` | Published |
|
|
| `future` | `completed` | Scheduled (counts as completed) |
|
|
| `private` | `completed` | Private but complete |
|
|
| `pending` | `pending` | Pending review |
|
|
| `draft` | `draft` | Draft |
|
|
| `auto-draft` | `draft` | Auto-draft |
|
|
| `trash` | `archived` | Trashed |
|
|
| *(other)* | `draft` | Default fallback |
|
|
|
|
**Important:** WordPress sends raw `wordpress_status` AND mapped IGNY8 `status` to backend
|
|
|
|
---
|
|
|
|
## Troubleshooting Guide
|
|
|
|
### Common Issues
|
|
|
|
#### 1. "Failed to publish: undefined"
|
|
|
|
**Symptoms:**
|
|
- Error message in frontend when clicking publish
|
|
- No post created in WordPress
|
|
|
|
**Diagnosis Steps:**
|
|
1. **Check browser console:**
|
|
```javascript
|
|
[Review.handlePublishSingle] Starting publish for content: {id, title}
|
|
[Review.handlePublishSingle] Calling API endpoint /v1/publisher/publish/
|
|
```
|
|
|
|
2. **Check Django logs:**
|
|
```
|
|
[PublisherViewSet.publish] 🚀 Publish request received: content_id=X
|
|
[PublisherService.publish_content] 📄 Content found: title='...'
|
|
[WordPressAdapter.publish] 🎬 Starting WordPress publish
|
|
```
|
|
|
|
3. **Check WordPress logs:**
|
|
```
|
|
IGNY8: Creating WordPress post from IGNY8 content
|
|
IGNY8: Post created with ID 123
|
|
```
|
|
|
|
**Common Causes:**
|
|
- ❌ SiteIntegration missing API key → Check `credentials_json['api_key']`
|
|
- ❌ WordPress plugin not active → Verify plugin activated
|
|
- ❌ API key mismatch → Compare IGNY8 and WordPress `api_key`
|
|
- ❌ `site_url` not set → Check `SiteIntegration.config_json['site_url']`
|
|
- ❌ Content missing `content_html` → Content.content_html is empty
|
|
|
|
**Fixes:**
|
|
- Ensure SiteIntegration has both `config_json['site_url']` and `credentials_json['api_key']`
|
|
- Verify WordPress endpoint accessible: `GET {site_url}/wp-json/igny8/v1/status`
|
|
- Check Content model has `content_html` field populated
|
|
|
|
#### 2. Posts Not Syncing Back to IGNY8
|
|
|
|
**Symptoms:**
|
|
- Edit post in WordPress, status not updated in IGNY8
|
|
- Post published in WordPress, keyword status not changing in IGNY8
|
|
|
|
**Diagnosis:**
|
|
1. Check if post has `_igny8_task_id`: `get_post_meta($post_id, '_igny8_task_id', true)`
|
|
2. Check connection enabled: `get_option('igny8_connection_enabled')`
|
|
3. Check WordPress error logs for API failures
|
|
|
|
**Common Causes:**
|
|
- Post not IGNY8-managed (missing `_igny8_task_id`)
|
|
- Connection disabled in settings
|
|
- Access token expired and refresh failed
|
|
- Network/firewall blocking outbound requests
|
|
|
|
**Fixes:**
|
|
- Only IGNY8-created posts sync (have `_igny8_task_id`)
|
|
- Enable connection in Settings → IGNY8 API
|
|
- Re-authenticate: delete `igny8_access_token` and `igny8_refresh_token`, save settings again
|
|
- Check server can reach `api.igny8.com`
|
|
|
|
#### 3. SEO Metadata Not Appearing
|
|
|
|
**Symptoms:**
|
|
- Meta title/description not in Yoast/RankMath/SEOPress
|
|
|
|
**Diagnosis:**
|
|
1. Check if generic meta exists: `get_post_meta($post_id, '_igny8_meta_title')`
|
|
2. Check SEO plugin-specific meta: `get_post_meta($post_id, '_yoast_wpseo_title')`
|
|
|
|
**Common Causes:**
|
|
- Content model missing `meta_title` or `meta_description`
|
|
- SEO plugin not detected during import
|
|
|
|
**Fixes:**
|
|
- Ensure Content has `meta_title` and `meta_description` populated
|
|
- Function `igny8_import_seo_metadata()` writes to all major SEO plugins
|
|
- Manually copy from `_igny8_meta_title` to SEO plugin field
|
|
|
|
#### 4. Images Not Importing
|
|
|
|
**Symptoms:**
|
|
- Featured image not set
|
|
- Images in content not downloaded
|
|
|
|
**Diagnosis:**
|
|
1. Check if `featured_image` in payload
|
|
2. Check WordPress upload permissions
|
|
3. Check `igny8_import_featured_image()` logs
|
|
|
|
**Common Causes:**
|
|
- Image URL not accessible (403, 404)
|
|
- WordPress upload directory not writable
|
|
- `allow_url_fopen` disabled in PHP
|
|
|
|
**Fixes:**
|
|
- Verify image URLs publicly accessible
|
|
- Check `wp-content/uploads/` permissions (775 or 755)
|
|
- Enable `allow_url_fopen` in `php.ini`
|
|
|
|
#### 5. Duplicate Posts Created
|
|
|
|
**Symptoms:**
|
|
- Same content published multiple times in WordPress
|
|
- Multiple posts with same `_igny8_content_id`
|
|
|
|
**Diagnosis:**
|
|
1. Check if Celery task has duplicate check
|
|
2. Check if `external_id` set on Content after first publish
|
|
|
|
**Common Causes:**
|
|
- Celery task retrying after network error
|
|
- Content.external_id not being saved
|
|
- Multiple publish button clicks
|
|
|
|
**Fixes:**
|
|
- Celery task has duplicate check: `if content.external_id: return`
|
|
- Ensure WordPressAdapter saves `external_id` after successful publish
|
|
- Disable publish button after first click (frontend)
|
|
|
|
### Debug Mode
|
|
|
|
**Enable WordPress Debug Logging:**
|
|
```php
|
|
// wp-config.php
|
|
define('WP_DEBUG', true);
|
|
define('WP_DEBUG_LOG', true);
|
|
define('WP_DEBUG_DISPLAY', false);
|
|
define('IGNY8_DEBUG', true); // Plugin-specific
|
|
```
|
|
|
|
**Enable Django Logging:**
|
|
```python
|
|
# settings.py
|
|
LOGGING = {
|
|
'version': 1,
|
|
'handlers': {
|
|
'console': {
|
|
'class': 'logging.StreamHandler',
|
|
},
|
|
},
|
|
'loggers': {
|
|
'igny8_core.business.publishing': {
|
|
'handlers': ['console'],
|
|
'level': 'DEBUG',
|
|
},
|
|
},
|
|
}
|
|
```
|
|
|
|
**Check Logs:**
|
|
- WordPress: `wp-content/debug.log`
|
|
- Django: Console output or configured log file
|
|
- Browser: Console (F12 → Console tab)
|
|
|
|
### Health Check Endpoints
|
|
|
|
**WordPress Plugin Status:**
|
|
```bash
|
|
GET https://yoursite.com/wp-json/igny8/v1/status
|
|
```
|
|
Response:
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"plugin_version": "1.0.0",
|
|
"connected": true,
|
|
"api_key_present": true,
|
|
"token_present": true,
|
|
"connection_enabled": true
|
|
}
|
|
}
|
|
```
|
|
|
|
**IGNY8 API Health:**
|
|
```bash
|
|
GET https://api.igny8.com/api/v1/health/
|
|
```
|
|
|
|
### Common WordPress Errors
|
|
|
|
**Error:** `IGNY8 API key not authenticated`
|
|
- **Cause:** API key missing or invalid
|
|
- **Fix:** Settings → IGNY8 API → Enter API key
|
|
|
|
**Error:** `IGNY8 connection is disabled`
|
|
- **Cause:** Connection toggle disabled in settings
|
|
- **Fix:** Settings → IGNY8 API → Enable "Enable Connection"
|
|
|
|
**Error:** `Post not found for this content ID`
|
|
- **Cause:** WordPress post with `_igny8_content_id` doesn't exist
|
|
- **Fix:** Content not yet published to WordPress or meta field missing
|
|
|
|
---
|
|
|
|
## Developer Quick Reference
|
|
|
|
### Quick Start: New Developer Onboarding
|
|
|
|
**1. Understand the Architecture**
|
|
- Read this document (WORDPRESS_INTEGRATION_REFERENCE.md)
|
|
- Read MASTER_REFERENCE.md for overall IGNY8 architecture
|
|
- Review `docs/WORDPRESS-PLUGIN-INTEGRATION.md` for plugin details
|
|
|
|
**2. Setup Local Environment**
|
|
|
|
**WordPress Plugin:**
|
|
```bash
|
|
# Clone plugin
|
|
cd wp-content/plugins/
|
|
git clone <repo> igny8-bridge
|
|
|
|
# Activate plugin in WordPress admin
|
|
# Go to Settings → IGNY8 API
|
|
# Enter credentials: Email, Password, API Key
|
|
```
|
|
|
|
**IGNY8 Backend:**
|
|
```bash
|
|
cd igny8/backend
|
|
pip install -r requirements.txt
|
|
python manage.py migrate
|
|
python manage.py runserver
|
|
|
|
# Start Celery (for async tasks)
|
|
celery -A igny8_core worker -l info
|
|
```
|
|
|
|
**3. Test Publishing Flow**
|
|
```bash
|
|
# 1. Create Content in IGNY8
|
|
# 2. Click Publish in Review page
|
|
# 3. Check browser console for logs
|
|
# 4. Check Django logs for backend flow
|
|
# 5. Check WordPress for created post
|
|
# 6. Edit post in WordPress
|
|
# 7. Check IGNY8 for updated status
|
|
```
|
|
|
|
### Key Files to Modify
|
|
|
|
**To Change Publishing Payload:**
|
|
- `igny8_core/business/publishing/services/adapters/wordpress_adapter.py` (IGNY8)
|
|
- `sync/igny8-to-wp.php` → `igny8_create_wordpress_post_from_task()` (WordPress)
|
|
|
|
**To Add New Meta Field:**
|
|
- `wordpress_adapter.py` → Add to payload
|
|
- `igny8-to-wp.php` → Add to `meta_input` array
|
|
- Update this document's meta fields table
|
|
|
|
**To Add New REST Endpoint:**
|
|
- `includes/class-igny8-rest-api.php` → `register_routes()`
|
|
- Add permission callback
|
|
- Add handler method
|
|
|
|
**To Change Status Mapping:**
|
|
- `sync/igny8-to-wp.php` → `igny8_map_igny8_status_to_wp()`
|
|
- `sync/post-sync.php` → `igny8_map_wp_status_to_igny8()`
|
|
|
|
### Testing Checklist
|
|
|
|
**IGNY8 → WordPress Publishing**
|
|
- [ ] Create content in IGNY8 Writer
|
|
- [ ] Content has title, content_html, cluster
|
|
- [ ] Click Publish button
|
|
- [ ] Verify post created in WordPress
|
|
- [ ] Check post has correct title and content
|
|
- [ ] Verify SEO meta fields populated
|
|
- [ ] Check featured image attached
|
|
- [ ] Verify categories/tags assigned
|
|
- [ ] Confirm cluster/sector taxonomies set
|
|
- [ ] Check all `_igny8_*` meta fields present
|
|
|
|
**WordPress → IGNY8 sync**
|
|
- [ ] Create IGNY8 content and publish to WP
|
|
- [ ] Edit post in WordPress (change status to draft)
|
|
- [ ] Check IGNY8 task status updated to 'draft'
|
|
- [ ] Publish post in WordPress
|
|
- [ ] Check IGNY8 task status updated to 'completed'
|
|
- [ ] Verify `wordpress_status` field shows 'publish'
|
|
|
|
**Site Metadata Discovery**
|
|
- [ ] Call `GET /wp-json/igny8/v1/site-metadata/`
|
|
- [ ] Verify post types listed
|
|
- [ ] Check taxonomies included
|
|
- [ ] Confirm counts accurate
|
|
- [ ] Check WooCommerce detected (if installed)
|
|
|
|
### Code Snippets
|
|
|
|
**WordPress: Get IGNY8 Content ID from Post**
|
|
```php
|
|
$content_id = get_post_meta($post_id, '_igny8_content_id', true);
|
|
if ($content_id) {
|
|
echo "This post is IGNY8 Content ID: " . $content_id;
|
|
}
|
|
```
|
|
|
|
**WordPress: Check if Post is IGNY8-Managed**
|
|
```php
|
|
function is_igny8_post($post_id) {
|
|
$task_id = get_post_meta($post_id, '_igny8_task_id', true);
|
|
return !empty($task_id);
|
|
}
|
|
```
|
|
|
|
**IGNY8: Get WordPress Post ID from Content**
|
|
```python
|
|
from igny8_core.business.content.models import Content
|
|
|
|
content = Content.objects.get(id=123)
|
|
if content.external_id:
|
|
print(f"WordPress Post ID: {content.external_id}")
|
|
print(f"WordPress URL: {content.external_url}")
|
|
```
|
|
|
|
**IGNY8: Manually Trigger Publish**
|
|
```python
|
|
from igny8_core.tasks.wordpress_publishing import publish_content_to_wordpress
|
|
|
|
# Queue Celery task
|
|
publish_content_to_wordpress.delay(
|
|
content_id=123,
|
|
site_integration_id=1
|
|
)
|
|
```
|
|
|
|
**WordPress: Call IGNY8 API**
|
|
```php
|
|
$api = new Igny8API();
|
|
$response = $api->get('/writer/tasks/123/');
|
|
|
|
if ($response['success']) {
|
|
$task = $response['data'];
|
|
echo $task['title'];
|
|
}
|
|
```
|
|
|
|
### Useful Queries
|
|
|
|
**WordPress: Find all IGNY8-managed posts**
|
|
```sql
|
|
SELECT p.ID, p.post_title, pm.meta_value as content_id
|
|
FROM wp_posts p
|
|
INNER JOIN wp_postmeta pm ON p.ID = pm.post_id
|
|
WHERE pm.meta_key = '_igny8_content_id'
|
|
ORDER BY p.ID DESC;
|
|
```
|
|
|
|
**IGNY8: Find all content published to WordPress**
|
|
```sql
|
|
SELECT id, title, external_id, external_url, sync_status
|
|
FROM igny8_content
|
|
WHERE external_id IS NOT NULL
|
|
ORDER BY updated_at DESC;
|
|
```
|
|
|
|
**WordPress: Find posts missing IGNY8 sync**
|
|
```sql
|
|
SELECT p.ID, p.post_title
|
|
FROM wp_posts p
|
|
LEFT JOIN wp_postmeta pm ON p.ID = pm.post_id AND pm.meta_key = '_igny8_content_id'
|
|
WHERE p.post_type = 'post'
|
|
AND p.post_status = 'publish'
|
|
AND pm.meta_value IS NULL;
|
|
```
|
|
|
|
### Integration Roadmap
|
|
|
|
**Phase 1: Core Publishing (✅ Complete)**
|
|
- [x] IGNY8 → WordPress content publishing
|
|
- [x] WordPress → IGNY8 status sync
|
|
- [x] Authentication (JWT + API Key)
|
|
- [x] SEO metadata sync
|
|
- [x] Featured images
|
|
- [x] Taxonomies (categories, tags, clusters, sectors)
|
|
|
|
**Phase 2: Enhanced Sync (🔄 In Progress)**
|
|
- [x] Site metadata discovery
|
|
- [ ] Automated content import from WordPress
|
|
- [ ] Bulk publishing
|
|
- [ ] Scheduled publishing
|
|
- [ ] Content update sync (detect WordPress edits)
|
|
|
|
**Phase 3: Advanced Features (📋 Planned)**
|
|
- [ ] Internal linking recommendations
|
|
- [ ] Content optimization scoring
|
|
- [ ] Link graph sync
|
|
- [ ] Automated interlinking
|
|
- [ ] Content analytics sync
|
|
|
|
**Phase 4: Automation (📋 Planned)**
|
|
- [ ] Webhooks (IGNY8 → WordPress events)
|
|
- [ ] WP-CLI commands
|
|
- [ ] Automated content refresh
|
|
- [ ] SEO score monitoring
|
|
- [ ] Broken link detection
|
|
|
|
---
|
|
|
|
## Appendix: Data Flow Diagrams
|
|
|
|
### Complete Publishing Architecture
|
|
|
|
```
|
|
┌────────────────────────────────────────────────────────────────┐
|
|
│ IGNY8 SaaS Platform │
|
|
├────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
│ │ Frontend (React) │ │
|
|
│ │ ──────────────── │ │
|
|
│ │ Review.tsx → Publish Button │ │
|
|
│ │ POST /api/v1/publisher/publish/ │ │
|
|
│ │ {content_id: 123, destinations: ['wordpress']} │ │
|
|
│ └────────────────────┬─────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
│ │ API Layer (Django REST Framework) │ │
|
|
│ │ ────────────────────────────────── │ │
|
|
│ │ PublisherViewSet.publish() │ │
|
|
│ │ - Validate request │ │
|
|
│ │ - Call PublisherService │ │
|
|
│ └────────────────────┬─────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
│ │ Business Logic (Services) │ │
|
|
│ │ ───────────────────────────── │ │
|
|
│ │ PublisherService.publish_content() │ │
|
|
│ │ 1. Load Content from DB │ │
|
|
│ │ 2. Create PublishingRecord │ │
|
|
│ │ 3. Get SiteIntegration config │ │
|
|
│ │ 4. Call WordPressAdapter │ │
|
|
│ └────────────────────┬─────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
│ │ Adapter Layer (Platform-Specific) │ │
|
|
│ │ ────────────────────────────────── │ │
|
|
│ │ WordPressAdapter.publish() │ │
|
|
│ │ - Detect auth method (API key vs user/pass) │ │
|
|
│ │ - Prepare payload │ │
|
|
│ │ - POST to WordPress REST API │ │
|
|
│ │ - Handle response │ │
|
|
│ └────────────────────┬─────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ │ HTTPS Request │
|
|
│ │ POST /wp-json/igny8/v1/publish-content/ │
|
|
│ │ X-IGNY8-API-KEY: *** │
|
|
└───────────────────────┼─────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌────────────────────────────────────────────────────────────────┐
|
|
│ WordPress Site (yoursite.com) │
|
|
├────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
│ │ WordPress REST API │ │
|
|
│ │ ────────────────── │ │
|
|
│ │ /wp-json/igny8/v1/publish-content/ │ │
|
|
│ │ - Validate API key │ │
|
|
│ │ - Parse request │ │
|
|
│ │ - Call content creation handler │ │
|
|
│ └────────────────────┬─────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
│ │ Content Handler (PHP) │ │
|
|
│ │ ───────────────────────── │ │
|
|
│ │ igny8_create_wordpress_post_from_task() │ │
|
|
│ │ 1. Resolve post type │ │
|
|
│ │ 2. Prepare post data │ │
|
|
│ │ 3. wp_insert_post() │ │
|
|
│ │ 4. Import SEO metadata │ │
|
|
│ │ 5. Import featured image │ │
|
|
│ │ 6. Set taxonomies │ │
|
|
│ │ 7. Store IGNY8 meta fields │ │
|
|
│ └────────────────────┬─────────────────────────────────────┘ │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
│ │ WordPress Database │ │
|
|
│ │ ────────────────── │ │
|
|
│ │ wp_posts │ │
|
|
│ │ - ID, post_title, post_content, post_status │ │
|
|
│ │ │ │
|
|
│ │ wp_postmeta │ │
|
|
│ │ - _igny8_content_id, _igny8_task_id, _igny8_cluster_id │ │
|
|
│ │ - _igny8_meta_title, _igny8_meta_description │ │
|
|
│ │ │ │
|
|
│ │ wp_term_relationships │ │
|
|
│ │ - Categories, Tags, Clusters, Sectors │ │
|
|
│ └──────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
**End of Document**
|
|
|
|
For questions or issues, consult:
|
|
- Main documentation: `/docs/MASTER_REFERENCE.md`
|
|
- Plugin docs: `/igny8-wp-integration/docs/WORDPRESS-PLUGIN-INTEGRATION.md`
|
|
- Publishing fixes: `/igny8/FIXES-PUBLISH-FAILURE.md`
|
|
- GitHub Issues: [Repository Issues](https://github.com/alorig/igny8-wp-integration/issues)
|