This commit is contained in:
IGNY8 VPS (Salman)
2025-12-01 06:47:13 +00:00
parent 0af40c0929
commit 7357846527
4 changed files with 1339 additions and 17 deletions

View File

@@ -0,0 +1,334 @@
# 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)

View File

@@ -0,0 +1,744 @@
# IGNY8 WordPress Plugin - Complete Refactor Plan
**Created**: 2025-12-01
**Scope**: Simplify to one-way publishing only, remove all automatic sync, fix broken features
---
## 🎯 Refactor Goals
1. **One-Way Publishing Only**: IGNY8 → WordPress, no bidirectional sync
2. **Immediate Response**: WordPress returns all IDs right after post creation
3. **Clean UI**: Proper meta boxes for keywords, SEO, and tracking data
4. **Pure Taxonomies**: Cluster/sector as taxonomies only (remove post_meta duplication)
5. **Working Images**: Fix gallery image saving
6. **User Control**: Draft vs publish setting in WP admin
7. **Remove Complexity**: Delete all automatic sync, cron jobs, hooks
---
## 📋 Refactor Tasks
### Phase 1: Remove Automatic Sync (Clean Up)
#### Task 1.1: Delete Sync Hooks
**File**: `sync/hooks.php`
**Action**: Delete entire file
**Reason**: All automatic sync hooks removed
**Files to remove:**
```
sync/hooks.php (DELETE)
sync/post-sync.php (DELETE - bidirectional sync)
sync/taxonomy-sync.php (DELETE - bidirectional sync)
```
**Code to remove from other files:**
- Remove `require_once 'sync/hooks.php'` from main plugin file
- Remove all cron job registrations
- Remove all `save_post`, `publish_post`, `transition_post_status` hooks
---
#### Task 1.2: Remove Brief Meta Box
**File**: `admin/class-post-meta-boxes.php`
**Changes**:
```php
// REMOVE these lines from add_meta_boxes() method:
add_meta_box(
'igny8-planner-brief',
__('IGNY8 Planner Brief', 'igny8-bridge'),
array($this, 'render_planner_brief_box'),
$post_type,
'side',
'default'
);
// REMOVE entire method:
public function render_planner_brief_box($post) { ... }
// REMOVE AJAX handlers:
add_action('wp_ajax_igny8_fetch_planner_brief', array($this, 'fetch_planner_brief'));
add_action('wp_ajax_igny8_refresh_planner_task', array($this, 'refresh_planner_task'));
// REMOVE methods:
public function fetch_planner_brief() { ... }
public function refresh_planner_task() { ... }
```
**Reason**: No brief data exists in IGNY8
---
#### Task 1.3: Clean Up task_id References
**Investigation needed**: Determine if `_igny8_task_id` is:
- Writer task (remove completely)
- Celery task for async operations (keep for tracking)
**Action**: If writer task, remove all references to `_igny8_task_id`
---
### Phase 2: Fix Core Publishing
#### Task 2.1: Fix Gallery Images Function
**File**: `sync/igny8-to-wp.php`
**Current**: Line 290 calls `igny8_set_gallery_images()` but function is named `igny8_set_image_gallery()`
**Fix**:
```php
// Option 1: Rename function call
if (!empty($content_data['gallery_images'])) {
Igny8_Logger::info("{$log_prefix} STEP: Setting gallery with " . count($content_data['gallery_images']) . " images");
igny8_set_image_gallery($post_id, $content_data['gallery_images']); // Changed from igny8_set_gallery_images
}
// OR Option 2: Add alias function
function igny8_set_gallery_images($post_id, $gallery_images) {
return igny8_set_image_gallery($post_id, $gallery_images);
}
```
**Test**: Verify gallery images are saved to `_igny8_gallery_images` post_meta
---
#### Task 2.2: Fix Cluster/Sector Storage
**File**: `sync/igny8-to-wp.php`
**Current**: Lines 141-175 save cluster_id and sector_id as post_meta
**Remove these lines**:
```php
// REMOVE (lines ~163-175):
if (!empty($content_data['cluster_id'])) {
$post_data['meta_input']['_igny8_cluster_id'] = $content_data['cluster_id'];
}
if (!empty($content_data['sector_id'])) {
$post_data['meta_input']['_igny8_sector_id'] = $content_data['sector_id'];
}
```
**Keep only taxonomy assignment** (lines ~195-230):
```php
// KEEP: This correctly assigns taxonomies
if (!empty($content_data['cluster_id'])) {
$cluster_terms = get_terms(array(
'taxonomy' => 'igny8_clusters',
'meta_key' => '_igny8_cluster_id',
'meta_value' => $content_data['cluster_id'],
'hide_empty' => false
));
if (!is_wp_error($cluster_terms) && !empty($cluster_terms)) {
wp_set_post_terms($post_id, array($cluster_terms[0]->term_id), 'igny8_clusters');
}
}
// Same for sector...
```
**Issue**: This searches for terms by meta_key, but terms need to exist first!
**Better approach**:
```php
if (!empty($content_data['cluster_id'])) {
// Get cluster name from IGNY8 (need to send cluster_name in payload)
$cluster_name = $content_data['cluster_name'] ?? '';
if (!empty($cluster_name)) {
$term = wp_insert_term($cluster_name, 'igny8_clusters', array(
'slug' => sanitize_title($cluster_name)
));
if (!is_wp_error($term)) {
// Store IGNY8 cluster_id as term meta for future lookups
update_term_meta($term['term_id'], '_igny8_cluster_id', $content_data['cluster_id']);
wp_set_post_terms($post_id, array($term['term_id']), 'igny8_clusters');
}
}
}
```
**Backend change needed**: `wordpress_adapter.py` must send `cluster_name` and `sector_name` in payload
---
#### Task 2.3: Add Draft/Publish Setting
**File**: `admin/settings.php`
**Add new setting field**:
```php
// In settings registration (around line ~100):
add_settings_field(
'igny8_default_post_status',
__('Default Post Status', 'igny8-bridge'),
'igny8_render_default_post_status_field',
'igny8-settings',
'igny8_settings_section'
);
// Add field renderer:
function igny8_render_default_post_status_field() {
$status = get_option('igny8_default_post_status', 'draft');
?>
<fieldset>
<label>
<input type="radio" name="igny8_default_post_status" value="draft" <?php checked($status, 'draft'); ?>>
<?php _e('Draft - Save as draft for review', 'igny8-bridge'); ?>
</label><br>
<label>
<input type="radio" name="igny8_default_post_status" value="publish" <?php checked($status, 'publish'); ?>>
<?php _e('Publish - Publish immediately', 'igny8-bridge'); ?>
</label>
<p class="description">
<?php _e('Choose whether content from IGNY8 should be published immediately or saved as draft.', 'igny8-bridge'); ?>
</p>
</fieldset>
<?php
}
```
**File**: `sync/igny8-to-wp.php`
**Use setting in post creation** (line ~122):
```php
// OLD:
'post_status' => igny8_map_igny8_status_to_wp($content_data['status'] ?? 'draft'),
// NEW:
'post_status' => get_option('igny8_default_post_status', 'draft'),
```
---
#### Task 2.4: Return All Term IDs Immediately
**File**: `includes/class-igny8-rest-api.php`
**Modify `publish_content_to_wordpress()` return** (around line 615):
```php
// After post creation (line ~605), collect term IDs:
$term_ids = array(
'categories' => array(),
'tags' => array(),
'igny8_clusters' => array(),
'igny8_sectors' => array()
);
// Get assigned category IDs
$category_terms = wp_get_post_terms($post_id, 'category', array('fields' => 'ids'));
if (!is_wp_error($category_terms)) {
$term_ids['categories'] = $category_terms;
}
// Get assigned tag IDs
$tag_terms = wp_get_post_terms($post_id, 'post_tag', array('fields' => 'ids'));
if (!is_wp_error($tag_terms)) {
$term_ids['tags'] = $tag_terms;
}
// Get assigned cluster IDs
$cluster_terms = wp_get_post_terms($post_id, 'igny8_clusters', array('fields' => 'ids'));
if (!is_wp_error($cluster_terms)) {
$term_ids['igny8_clusters'] = $cluster_terms;
}
// Get assigned sector IDs
$sector_terms = wp_get_post_terms($post_id, 'igny8_sectors', array('fields' => 'ids'));
if (!is_wp_error($sector_terms)) {
$term_ids['igny8_sectors'] = $sector_terms;
}
// Return enhanced response:
return $this->build_unified_response(
true,
array(
'post_id' => $post_id,
'post_url' => get_permalink($post_id),
'post_status' => get_post_status($post_id),
'content_id' => $content_id,
'task_id' => $task_id,
'term_ids' => $term_ids // NEW
),
'Content successfully published to WordPress',
null,
null,
201
);
```
**Backend change needed**: `wordpress_adapter.py` must capture and save `term_ids` from response
---
### Phase 3: Add Custom Meta Boxes
#### Task 3.1: Add IGNY8 Keywords Meta Box
**File**: `admin/class-post-meta-boxes.php`
**Add meta box registration**:
```php
public function add_meta_boxes() {
$post_types = array('post', 'page', 'product');
foreach ($post_types as $post_type) {
// NEW: IGNY8 Keywords
add_meta_box(
'igny8-keywords',
__('IGNY8 Keywords', 'igny8-bridge'),
array($this, 'render_keywords_box'),
$post_type,
'side',
'high'
);
// NEW: IGNY8 SEO
add_meta_box(
'igny8-seo',
__('IGNY8 SEO', 'igny8-bridge'),
array($this, 'render_seo_box'),
$post_type,
'normal',
'high'
);
// NEW: IGNY8 Sync Data (read-only)
add_meta_box(
'igny8-sync-data',
__('IGNY8 Sync Data', 'igny8-bridge'),
array($this, 'render_sync_data_box'),
$post_type,
'side',
'low'
);
// KEEP: IGNY8 Optimizer (existing)
add_meta_box(
'igny8-optimizer',
__('IGNY8 Optimizer', 'igny8-bridge'),
array($this, 'render_optimizer_box'),
$post_type,
'side',
'default'
);
}
}
```
**Add render methods**:
```php
/**
* Render Keywords meta box
*/
public function render_keywords_box($post) {
$primary_keyword = get_post_meta($post->ID, '_igny8_primary_keyword', true);
$secondary_keywords = get_post_meta($post->ID, '_igny8_secondary_keywords', true);
// Decode JSON if needed
if (is_string($secondary_keywords)) {
$secondary_keywords = json_decode($secondary_keywords, true);
}
if (!is_array($secondary_keywords)) {
$secondary_keywords = array();
}
wp_nonce_field('igny8_keywords_nonce', 'igny8_keywords_nonce');
?>
<div class="igny8-keywords-box">
<p>
<label for="igny8_primary_keyword">
<strong><?php _e('Primary Keyword', 'igny8-bridge'); ?></strong>
</label>
<input type="text"
id="igny8_primary_keyword"
name="igny8_primary_keyword"
value="<?php echo esc_attr($primary_keyword); ?>"
class="widefat"
placeholder="<?php _e('Enter primary keyword', 'igny8-bridge'); ?>">
</p>
<p>
<label for="igny8_secondary_keywords">
<strong><?php _e('Secondary Keywords', 'igny8-bridge'); ?></strong>
</label>
<input type="text"
id="igny8_secondary_keywords"
name="igny8_secondary_keywords"
value="<?php echo esc_attr(implode(', ', $secondary_keywords)); ?>"
class="widefat"
placeholder="<?php _e('keyword1, keyword2, keyword3', 'igny8-bridge'); ?>">
<span class="description"><?php _e('Separate keywords with commas', 'igny8-bridge'); ?></span>
</p>
<?php if (!empty($primary_keyword) || !empty($secondary_keywords)) : ?>
<p class="description">
<em><?php _e('✅ These keywords were set by IGNY8', 'igny8-bridge'); ?></em>
</p>
<?php endif; ?>
</div>
<?php
}
/**
* Render SEO meta box
*/
public function render_seo_box($post) {
$meta_title = get_post_meta($post->ID, '_igny8_meta_title', true);
$meta_description = get_post_meta($post->ID, '_igny8_meta_description', true);
wp_nonce_field('igny8_seo_nonce', 'igny8_seo_nonce');
?>
<div class="igny8-seo-box">
<p>
<label for="igny8_meta_title">
<strong><?php _e('SEO Title', 'igny8-bridge'); ?></strong>
</label>
<input type="text"
id="igny8_meta_title"
name="igny8_meta_title"
value="<?php echo esc_attr($meta_title); ?>"
class="widefat"
placeholder="<?php _e('Enter SEO title', 'igny8-bridge'); ?>">
<span class="description"><?php _e('Recommended: 50-60 characters', 'igny8-bridge'); ?></span>
</p>
<p>
<label for="igny8_meta_description">
<strong><?php _e('SEO Meta Description', 'igny8-bridge'); ?></strong>
</label>
<textarea id="igny8_meta_description"
name="igny8_meta_description"
rows="3"
class="widefat"
placeholder="<?php _e('Enter meta description', 'igny8-bridge'); ?>"><?php echo esc_textarea($meta_description); ?></textarea>
<span class="description"><?php _e('Recommended: 150-160 characters', 'igny8-bridge'); ?></span>
</p>
<?php if (!empty($meta_title) || !empty($meta_description)) : ?>
<p class="description">
<em><?php _e('✅ These SEO fields were set by IGNY8', 'igny8-bridge'); ?></em>
</p>
<?php endif; ?>
</div>
<?php
}
/**
* Render Sync Data meta box (read-only)
*/
public function render_sync_data_box($post) {
$content_id = get_post_meta($post->ID, '_igny8_content_id', true);
$content_type = get_post_meta($post->ID, '_igny8_content_type', true);
$content_structure = get_post_meta($post->ID, '_igny8_content_structure', true);
$cluster_id = get_post_meta($post->ID, '_igny8_cluster_id', true);
$sector_id = get_post_meta($post->ID, '_igny8_sector_id', true);
?>
<div class="igny8-sync-data-box">
<table class="widefat striped">
<tbody>
<?php if ($content_id) : ?>
<tr>
<td><strong><?php _e('Content ID', 'igny8-bridge'); ?></strong></td>
<td><code><?php echo esc_html($content_id); ?></code></td>
</tr>
<?php endif; ?>
<?php if ($content_type) : ?>
<tr>
<td><strong><?php _e('Content Type', 'igny8-bridge'); ?></strong></td>
<td><?php echo esc_html($content_type); ?></td>
</tr>
<?php endif; ?>
<?php if ($content_structure) : ?>
<tr>
<td><strong><?php _e('Structure', 'igny8-bridge'); ?></strong></td>
<td><?php echo esc_html($content_structure); ?></td>
</tr>
<?php endif; ?>
<?php if ($cluster_id) : ?>
<tr>
<td><strong><?php _e('Cluster ID', 'igny8-bridge'); ?></strong></td>
<td><code><?php echo esc_html($cluster_id); ?></code></td>
</tr>
<?php endif; ?>
<?php if ($sector_id) : ?>
<tr>
<td><strong><?php _e('Sector ID', 'igny8-bridge'); ?></strong></td>
<td><code><?php echo esc_html($sector_id); ?></code></td>
</tr>
<?php endif; ?>
</tbody>
</table>
<?php if ($content_id) : ?>
<p class="description">
<em><?php _e('This post was published from IGNY8', 'igny8-bridge'); ?></em>
</p>
<?php else : ?>
<p class="description">
<em><?php _e('This post was not created by IGNY8', 'igny8-bridge'); ?></em>
</p>
<?php endif; ?>
</div>
<?php
}
```
**Add save handlers**:
```php
/**
* Save keywords when post is saved
*/
public function save_keywords($post_id) {
// Check nonce
if (!isset($_POST['igny8_keywords_nonce']) || !wp_verify_nonce($_POST['igny8_keywords_nonce'], 'igny8_keywords_nonce')) {
return;
}
// Check autosave
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
// Check permissions
if (!current_user_can('edit_post', $post_id)) {
return;
}
// Save primary keyword
if (isset($_POST['igny8_primary_keyword'])) {
update_post_meta($post_id, '_igny8_primary_keyword', sanitize_text_field($_POST['igny8_primary_keyword']));
}
// Save secondary keywords
if (isset($_POST['igny8_secondary_keywords'])) {
$keywords = sanitize_text_field($_POST['igny8_secondary_keywords']);
$keywords_array = array_map('trim', explode(',', $keywords));
$keywords_array = array_filter($keywords_array); // Remove empty
update_post_meta($post_id, '_igny8_secondary_keywords', json_encode($keywords_array));
}
}
/**
* Save SEO fields when post is saved
*/
public function save_seo($post_id) {
// Check nonce
if (!isset($_POST['igny8_seo_nonce']) || !wp_verify_nonce($_POST['igny8_seo_nonce'], 'igny8_seo_nonce')) {
return;
}
// Check autosave
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
// Check permissions
if (!current_user_can('edit_post', $post_id)) {
return;
}
// Save meta title
if (isset($_POST['igny8_meta_title'])) {
$meta_title = sanitize_text_field($_POST['igny8_meta_title']);
update_post_meta($post_id, '_igny8_meta_title', $meta_title);
// Also update SEO plugin fields
update_post_meta($post_id, '_yoast_wpseo_title', $meta_title);
update_post_meta($post_id, '_seopress_titles_title', $meta_title);
update_post_meta($post_id, '_aioseo_title', $meta_title);
}
// Save meta description
if (isset($_POST['igny8_meta_description'])) {
$meta_description = sanitize_textarea_field($_POST['igny8_meta_description']);
update_post_meta($post_id, '_igny8_meta_description', $meta_description);
// Also update SEO plugin fields
update_post_meta($post_id, '_yoast_wpseo_metadesc', $meta_description);
update_post_meta($post_id, '_seopress_titles_desc', $meta_description);
update_post_meta($post_id, '_aioseo_description', $meta_description);
}
}
// Register save handlers in constructor:
public function __construct() {
add_action('add_meta_boxes', array($this, 'add_meta_boxes'));
add_action('save_post', array($this, 'save_keywords'), 10, 1);
add_action('save_post', array($this, 'save_seo'), 10, 1);
add_action('admin_enqueue_scripts', array($this, 'enqueue_scripts'));
// Keep optimizer AJAX handler
add_action('wp_ajax_igny8_create_optimizer_job', array($this, 'create_optimizer_job'));
add_action('wp_ajax_igny8_get_optimizer_status', array($this, 'get_optimizer_status'));
}
```
---
### Phase 4: Backend Changes (IGNY8 Django)
#### Task 4.1: Send cluster_name and sector_name
**File**: `backend/igny8_core/business/publishing/services/adapters/wordpress_adapter.py`
**Add to payload** (around line 172):
```python
# Current:
if hasattr(content, 'cluster') and content.cluster:
content_data['cluster_id'] = content.cluster.id
optional_fields.append('cluster_id')
# NEW:
if hasattr(content, 'cluster') and content.cluster:
content_data['cluster_id'] = content.cluster.id
content_data['cluster_name'] = content.cluster.name # ADD THIS
optional_fields.append('cluster_id')
if hasattr(content, 'sector') and content.sector:
content_data['sector_id'] = content.sector.id
content_data['sector_name'] = content.sector.name # ADD THIS
optional_fields.append('sector_id')
```
---
#### Task 4.2: Capture and Save term_ids from Response
**File**: `backend/igny8_core/business/publishing/services/adapters/wordpress_adapter.py`
**Modify response handling** (around line 300):
```python
if response.status_code == 201:
wp_data = response.json().get('data', {})
logger.info(f"[WordPressAdapter._publish_via_api_key] ✅ Success! WordPress post created: post_id={wp_data.get('post_id')}, url={wp_data.get('post_url')}")
# NEW: Extract term_ids
term_ids = wp_data.get('term_ids', {})
return {
'success': True,
'external_id': str(wp_data.get('post_id')),
'url': wp_data.get('post_url'),
'published_at': datetime.now(),
'metadata': {
'post_id': wp_data.get('post_id'),
'status': destination_config.get('status', 'publish'),
'term_ids': term_ids # NEW: Save term mappings
}
}
```
**Optional**: Store term_ids in `content.external_metadata` JSON field for future reference
---
## 🗂️ File Structure After Refactor
```
igny8-wp-plugin/
├── igny8-bridge.php (main plugin file)
├── uninstall.php
├── admin/
│ ├── class-admin.php
│ ├── class-admin-columns.php
│ ├── class-post-meta-boxes.php ✅ UPDATED (new meta boxes, remove brief)
│ ├── settings.php ✅ UPDATED (add draft/publish setting)
│ └── assets/
├── includes/
│ ├── class-igny8-api.php
│ ├── class-igny8-rest-api.php ✅ UPDATED (return term_ids)
│ ├── class-igny8-logger.php
│ ├── class-igny8-webhooks.php (KEEP for future)
│ └── functions.php ✅ UPDATED (ensure taxonomies registered)
├── sync/
│ ├── igny8-to-wp.php ✅ UPDATED (fix gallery, remove post_meta for cluster/sector)
│ ├── hooks.php ❌ DELETE
│ ├── post-sync.php ❌ DELETE
│ └── taxonomy-sync.php ❌ DELETE
├── data/ (keep for site collection/link graph)
└── tests/ (keep for testing)
```
---
## ✅ Testing Checklist
### After Refactor, Test:
1. **Publish from IGNY8**:
- ✅ Post created in WordPress
- ✅ Title, content, excerpt correct
- ✅ Categories assigned correctly
- ✅ Tags assigned correctly
- ✅ Featured image downloaded and set
- ✅ Gallery images downloaded and saved to post_meta
- ✅ Cluster assigned as taxonomy (no post_meta)
- ✅ Sector assigned as taxonomy (no post_meta)
- ✅ Primary keyword saved to `_igny8_primary_keyword`
- ✅ Secondary keywords saved to `_igny8_secondary_keywords` (JSON)
- ✅ Meta title saved to SEO plugin fields
- ✅ Meta description saved to SEO plugin fields
2. **WordPress Response**:
- ✅ Returns post_id immediately
- ✅ Returns post_url immediately
- ✅ Returns term_ids for all taxonomies
- ✅ IGNY8 backend saves external_id, external_url, status='published'
3. **WordPress Editor UI**:
- ✅ "IGNY8 Keywords" meta box shows primary + secondary keywords (editable)
- ✅ "IGNY8 SEO" meta box shows meta_title + meta_description (editable)
- ✅ "IGNY8 Sync Data" meta box shows all tracking fields (read-only)
- ✅ NO "IGNY8 Planner Brief" meta box
- ✅ Cluster and sector show in taxonomy sidebars
- ✅ Editing keywords/SEO saves correctly
4. **WordPress Settings**:
- ✅ "Default Post Status" option exists (draft/publish radio)
- ✅ Changing setting affects next publish
5. **No Automatic Sync**:
- ✅ Editing post in WordPress does NOT trigger API call to IGNY8
- ✅ Publishing post in WordPress does NOT trigger sync
- ✅ No cron jobs running
---
## 📝 Summary of Changes
### Removed:
- ❌ All automatic sync hooks (save_post, publish_post, etc.)
- ❌ Bidirectional sync files (post-sync.php, taxonomy-sync.php, hooks.php)
- ❌ Brief meta box (no data in IGNY8)
- ❌ Cron jobs for sync
- ❌ Post_meta storage for cluster_id and sector_id
### Fixed:
- ✅ Gallery images function name (`igny8_set_gallery_images``igny8_set_image_gallery`)
- ✅ Cluster/sector stored ONLY as taxonomies (with term_meta for IGNY8 ID mapping)
### Added:
- ✅ "IGNY8 Keywords" meta box (primary_keyword, secondary_keywords)
- ✅ "IGNY8 SEO" meta box (meta_title, meta_description)
- ✅ "IGNY8 Sync Data" meta box (read-only tracking fields)
- ✅ WP admin setting: "Default Post Status" (draft/publish)
- ✅ WordPress returns term_ids in publish response
- ✅ Backend sends cluster_name and sector_name for taxonomy creation
- ✅ Backend saves term_ids from WordPress response
---
## 🚀 Implementation Order
1. **Phase 1** (Clean up): Remove sync files, hooks, brief meta box
2. **Phase 2** (Fix core): Gallery images, cluster/sector, draft setting, response enhancement
3. **Phase 3** (Add UI): New meta boxes for keywords, SEO, sync data
4. **Phase 4** (Backend): Update WordPress adapter to send names and capture term_ids
**Estimated time**: 4-6 hours
**Risk level**: Low (mostly removing code, fixing bugs, adding UI)
**Testing requirement**: High (verify all fields save correctly)

View File

@@ -0,0 +1,253 @@
# WordPress Publishing - Simplified Field Reference
**Last Updated**: 2025-12-01
**Purpose**: Simple reference for IGNY8 → WordPress one-way publishing
---
## 🔄 Publishing Flow (One-Way Only)
```
IGNY8 Review.tsx
↓ User clicks "Publish to WordPress"
↓ POST /v1/publisher/publish/
Backend WordPressAdapter
↓ POST {site_url}/wp-json/igny8/v1/publish (with all content data)
WordPress Plugin
↓ wp_insert_post() + taxonomies + images + meta
↓ IMMEDIATE RETURN: {post_id, post_url, term_ids}
IGNY8 Backend
↓ Saves: external_id, external_url, status='published'
✅ DONE - No automatic sync, no bidirectional updates
```
**Publishing Timing**: Immediate (synchronous) - WordPress returns data right after post creation
---
## 📊 Published Fields Reference
| Field | IGNY8 → WordPress | WordPress Stores As | Notes |
|-------|-------------------|---------------------|-------|
| **CORE CONTENT** |
| title | `title` | `wp_posts.post_title` | Required |
| content_html | `content_html` | `wp_posts.post_content` | Required, sanitized |
| status | `status` | `wp_posts.post_status` | User-configurable: draft or publish (WP admin setting) |
| **SEO FIELDS** (Need Meta Boxes) |
| meta_title | `seo_title` OR `meta_title` | `_yoast_wpseo_title`, `_seopress_titles_title`, `_aioseo_title`, `_igny8_meta_title` | **⚠️ No UI in WP editor yet** |
| meta_description | `seo_description` OR `meta_description` | `_yoast_wpseo_metadesc`, `_seopress_titles_desc`, `_aioseo_description`, `_igny8_meta_description` | **⚠️ No UI in WP editor yet** |
| **KEYWORDS** (Need Meta Boxes) |
| primary_keyword | `primary_keyword` | `_igny8_primary_keyword` (post_meta) + added as tag | **⚠️ Should be custom field, not just tag** |
| secondary_keywords | `secondary_keywords` (JSON array) | `_igny8_secondary_keywords` (post_meta) + each added as tag | **⚠️ Should be custom field, not just tag** |
| **TAXONOMIES** |
| categories (from taxonomy_terms) | `categories` (array of names) | `wp_term_relationships` (category taxonomy) | ✅ Working |
| tags (from taxonomy_terms) | `tags` (array of names) | `wp_term_relationships` (post_tag taxonomy) | ✅ Working |
| **CUSTOM TAXONOMIES** (Currently Broken) |
| cluster | `cluster_id` | ~~`_igny8_cluster_id` (post_meta)~~ + `igny8_clusters` (taxonomy) | **⚠️ Currently saved as post_meta, should ONLY be taxonomy** |
| sector | `sector_id` | ~~`_igny8_sector_id` (post_meta)~~ + `igny8_sectors` (taxonomy) | **⚠️ Currently saved as post_meta, should ONLY be taxonomy** |
| **IMAGES** |
| featured_image | `featured_image_url` | `_thumbnail_id` (post_meta) → attachment | ✅ Working |
| gallery_images | `gallery_images` (array) | `_igny8_gallery_images`, `_product_image_gallery`, `_gallery_images` | **❌ BROKEN - Not saving** |
| **TRACKING FIELDS** (Show in Meta Box) |
| content_id | `content_id` | `_igny8_content_id` (post_meta) | ✅ Saved, **⚠️ Not visible in editor** |
| content_type | `content_type` | `_igny8_content_type` (post_meta) | ✅ Saved, **⚠️ Not visible in editor** |
| content_structure | `content_structure` | `_igny8_content_structure` (post_meta) | ✅ Saved, **⚠️ Not visible in editor** |
| **AUTO-GENERATED** |
| excerpt | Generated from content_html (first 150 chars) | `wp_posts.post_excerpt` | ✅ Working |
| **RETURN VALUES** (WordPress → IGNY8) |
| - | `post_id` | Returned immediately | Saved to `content.external_id` |
| - | `post_url` | Returned immediately | Saved to `content.external_url` |
| - | `category_term_ids` | Returned immediately | **⚠️ Need to return for mapping** |
| - | `tag_term_ids` | Returned immediately | **⚠️ Need to return for mapping** |
| - | `cluster_term_id` | Returned immediately | **⚠️ Need to return for mapping** |
| - | `sector_term_id` | Returned immediately | **⚠️ Need to return for mapping** |
---
## 🔧 Required Fixes
### 1. **Add Meta Boxes in WordPress Post Editor**
**Missing UI for these 4 fields:**
- `primary_keyword` - Custom field (text input)
- `secondary_keywords` - Custom field (tag-style input)
- `meta_title` - SEO field (text input)
- `meta_description` - SEO field (textarea)
**Action**: Create new meta boxes in `class-post-meta-boxes.php`
---
### 2. **Fix Cluster/Sector as Pure Taxonomies**
**Current**: Saved as both post_meta AND taxonomy
**Required**: Save ONLY as taxonomy, remove post_meta storage
**Code locations to fix:**
- `sync/igny8-to-wp.php` lines 141-175 (remove `_igny8_cluster_id`, `_igny8_sector_id` post_meta)
- Keep only `wp_set_post_terms()` for `igny8_clusters` and `igny8_sectors` taxonomies
---
### 3. **Fix Gallery Images Not Saving**
**Current**: Function `igny8_set_gallery_images()` doesn't exist, only `igny8_set_image_gallery()` exists
**Issue**: Called at line 290 but function is named differently
**Action**: Fix function name or create alias
---
### 4. **Add Draft/Publish Setting in WP Admin**
**Location**: `admin/settings.php`
**Add option**: "Default status for IGNY8 content" → Radio: Draft / Publish
**Use in**: `igny8_create_wordpress_post_from_task()` to override `post_status`
---
### 5. **Show All IGNY8 Meta in Post Editor**
**Create meta box displaying:**
- Content ID: `_igny8_content_id`
- Content Type: `_igny8_content_type`
- Content Structure: `_igny8_content_structure`
- Cluster ID: `_igny8_cluster_id` (until removed)
- Sector ID: `_igny8_sector_id` (until removed)
- All other `_igny8_*` meta fields
**Action**: Add "IGNY8 Sync Data" meta box (read-only)
---
### 6. **Immediate Response with All IDs**
**Current return**:
```json
{
"post_id": 123,
"post_url": "https://...",
"post_status": "draft"
}
```
**Required return**:
```json
{
"post_id": 123,
"post_url": "https://...",
"post_status": "draft",
"term_ids": {
"categories": [45, 67],
"tags": [12, 34, 56],
"igny8_clusters": [89],
"igny8_sectors": [101]
}
}
```
**Action**: Modify `publish_content_to_wordpress()` to collect and return all term IDs
---
### 7. **Remove All Automatic Sync Code**
**Remove these files/functions:**
- `sync/hooks.php` - All `save_post`, `publish_post`, cron hooks
- `sync/post-sync.php` - All WordPress → IGNY8 sync functions
- `sync/taxonomy-sync.php` - Bidirectional taxonomy sync
- Cron jobs: `igny8_sync_post_statuses`, `igny8_sync_from_igny8`, etc.
**Keep only:**
- One-way publish: IGNY8 → WordPress via `/wp-json/igny8/v1/publish`
- Immediate response after post creation
---
### 8. **Remove Brief Meta Box**
**Current**: `class-post-meta-boxes.php` has "IGNY8 Planner Brief" meta box
**Issue**: No `brief` data exists in IGNY8
**Action**: Remove entire brief meta box and related AJAX handlers
---
### 9. **Clean Up task_id Usage**
**Current**: `_igny8_task_id` stored in post_meta
**Question**: Is this Celery task_id or writer task_id?
**Action**: If writer task, remove. If Celery, keep for tracking async operations.
---
## 🗂️ WordPress Database After Publishing
### Post Data
```
wp_posts:
- post_title (from title)
- post_content (from content_html)
- post_excerpt (auto-generated)
- post_status (draft or publish, from WP admin setting)
- post_author (mapped)
```
### Post Meta
```
wp_postmeta:
- _igny8_content_id (tracking)
- _igny8_content_type (tracking)
- _igny8_content_structure (tracking)
- _igny8_primary_keyword (NEW - custom field)
- _igny8_secondary_keywords (NEW - custom field, JSON)
- _igny8_meta_title (SEO)
- _igny8_meta_description (SEO)
- _yoast_wpseo_title (SEO plugin compatibility)
- _yoast_wpseo_metadesc (SEO plugin compatibility)
- _thumbnail_id (featured image)
- _igny8_gallery_images (gallery attachment IDs)
```
### Taxonomies
```
wp_term_relationships:
- category (standard WP categories)
- post_tag (standard WP tags)
- igny8_clusters (custom taxonomy)
- igny8_sectors (custom taxonomy)
```
### Attachments
```
wp_posts (type=attachment):
- Featured image (downloaded from featured_image_url)
- Gallery images (downloaded from gallery_images[].url)
```
---
## 🔍 Code Locations
### Backend (IGNY8)
- **Publisher**: `backend/igny8_core/business/publishing/services/adapters/wordpress_adapter.py`
- **Line 147-253**: Prepares payload with all fields
### WordPress Plugin
- **Endpoint**: `includes/class-igny8-rest-api.php` line 490
- **Post Creation**: `sync/igny8-to-wp.php` line 73
- **Taxonomies**: `includes/functions.php` line 465 (registration)
- **Meta Boxes**: `admin/class-post-meta-boxes.php`
- **Settings**: `admin/settings.php`
---
## ✅ What Works Now
- ✅ Post creation with title, content, excerpt
- ✅ Categories and tags from taxonomy_terms
- ✅ Featured image download and attachment
- ✅ SEO meta saved to plugin fields
- ✅ Cluster/sector taxonomies registered
## ❌ What's Broken
- ❌ Gallery images not saving (function name mismatch)
- ❌ No UI for primary_keyword, secondary_keywords, meta_title, meta_description
- ❌ Cluster/sector saved as both post_meta AND taxonomy (should be taxonomy only)
- ❌ No visibility of IGNY8 tracking fields in WP editor
- ❌ Brief meta box exists but has no data
- ❌ Automatic sync hooks causing unnecessary API calls
## ⚠️ What's Missing
- ⚠️ User can't choose draft vs publish in WP admin settings
- ⚠️ WordPress doesn't return term_ids for mapping
- ⚠️ Keywords stored as tags only, not as custom fields for editing

View File

@@ -2,8 +2,8 @@
/** /**
* Plugin Name: IGNY8 WordPress Bridge * Plugin Name: IGNY8 WordPress Bridge
* Plugin URI: https://github.com/your-repo/igny8-ai-os * Plugin URI: https://github.com/your-repo/igny8-ai-os
* Description: Lightweight bridge plugin that connects WordPress to IGNY8 API. Syncs posts, taxonomies, and site data bidirectionally. * Description: Lightweight bridge plugin that connects WordPress to IGNY8 API for one-way content publishing.
* Version: 1.0.0 * Version: 1.1.0
* Author: Your Name * Author: Your Name
* Author URI: https://yourwebsite.com * Author URI: https://yourwebsite.com
* License: GPL v2 or later * License: GPL v2 or later
@@ -22,7 +22,7 @@ if (!defined('ABSPATH')) {
} }
// Define plugin constants // Define plugin constants
define('IGNY8_BRIDGE_VERSION', '1.0.0'); define('IGNY8_BRIDGE_VERSION', '1.1.0');
define('IGNY8_BRIDGE_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('IGNY8_BRIDGE_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('IGNY8_BRIDGE_PLUGIN_URL', plugin_dir_url(__FILE__)); define('IGNY8_BRIDGE_PLUGIN_URL', plugin_dir_url(__FILE__));
define('IGNY8_BRIDGE_PLUGIN_FILE', __FILE__); define('IGNY8_BRIDGE_PLUGIN_FILE', __FILE__);
@@ -97,10 +97,7 @@ class Igny8Bridge {
require_once IGNY8_BRIDGE_PLUGIN_DIR . 'admin/class-post-meta-boxes.php'; require_once IGNY8_BRIDGE_PLUGIN_DIR . 'admin/class-post-meta-boxes.php';
} }
// Sync handlers // IGNY8 to WordPress publishing (one-way only)
require_once IGNY8_BRIDGE_PLUGIN_DIR . 'sync/hooks.php';
require_once IGNY8_BRIDGE_PLUGIN_DIR . 'sync/post-sync.php';
require_once IGNY8_BRIDGE_PLUGIN_DIR . 'sync/taxonomy-sync.php';
require_once IGNY8_BRIDGE_PLUGIN_DIR . 'sync/igny8-to-wp.php'; require_once IGNY8_BRIDGE_PLUGIN_DIR . 'sync/igny8-to-wp.php';
// Data collection // Data collection
@@ -134,11 +131,6 @@ class Igny8Bridge {
if (is_admin()) { if (is_admin()) {
Igny8Admin::get_instance(); Igny8Admin::get_instance();
} }
// Initialize sync handlers
if (class_exists('Igny8WordPressSync')) {
new Igny8WordPressSync();
}
} }
/** /**
@@ -157,17 +149,16 @@ class Igny8Bridge {
add_option('igny8_bridge_version', IGNY8_BRIDGE_VERSION); add_option('igny8_bridge_version', IGNY8_BRIDGE_VERSION);
} }
// Schedule cron jobs // Set default post status option
igny8_schedule_cron_jobs(); if (!get_option('igny8_default_post_status')) {
add_option('igny8_default_post_status', 'draft');
}
} }
/** /**
* Plugin deactivation * Plugin deactivation
*/ */
public function deactivate() { public function deactivate() {
// Unschedule cron jobs
igny8_unschedule_cron_jobs();
// Flush rewrite rules // Flush rewrite rules
flush_rewrite_rules(); flush_rewrite_rules();
} }