From 1227df4a41d3a7b85a872126640610a8da86cc6d Mon Sep 17 00:00:00 2001 From: "IGNY8 VPS (Salman)" Date: Fri, 21 Nov 2025 15:54:01 +0000 Subject: [PATCH] 1 --- backend/celerybeat-schedule | Bin 16384 -> 16384 bytes igny8-wp-integration-plugin/.gitattributes | 2 - igny8-wp-integration-plugin/README.md | 396 --- .../admin/assets/css/admin.css | 347 --- .../admin/assets/js/admin.js | 178 -- .../admin/assets/js/post-editor.js | 200 -- .../admin/class-admin-columns.php | 335 --- .../admin/class-admin.php | 566 ----- .../admin/class-post-meta-boxes.php | 469 ---- .../admin/settings.php | 584 ----- .../data/link-graph.php | 192 -- .../data/semantic-mapping.php | 225 -- .../data/site-collection.php | 579 ----- .../data/woocommerce.php | 226 -- .../docs/PHASE_5_IMPLEMENTATION_SUMMARY.md | 185 -- .../docs/PHASE_6_IMPLEMENTATION_SUMMARY.md | 298 --- .../docs/STATUS_SYNC_DOCUMENTATION.md | 249 -- .../docs/STYLE_GUIDE.md | 186 -- .../docs/VERIFICATION_REPORT_PHASES_1-5.md | 430 ---- .../docs/WORDPRESS-PLUGIN-INTEGRATION.md | 2135 ----------------- .../docs/missing-saas-api-endpoints.md | 27 - .../docs/wp-bridge-implementation-plan.md | 84 - igny8-wp-integration-plugin/igny8-bridge.php | 183 -- .../includes/class-igny8-api.php | 343 --- .../includes/class-igny8-link-queue.php | 202 -- .../includes/class-igny8-rest-api.php | 426 ---- .../includes/class-igny8-site.php | 118 - .../includes/class-igny8-webhook-logs.php | 147 -- .../includes/class-igny8-webhooks.php | 381 --- .../includes/functions.php | 605 ----- .../languages/igny8-bridge.pot | 100 - igny8-wp-integration-plugin/sync/hooks.php | 41 - .../sync/igny8-to-wp.php | 807 ------- .../sync/post-sync.php | 363 --- .../sync/taxonomy-sync.php | 425 ---- .../tests/test-revoke-api-key.php | 28 - .../tests/test-site-metadata.php | 36 - igny8-wp-integration-plugin/uninstall.php | 53 - 38 files changed, 12151 deletions(-) delete mode 100644 igny8-wp-integration-plugin/.gitattributes delete mode 100644 igny8-wp-integration-plugin/README.md delete mode 100644 igny8-wp-integration-plugin/admin/assets/css/admin.css delete mode 100644 igny8-wp-integration-plugin/admin/assets/js/admin.js delete mode 100644 igny8-wp-integration-plugin/admin/assets/js/post-editor.js delete mode 100644 igny8-wp-integration-plugin/admin/class-admin-columns.php delete mode 100644 igny8-wp-integration-plugin/admin/class-admin.php delete mode 100644 igny8-wp-integration-plugin/admin/class-post-meta-boxes.php delete mode 100644 igny8-wp-integration-plugin/admin/settings.php delete mode 100644 igny8-wp-integration-plugin/data/link-graph.php delete mode 100644 igny8-wp-integration-plugin/data/semantic-mapping.php delete mode 100644 igny8-wp-integration-plugin/data/site-collection.php delete mode 100644 igny8-wp-integration-plugin/data/woocommerce.php delete mode 100644 igny8-wp-integration-plugin/docs/PHASE_5_IMPLEMENTATION_SUMMARY.md delete mode 100644 igny8-wp-integration-plugin/docs/PHASE_6_IMPLEMENTATION_SUMMARY.md delete mode 100644 igny8-wp-integration-plugin/docs/STATUS_SYNC_DOCUMENTATION.md delete mode 100644 igny8-wp-integration-plugin/docs/STYLE_GUIDE.md delete mode 100644 igny8-wp-integration-plugin/docs/VERIFICATION_REPORT_PHASES_1-5.md delete mode 100644 igny8-wp-integration-plugin/docs/WORDPRESS-PLUGIN-INTEGRATION.md delete mode 100644 igny8-wp-integration-plugin/docs/missing-saas-api-endpoints.md delete mode 100644 igny8-wp-integration-plugin/docs/wp-bridge-implementation-plan.md delete mode 100644 igny8-wp-integration-plugin/igny8-bridge.php delete mode 100644 igny8-wp-integration-plugin/includes/class-igny8-api.php delete mode 100644 igny8-wp-integration-plugin/includes/class-igny8-link-queue.php delete mode 100644 igny8-wp-integration-plugin/includes/class-igny8-rest-api.php delete mode 100644 igny8-wp-integration-plugin/includes/class-igny8-site.php delete mode 100644 igny8-wp-integration-plugin/includes/class-igny8-webhook-logs.php delete mode 100644 igny8-wp-integration-plugin/includes/class-igny8-webhooks.php delete mode 100644 igny8-wp-integration-plugin/includes/functions.php delete mode 100644 igny8-wp-integration-plugin/languages/igny8-bridge.pot delete mode 100644 igny8-wp-integration-plugin/sync/hooks.php delete mode 100644 igny8-wp-integration-plugin/sync/igny8-to-wp.php delete mode 100644 igny8-wp-integration-plugin/sync/post-sync.php delete mode 100644 igny8-wp-integration-plugin/sync/taxonomy-sync.php delete mode 100644 igny8-wp-integration-plugin/tests/test-revoke-api-key.php delete mode 100644 igny8-wp-integration-plugin/tests/test-site-metadata.php delete mode 100644 igny8-wp-integration-plugin/uninstall.php diff --git a/backend/celerybeat-schedule b/backend/celerybeat-schedule index 9f71c945d21f0517692d96c291634f71957468cf..883a4257b095374358a78cd00ce8abab945fe6e5 100644 GIT binary patch delta 34 qcmZo@U~Fh$++b$Lugk!|P&Oq)v~5bz6yIv*$p$9Mn={O2Z~_3gR|>fR delta 34 qcmZo@U~Fh$++b$LFT%jU;5H>gv~5bz6yGxD$p$9Mn={O2Z~_3a2nuTe diff --git a/igny8-wp-integration-plugin/.gitattributes b/igny8-wp-integration-plugin/.gitattributes deleted file mode 100644 index dfe07704..00000000 --- a/igny8-wp-integration-plugin/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -# Auto detect text files and perform LF normalization -* text=auto diff --git a/igny8-wp-integration-plugin/README.md b/igny8-wp-integration-plugin/README.md deleted file mode 100644 index fa7604b8..00000000 --- a/igny8-wp-integration-plugin/README.md +++ /dev/null @@ -1,396 +0,0 @@ -# IGNY8 WordPress Bridge Plugin - -**Version**: 1.0.0 -**Last Updated**: 2025-10-17 -**Requires**: WordPress 5.0+, PHP 7.4+ - ---- - -## Overview - -The IGNY8 WordPress Bridge Plugin is a **lightweight synchronization interface** that connects WordPress sites to the IGNY8 API. This plugin acts as a bridge, not a content management system, using WordPress native structures (taxonomies, post meta) to sync data bidirectionally with IGNY8. - -### Key Principles - -- ✅ **No Custom Database Tables** - Uses WordPress native taxonomies and post meta -- ✅ **Lightweight Bridge** - Minimal code, maximum efficiency -- ✅ **Two-Way Sync** - WordPress ↔ IGNY8 API synchronization -- ✅ **WordPress Native** - Leverages existing WordPress structures -- ✅ **API-First** - IGNY8 API is the source of truth - ---- - -## Features - -### Core Functionality - -1. **API Authentication** - - Secure token management - - Automatic token refresh - - Encrypted credential storage - -2. **Two-Way Synchronization** - - WordPress → IGNY8: Post status changes sync to IGNY8 tasks - - IGNY8 → WordPress: Content published from IGNY8 creates WordPress posts - -3. **Taxonomy Mapping** - - WordPress taxonomies → IGNY8 Sectors/Clusters - - Hierarchical taxonomies map to IGNY8 Sectors - - Taxonomy terms map to IGNY8 Clusters - -4. **Post Meta Integration** - - `_igny8_task_id` - Links WordPress posts to IGNY8 tasks - - `_igny8_cluster_id` - Links posts to IGNY8 clusters - - `_igny8_sector_id` - Links posts to IGNY8 sectors - - `_igny8_keyword_ids` - Links posts to IGNY8 keywords - -5. **Site Data Collection** - - Automatic collection of WordPress posts, taxonomies, products - - Semantic mapping to IGNY8 structure - - WooCommerce integration support - -6. **Status Mapping** - - WordPress post status → IGNY8 task status - - Automatic sync on post save/publish/status change - ---- - -## Installation - -### Requirements - -- WordPress 5.0 or higher -- PHP 7.4 or higher -- WordPress REST API enabled -- IGNY8 API account credentials - -### Installation Steps - -1. **Download/Clone Plugin** - ```bash - git clone [repository-url] - cd igny8-ai-os - ``` - -2. **Install in WordPress** - - Copy the `igny8-ai-os` folder to `/wp-content/plugins/` - - Or create a symlink for development - -3. **Activate Plugin** - - Go to WordPress Admin → Plugins - - Activate "IGNY8 WordPress Bridge" - -4. **Configure API Connection** - - Go to Settings → IGNY8 API - - Enter your IGNY8 email and password - - Click "Connect to IGNY8" - ---- - -## Configuration - -### API Settings - -Navigate to **Settings → IGNY8 API** to configure: - -- **Email**: Your IGNY8 account email -- **Password**: Your IGNY8 account password -- **Site ID**: Your IGNY8 site ID (auto-detected after connection) - -### WordPress Integration - -The plugin automatically: - -1. **Registers Taxonomies** (if needed): - - `sectors` - Maps to IGNY8 Sectors - - `clusters` - Maps to IGNY8 Clusters - -2. **Registers Post Meta Fields**: - - `_igny8_task_id` - - `_igny8_cluster_id` - - `_igny8_sector_id` - - `_igny8_keyword_ids` - - `_igny8_content_id` - -3. **Sets Up WordPress Hooks**: - - `save_post` - Syncs post changes to IGNY8 - - `publish_post` - Updates keywords on publish - - `transition_post_status` - Handles status changes - ---- - -## Usage - -### Basic Workflow - -#### 1. Connect to IGNY8 API - -```php -// Automatically handled via Settings page -// Or programmatically: -$api = new Igny8API(); -$api->login('your@email.com', 'password'); -``` - -#### 2. Sync WordPress Site Data - -```php -// Collect and send site data to IGNY8 -$site_id = get_option('igny8_site_id'); -igny8_send_site_data_to_igny8($site_id); -``` - -#### 3. WordPress → IGNY8 Sync - -When you save/publish a WordPress post: - -1. Plugin checks for `_igny8_task_id` in post meta -2. If found, syncs post status to IGNY8 task -3. If published, updates related keywords to 'mapped' status - -#### 4. IGNY8 → WordPress Sync - -When content is published from IGNY8: - -1. IGNY8 triggers webhook (or scheduled sync) -2. Plugin creates WordPress post via `wp_insert_post()` -3. Post meta saved with `_igny8_task_id` -4. IGNY8 task updated with WordPress post ID - ---- - -## WordPress Structures Used - -### Taxonomies - -- **`sectors`** (hierarchical) - - Maps to IGNY8 Sectors - - Can be created manually or synced from IGNY8 - -- **`clusters`** (hierarchical) - - Maps to IGNY8 Clusters - - Can be created manually or synced from IGNY8 - -- **Native Taxonomies** - - `category` - Can map to IGNY8 Sectors - - `post_tag` - Can be used for keyword extraction - -### Post Meta Fields - -All stored in WordPress `wp_postmeta` table: - -- `_igny8_task_id` (integer) - IGNY8 task ID -- `_igny8_cluster_id` (integer) - IGNY8 cluster ID -- `_igny8_sector_id` (integer) - IGNY8 sector ID -- `_igny8_keyword_ids` (array) - Array of IGNY8 keyword IDs -- `_igny8_content_id` (integer) - IGNY8 content ID -- `_igny8_last_synced` (datetime) - Last sync timestamp - -### Post Status Mapping - -| WordPress Status | IGNY8 Task Status | -|------------------|-------------------| -| `publish` | `completed` | -| `draft` | `draft` | -| `pending` | `pending` | -| `private` | `completed` | -| `trash` | `archived` | -| `future` | `scheduled` | - ---- - -## API Reference - -### Main Classes - -#### `Igny8API` - -Main API client class for all IGNY8 API interactions. - -```php -$api = new Igny8API(); - -// Login -$api->login('email@example.com', 'password'); - -// Get keywords -$response = $api->get('/planner/keywords/'); - -// Create task -$response = $api->post('/writer/tasks/', $data); - -// Update task -$response = $api->put('/writer/tasks/123/', $data); -``` - -#### `Igny8WordPressSync` - -Handles two-way synchronization between WordPress and IGNY8. - -```php -$sync = new Igny8WordPressSync(); -// Automatically hooks into WordPress post actions -``` - -#### `Igny8SiteIntegration` - -Manages site data collection and semantic mapping. - -```php -$integration = new Igny8SiteIntegration($site_id); -$result = $integration->full_site_scan(); -``` - -### Main Functions - -- `igny8_login($email, $password)` - Authenticate with IGNY8 -- `igny8_sync_post_status_to_igny8($post_id, $post, $update)` - Sync post to IGNY8 -- `igny8_collect_site_data()` - Collect all WordPress site data -- `igny8_send_site_data_to_igny8($site_id)` - Send site data to IGNY8 -- `igny8_map_site_to_semantic_strategy($site_id, $site_data)` - Map to semantic structure - -### Site Metadata Endpoint (Plugin) - -The plugin exposes a discovery endpoint that your IGNY8 app can call to learn which WordPress post types and taxonomies exist and how many items each contains. - -- Endpoint: `GET /wp-json/igny8/v1/site-metadata/` -- Auth: Plugin-level connection must be enabled and authenticated (plugin accepts stored API key or access token) -- Response: IGNY8 unified response format (`success`, `data`, `message`, `request_id`) - -Example response: - -```json -{ - "success": true, - "data": { - "post_types": { - "post": { "label": "Posts", "count": 123 }, - "page": { "label": "Pages", "count": 12 } - }, - "taxonomies": { - "category": { "label": "Categories", "count": 25 }, - "post_tag": { "label": "Tags", "count": 102 } - }, - "generated_at": 1700553600 - }, - "message": "Site metadata retrieved", - "request_id": "550e8400-e29b-41d4-a716-446655440000" -} -``` - ---- - -## File Structure - -``` -igny8-ai-os/ -├── igny8-bridge.php # Main plugin file -├── README.md # This file -├── docs/ # Documentation hub -│ ├── README.md # Index of available docs -│ ├── WORDPRESS-PLUGIN-INTEGRATION.md -│ ├── wp-bridge-implementation-plan.md -│ ├── missing-saas-api-endpoints.md -│ ├── STATUS_SYNC_DOCUMENTATION.md -│ └── STYLE_GUIDE.md -├── includes/ -│ ├── class-igny8-api.php # API client class -│ ├── class-igny8-sync.php # Sync handler class -│ ├── class-igny8-site.php # Site integration class -│ └── functions.php # Helper functions -├── admin/ -│ ├── class-admin.php # Admin interface -│ ├── settings.php # Settings page -│ └── assets/ -│ ├── css/ -│ └── js/ -├── sync/ -│ ├── hooks.php # WordPress hooks -│ ├── post-sync.php # Post synchronization -│ └── taxonomy-sync.php # Taxonomy synchronization -├── data/ -│ ├── site-collection.php # Site data collection -│ └── semantic-mapping.php # Semantic mapping -└── uninstall.php # Uninstall handler -``` - ---- - -## Development - -### Code Standards - -- Follow WordPress Coding Standards -- Use WordPress native functions -- No custom database tables -- All data in WordPress native structures - -### Testing - -```bash -# Run WordPress unit tests -phpunit - -# Test API connection -wp eval 'var_dump((new Igny8API())->login("test@example.com", "password"));' -``` - ---- - -## Troubleshooting - -### Authentication Issues - -**Problem**: Cannot connect to IGNY8 API - -**Solutions**: -1. Verify email and password are correct -2. Check API endpoint is accessible -3. Check WordPress REST API is enabled -4. Review error logs in WordPress debug log - -### Sync Issues - -**Problem**: Posts not syncing to IGNY8 - -**Solutions**: -1. Verify `_igny8_task_id` exists in post meta -2. Check API token is valid (not expired) -3. Review WordPress hooks are firing -4. Check error logs - -### Token Expiration - -**Problem**: Token expires frequently - -**Solution**: Plugin automatically refreshes tokens. If issues persist, check token refresh logic. - ---- - -## Support - -- **Documentation**: See `WORDPRESS-PLUGIN-INTEGRATION.md` -- **API Documentation**: https://api.igny8.com/docs -- **Issues**: [GitHub Issues](repository-url/issues) - ---- - -## License - -[Your License Here] - ---- - -## Changelog - -### 1.0.0 - 2025-10-17 -- Initial release -- API authentication -- Two-way sync -- Site data collection -- Semantic mapping - ---- - -**Last Updated**: 2025-10-17 - diff --git a/igny8-wp-integration-plugin/admin/assets/css/admin.css b/igny8-wp-integration-plugin/admin/assets/css/admin.css deleted file mode 100644 index fa2ef1ac..00000000 --- a/igny8-wp-integration-plugin/admin/assets/css/admin.css +++ /dev/null @@ -1,347 +0,0 @@ -/** - * Admin Styles - * - * All styles for IGNY8 Bridge admin interface - * Update this file to change global design - * - * @package Igny8Bridge - */ - -/* ============================================ - Container & Layout - ============================================ */ - -.igny8-settings-container { - max-width: 1200px; -} - -.igny8-settings-card { - background: #fff; - border: 1px solid #ccd0d4; - box-shadow: 0 1px 1px rgba(0,0,0,.04); - padding: 20px; - margin: 20px 0; -} - -.igny8-settings-card h2 { - margin-top: 0; - padding-bottom: 10px; - border-bottom: 1px solid #eee; -} - -/* ============================================ - Status Indicators - ============================================ */ - -.igny8-status-connected { - color: #46b450; - font-weight: bold; -} - -.igny8-status-disconnected { - color: #dc3232; - font-weight: bold; -} - -.igny8-test-result { - margin-left: 10px; -} - -.igny8-test-result .igny8-success { - color: #46b450; -} - -.igny8-test-result .igny8-error { - color: #dc3232; -} - -.igny8-test-result .igny8-loading { - color: #2271b1; -} - -/* ============================================ - Sync Operations - ============================================ */ - -.igny8-sync-actions { - display: flex; - flex-wrap: wrap; - gap: 10px; - margin-bottom: 20px; -} - -.igny8-sync-actions .button { - min-width: 150px; -} - -.igny8-sync-status { - margin-top: 15px; - padding: 10px; - border-radius: 4px; - display: none; -} - -.igny8-sync-status.igny8-sync-status-success { - background-color: #d4edda; - border: 1px solid #c3e6cb; - color: #155724; - display: block; -} - -.igny8-sync-status.igny8-sync-status-error { - background-color: #f8d7da; - border: 1px solid #f5c6cb; - color: #721c24; - display: block; -} - -.igny8-sync-status.igny8-sync-status-loading { - background-color: #d1ecf1; - border: 1px solid #bee5eb; - color: #0c5460; - display: block; -} - -/* ============================================ - Statistics - ============================================ */ - -.igny8-stats-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 20px; - margin-top: 15px; -} - -.igny8-stat-item { - padding: 15px; - background: #f9f9f9; - border: 1px solid #ddd; - border-radius: 4px; -} - -.igny8-stat-label { - font-size: 12px; - color: #666; - text-transform: uppercase; - margin-bottom: 8px; -} - -.igny8-stat-value { - font-size: 24px; - font-weight: bold; - color: #2271b1; -} - -/* ============================================ - Diagnostics - ============================================ */ - -.igny8-diagnostics-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - gap: 16px; - margin-top: 15px; -} - -.igny8-diagnostic-item { - padding: 15px; - background-color: #f6f7f7; - border: 1px solid #dcdcde; - border-radius: 4px; -} - -.igny8-diagnostic-label { - font-size: 11px; - text-transform: uppercase; - letter-spacing: 0.05em; - color: #555d66; - margin-bottom: 6px; -} - -.igny8-diagnostic-value { - font-size: 18px; - font-weight: 600; - color: #1d2327; -} - -.igny8-diagnostic-item .description { - margin: 6px 0 0; - color: #646970; -} - -/* ============================================ - Buttons - ============================================ */ - -.igny8-button-group { - display: flex; - gap: 10px; - margin: 15px 0; -} - -.igny8-button-group .button { - flex: 1; -} - -/* ============================================ - Loading States - ============================================ */ - -.igny8-loading { - opacity: 0.6; - pointer-events: none; -} - -.igny8-spinner { - display: inline-block; - width: 16px; - height: 16px; - border: 2px solid #f3f3f3; - border-top: 2px solid #2271b1; - border-radius: 50%; - animation: igny8-spin 1s linear infinite; - margin-right: 8px; - vertical-align: middle; -} - -@keyframes igny8-spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -/* ============================================ - Messages & Notifications - ============================================ */ - -.igny8-message { - padding: 12px; - margin: 15px 0; - border-left: 4px solid; - background: #fff; -} - -.igny8-message.igny8-message-success { - border-color: #46b450; - background-color: #f0f8f0; -} - -.igny8-message.igny8-message-error { - border-color: #dc3232; - background-color: #fff5f5; -} - -.igny8-message.igny8-message-info { - border-color: #2271b1; - background-color: #f0f6fc; -} - -.igny8-message.igny8-message-warning { - border-color: #f0b849; - background-color: #fffbf0; -} - -/* ============================================ - Tables - ============================================ */ - -.igny8-table { - width: 100%; - border-collapse: collapse; - margin: 15px 0; -} - -.igny8-table th, -.igny8-table td { - padding: 10px; - text-align: left; - border-bottom: 1px solid #ddd; -} - -.igny8-table th { - background-color: #f9f9f9; - font-weight: 600; -} - -.igny8-table tr:hover { - background-color: #f9f9f9; -} - -/* ============================================ - Admin Columns - ============================================ */ - -.igny8-badge { - display: inline-block; - padding: 3px 8px; - border-radius: 3px; - font-size: 11px; - font-weight: 600; - text-transform: uppercase; - line-height: 1.4; -} - -.igny8-badge-igny8 { - background-color: #2271b1; - color: #fff; -} - -.igny8-badge-wordpress { - background-color: #646970; - color: #fff; -} - -.igny8-terms-list { - display: flex; - flex-wrap: wrap; - gap: 4px; -} - -.igny8-term-badge { - display: inline-block; - padding: 2px 6px; - background-color: #f0f0f1; - border: 1px solid #c3c4c7; - border-radius: 2px; - font-size: 11px; - color: #50575e; -} - -.igny8-empty { - color: #a7aaad; - font-style: italic; -} - -.igny8-action-link { - color: #2271b1; - text-decoration: none; - cursor: pointer; -} - -.igny8-action-link:hover { - color: #135e96; - text-decoration: underline; -} - -/* ============================================ - Responsive - ============================================ */ - -@media (max-width: 782px) { - .igny8-sync-actions { - flex-direction: column; - } - - .igny8-sync-actions .button { - width: 100%; - } - - .igny8-stats-grid { - grid-template-columns: 1fr; - } - - .igny8-diagnostics-grid { - grid-template-columns: 1fr; - } -} - diff --git a/igny8-wp-integration-plugin/admin/assets/js/admin.js b/igny8-wp-integration-plugin/admin/assets/js/admin.js deleted file mode 100644 index b6e0f306..00000000 --- a/igny8-wp-integration-plugin/admin/assets/js/admin.js +++ /dev/null @@ -1,178 +0,0 @@ -/** - * Admin JavaScript - * - * @package Igny8Bridge - */ - -(function($) { - 'use strict'; - - $(document).ready(function() { - // Test connection button - $('#igny8-test-connection').on('click', function() { - var $button = $(this); - var $result = $('#igny8-test-result'); - - $button.prop('disabled', true).addClass('igny8-loading'); - $result.html('Testing...'); - - $.ajax({ - url: igny8Admin.ajaxUrl, - type: 'POST', - data: { - action: 'igny8_test_connection', - nonce: igny8Admin.nonce - }, - success: function(response) { - if (response.success) { - $result.html('✓ Connection successful'); - } else { - $result.html('✗ ' + (response.data.message || 'Connection failed') + ''); - } - }, - error: function() { - $result.html('✗ Request failed'); - }, - complete: function() { - $button.prop('disabled', false).removeClass('igny8-loading'); - } - }); - }); - - // Sync posts to IGNY8 - $('#igny8-sync-posts').on('click', function() { - igny8TriggerSync('igny8_sync_posts', 'Syncing posts to IGNY8...'); - }); - - // Sync taxonomies - $('#igny8-sync-taxonomies').on('click', function() { - igny8TriggerSync('igny8_sync_taxonomies', 'Syncing taxonomies...'); - }); - - // Sync from IGNY8 - $('#igny8-sync-from-igny8').on('click', function() { - igny8TriggerSync('igny8_sync_from_igny8', 'Syncing from IGNY8...'); - }); - - // Collect and send site data - $('#igny8-collect-site-data').on('click', function() { - igny8TriggerSync('igny8_collect_site_data', 'Collecting and sending site data...'); - }); - - // Load sync statistics - igny8LoadStats(); - - // Handle row action links - $(document).on('click', '.igny8-action-link', function(e) { - e.preventDefault(); - - var $link = $(this); - var postId = $link.data('post-id'); - var action = $link.data('action'); - - if (!postId) { - return; - } - - if (!confirm('Are you sure you want to ' + (action === 'send' ? 'send' : 'update') + ' this post to IGNY8?')) { - return; - } - - $link.text('Processing...').prop('disabled', true); - - $.ajax({ - url: igny8Admin.ajaxUrl, - type: 'POST', - data: { - action: 'igny8_send_to_igny8', - post_id: postId, - action_type: action, - nonce: igny8Admin.nonce - }, - success: function(response) { - if (response.success) { - alert(response.data.message || 'Success!'); - location.reload(); - } else { - alert(response.data.message || 'Failed to send to IGNY8'); - $link.text(action === 'send' ? 'Send to IGNY8' : 'Update in IGNY8').prop('disabled', false); - } - }, - error: function() { - alert('Request failed'); - $link.text(action === 'send' ? 'Send to IGNY8' : 'Update in IGNY8').prop('disabled', false); - } - }); - }); - }); - - /** - * Trigger sync operation - */ - function igny8TriggerSync(action, message) { - var $status = $('#igny8-sync-status'); - var $button = $('#' + action.replace('igny8_', 'igny8-')); - - $status.removeClass('igny8-sync-status-success igny8-sync-status-error') - .addClass('igny8-sync-status-loading') - .html('' + message); - - $button.prop('disabled', true).addClass('igny8-loading'); - - $.ajax({ - url: igny8Admin.ajaxUrl, - type: 'POST', - data: { - action: action, - nonce: igny8Admin.nonce - }, - success: function(response) { - if (response.success) { - $status.removeClass('igny8-sync-status-loading') - .addClass('igny8-sync-status-success') - .html('✓ ' + (response.data.message || 'Operation completed successfully')); - - // Reload stats - igny8LoadStats(); - } else { - $status.removeClass('igny8-sync-status-loading') - .addClass('igny8-sync-status-error') - .html('✗ ' + (response.data.message || 'Operation failed')); - } - }, - error: function() { - $status.removeClass('igny8-sync-status-loading') - .addClass('igny8-sync-status-error') - .html('✗ Request failed'); - }, - complete: function() { - $button.prop('disabled', false).removeClass('igny8-loading'); - } - }); - } - - /** - * Load sync statistics - */ - function igny8LoadStats() { - $.ajax({ - url: igny8Admin.ajaxUrl, - type: 'POST', - data: { - action: 'igny8_get_stats', - nonce: igny8Admin.nonce - }, - success: function(response) { - if (response.success && response.data) { - if (response.data.synced_posts !== undefined) { - $('#igny8-stat-posts').text(response.data.synced_posts); - } - if (response.data.last_sync) { - $('#igny8-stat-last-sync').text(response.data.last_sync); - } - } - } - }); - } -})(jQuery); - diff --git a/igny8-wp-integration-plugin/admin/assets/js/post-editor.js b/igny8-wp-integration-plugin/admin/assets/js/post-editor.js deleted file mode 100644 index 2be154c1..00000000 --- a/igny8-wp-integration-plugin/admin/assets/js/post-editor.js +++ /dev/null @@ -1,200 +0,0 @@ -/** - * Post Editor JavaScript - * - * Handles AJAX interactions for Planner and Optimizer meta boxes - * - * @package Igny8Bridge - */ - -(function($) { - 'use strict'; - - $(document).ready(function() { - // Fetch Planner Brief - $('#igny8-fetch-brief').on('click', function() { - var $button = $(this); - var $message = $('#igny8-planner-brief-message'); - var postId = $button.data('post-id'); - var taskId = $button.data('task-id'); - - $button.prop('disabled', true).text('Fetching...'); - $message.hide().removeClass('notice-success notice-error'); - - $.ajax({ - url: igny8PostEditor.ajaxUrl, - type: 'POST', - data: { - action: 'igny8_fetch_planner_brief', - nonce: igny8PostEditor.nonce, - post_id: postId, - task_id: taskId - }, - success: function(response) { - if (response.success) { - $message.addClass('notice notice-success inline') - .html('

' + response.data.message + '

') - .show(); - - // Reload page to show updated brief - setTimeout(function() { - location.reload(); - }, 1000); - } else { - $message.addClass('notice notice-error inline') - .html('

' + (response.data.message || 'Failed to fetch brief') + '

') - .show(); - $button.prop('disabled', false).text('Fetch Brief'); - } - }, - error: function() { - $message.addClass('notice notice-error inline') - .html('

Request failed

') - .show(); - $button.prop('disabled', false).text('Fetch Brief'); - } - }); - }); - - // Refresh Planner Task - $('#igny8-refresh-task').on('click', function() { - var $button = $(this); - var $message = $('#igny8-planner-brief-message'); - var postId = $button.data('post-id'); - var taskId = $button.data('task-id'); - - if (!confirm('Are you sure you want to request a refresh of this task from IGNY8 Planner?')) { - return; - } - - $button.prop('disabled', true).text('Requesting...'); - $message.hide().removeClass('notice-success notice-error'); - - $.ajax({ - url: igny8PostEditor.ajaxUrl, - type: 'POST', - data: { - action: 'igny8_refresh_planner_task', - nonce: igny8PostEditor.nonce, - post_id: postId, - task_id: taskId - }, - success: function(response) { - if (response.success) { - $message.addClass('notice notice-success inline') - .html('

' + response.data.message + '

') - .show(); - } else { - $message.addClass('notice notice-error inline') - .html('

' + (response.data.message || 'Failed to request refresh') + '

') - .show(); - } - $button.prop('disabled', false).text('Request Refresh'); - }, - error: function() { - $message.addClass('notice notice-error inline') - .html('

Request failed

') - .show(); - $button.prop('disabled', false).text('Request Refresh'); - } - }); - }); - - // Create Optimizer Job - $('#igny8-create-optimizer-job').on('click', function() { - var $button = $(this); - var $message = $('#igny8-optimizer-message'); - var postId = $button.data('post-id'); - var taskId = $button.data('task-id'); - - if (!confirm('Create a new optimizer job for this post?')) { - return; - } - - $button.prop('disabled', true).text('Creating...'); - $message.hide().removeClass('notice-success notice-error'); - - $.ajax({ - url: igny8PostEditor.ajaxUrl, - type: 'POST', - data: { - action: 'igny8_create_optimizer_job', - nonce: igny8PostEditor.nonce, - post_id: postId, - task_id: taskId, - job_type: 'audit', - priority: 'normal' - }, - success: function(response) { - if (response.success) { - $message.addClass('notice notice-success inline') - .html('

' + response.data.message + '

') - .show(); - - // Reload page to show updated status - setTimeout(function() { - location.reload(); - }, 1000); - } else { - $message.addClass('notice notice-error inline') - .html('

' + (response.data.message || 'Failed to create job') + '

') - .show(); - $button.prop('disabled', false).text('Request Optimization'); - } - }, - error: function() { - $message.addClass('notice notice-error inline') - .html('

Request failed

') - .show(); - $button.prop('disabled', false).text('Request Optimization'); - } - }); - }); - - // Check Optimizer Status - $('#igny8-check-optimizer-status').on('click', function() { - var $button = $(this); - var $message = $('#igny8-optimizer-message'); - var postId = $button.data('post-id'); - var jobId = $button.data('job-id'); - - $button.prop('disabled', true).text('Checking...'); - $message.hide().removeClass('notice-success notice-error'); - - $.ajax({ - url: igny8PostEditor.ajaxUrl, - type: 'POST', - data: { - action: 'igny8_get_optimizer_status', - nonce: igny8PostEditor.nonce, - post_id: postId, - job_id: jobId - }, - success: function(response) { - if (response.success) { - $message.addClass('notice notice-success inline') - .html('

Status: ' + response.data.status + '

') - .show(); - - // Reload page to show updated status - setTimeout(function() { - location.reload(); - }, 1000); - } else { - $message.addClass('notice notice-error inline') - .html('

' + (response.data.message || 'Failed to get status') + '

') - .show(); - } - $button.prop('disabled', false).text('Check Status'); - }, - error: function() { - $message.addClass('notice notice-error inline') - .html('

Request failed

') - .show(); - $button.prop('disabled', false).text('Check Status'); - } - }); - }); - }); - -})(jQuery); - diff --git a/igny8-wp-integration-plugin/admin/class-admin-columns.php b/igny8-wp-integration-plugin/admin/class-admin-columns.php deleted file mode 100644 index 59f9de5b..00000000 --- a/igny8-wp-integration-plugin/admin/class-admin-columns.php +++ /dev/null @@ -1,335 +0,0 @@ - $value) { - $new_columns[$key] = $value; - - if ($key === 'title') { - $new_columns['igny8_source'] = __('Source', 'igny8-bridge'); - $new_columns['igny8_sectors'] = __('Sectors', 'igny8-bridge'); - $new_columns['igny8_clusters'] = __('Clusters', 'igny8-bridge'); - } - } - - return $new_columns; - } - - /** - * Render column content - * - * @param string $column_name Column name - * @param int $post_id Post ID - */ - public function render_column_content($column_name, $post_id) { - switch ($column_name) { - case 'igny8_source': - $this->render_source_column($post_id); - break; - - case 'igny8_sectors': - $this->render_sectors_column($post_id); - break; - - case 'igny8_clusters': - $this->render_clusters_column($post_id); - break; - } - } - - /** - * Render source column - * - * @param int $post_id Post ID - */ - private function render_source_column($post_id) { - $task_id = get_post_meta($post_id, '_igny8_task_id', true); - - if ($task_id) { - echo ''; - echo esc_html__('IGNY8', 'igny8-bridge'); - echo ''; - } else { - echo ''; - echo esc_html__('WP', 'igny8-bridge'); - echo ''; - } - } - - /** - * Render sectors column - * - * @param int $post_id Post ID - */ - private function render_sectors_column($post_id) { - $sectors = wp_get_post_terms($post_id, 'igny8_sectors', array('fields' => 'names')); - - if (!empty($sectors) && !is_wp_error($sectors)) { - echo '
'; - foreach ($sectors as $sector) { - echo '' . esc_html($sector) . ''; - } - echo '
'; - } else { - echo ''; - } - } - - /** - * Render clusters column - * - * @param int $post_id Post ID - */ - private function render_clusters_column($post_id) { - $clusters = wp_get_post_terms($post_id, 'igny8_clusters', array('fields' => 'names')); - - if (!empty($clusters) && !is_wp_error($clusters)) { - echo '
'; - foreach ($clusters as $cluster) { - echo '' . esc_html($cluster) . ''; - } - echo '
'; - } else { - echo ''; - } - } - - /** - * Make columns sortable - * - * @param array $columns Sortable columns - * @return array Modified columns - */ - public function make_columns_sortable($columns) { - $columns['igny8_source'] = 'igny8_source'; - return $columns; - } - - /** - * Add row actions - * - * @param array $actions Existing actions - * @param WP_Post $post Post object - * @return array Modified actions - */ - public function add_row_actions($actions, $post) { - // Only add for published posts - if ($post->post_status !== 'publish') { - return $actions; - } - - // Check if already synced to IGNY8 - $task_id = get_post_meta($post->ID, '_igny8_task_id', true); - - if ($task_id) { - // Already synced - show update action - $actions['igny8_update'] = sprintf( - '%s', - '#', - $post->ID, - __('Update in IGNY8', 'igny8-bridge') - ); - } else { - // Not synced - show send action - $actions['igny8_send'] = sprintf( - '%s', - '#', - $post->ID, - __('Send to IGNY8', 'igny8-bridge') - ); - } - - return $actions; - } - - /** - * Send post to IGNY8 (AJAX handler) - */ - public static function send_to_igny8() { - check_ajax_referer('igny8_admin_nonce', 'nonce'); - - if (!current_user_can('edit_posts')) { - wp_send_json_error(array('message' => 'Unauthorized')); - } - - $post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0; - $action = isset($_POST['action_type']) ? sanitize_text_field($_POST['action_type']) : 'send'; - - if (!$post_id) { - wp_send_json_error(array('message' => 'Invalid post ID')); - } - - $post = get_post($post_id); - if (!$post) { - wp_send_json_error(array('message' => 'Post not found')); - } - - if (!igny8_is_connection_enabled()) { - wp_send_json_error(array('message' => 'Connection is disabled. Enable sync operations first.')); - } - - $api = new Igny8API(); - - if (!$api->is_authenticated()) { - wp_send_json_error(array('message' => 'Not authenticated with IGNY8')); - } - - $site_id = get_option('igny8_site_id'); - if (!$site_id) { - wp_send_json_error(array('message' => 'Site ID not set')); - } - - // Prepare post data for IGNY8 - $post_data = array( - 'title' => $post->post_title, - 'content' => $post->post_content, - 'excerpt' => $post->post_excerpt, - 'status' => $post->post_status === 'publish' ? 'completed' : 'draft', - 'post_type' => $post->post_type, - 'url' => get_permalink($post_id), - 'wordpress_post_id' => $post_id - ); - - // Get categories - $categories = wp_get_post_categories($post_id, array('fields' => 'names')); - if (!empty($categories)) { - $post_data['categories'] = $categories; - } - - // Get tags - $tags = wp_get_post_tags($post_id, array('fields' => 'names')); - if (!empty($tags)) { - $post_data['tags'] = $tags; - } - - // Get featured image - $featured_image_id = get_post_thumbnail_id($post_id); - if ($featured_image_id) { - $post_data['featured_image'] = wp_get_attachment_image_url($featured_image_id, 'full'); - } - - // Get sectors and clusters - $sectors = wp_get_post_terms($post_id, 'igny8_sectors', array('fields' => 'ids')); - $clusters = wp_get_post_terms($post_id, 'igny8_clusters', array('fields' => 'ids')); - - if (!empty($sectors)) { - // Get IGNY8 sector IDs from term meta - $igny8_sector_ids = array(); - foreach ($sectors as $term_id) { - $igny8_sector_id = get_term_meta($term_id, '_igny8_sector_id', true); - if ($igny8_sector_id) { - $igny8_sector_ids[] = $igny8_sector_id; - } - } - if (!empty($igny8_sector_ids)) { - $post_data['sector_id'] = $igny8_sector_ids[0]; // Use first sector - } - } - - if (!empty($clusters)) { - // Get IGNY8 cluster IDs from term meta - $igny8_cluster_ids = array(); - foreach ($clusters as $term_id) { - $igny8_cluster_id = get_term_meta($term_id, '_igny8_cluster_id', true); - if ($igny8_cluster_id) { - $igny8_cluster_ids[] = $igny8_cluster_id; - } - } - if (!empty($igny8_cluster_ids)) { - $post_data['cluster_id'] = $igny8_cluster_ids[0]; // Use first cluster - } - } - - // Check if post already has task ID - $existing_task_id = get_post_meta($post_id, '_igny8_task_id', true); - - if ($existing_task_id && $action === 'update') { - // Update existing task - $response = $api->put("/writer/tasks/{$existing_task_id}/", $post_data); - } else { - // Create new task - $response = $api->post("/writer/tasks/", $post_data); - } - - if ($response['success']) { - $task_id = $response['data']['id'] ?? $existing_task_id; - - // Store task ID - update_post_meta($post_id, '_igny8_task_id', $task_id); - update_post_meta($post_id, '_igny8_last_synced', current_time('mysql')); - - wp_send_json_success(array( - 'message' => $action === 'update' ? 'Post updated in IGNY8' : 'Post sent to IGNY8', - 'task_id' => $task_id - )); - } else { - wp_send_json_error(array( - 'message' => 'Failed to send to IGNY8: ' . ($response['error'] ?? 'Unknown error') - )); - } - } -} - -// Initialize -new Igny8AdminColumns(); - -// Register AJAX handler -add_action('wp_ajax_igny8_send_to_igny8', array('Igny8AdminColumns', 'send_to_igny8')); - diff --git a/igny8-wp-integration-plugin/admin/class-admin.php b/igny8-wp-integration-plugin/admin/class-admin.php deleted file mode 100644 index ed1f4b00..00000000 --- a/igny8-wp-integration-plugin/admin/class-admin.php +++ /dev/null @@ -1,566 +0,0 @@ - 'boolean', - 'sanitize_callback' => array($this, 'sanitize_boolean'), - 'default' => 1 - )); - - register_setting('igny8_bridge_connection', 'igny8_connection_enabled', array( - 'type' => 'boolean', - 'sanitize_callback' => array($this, 'sanitize_boolean'), - 'default' => 1 - )); - - register_setting('igny8_bridge_controls', 'igny8_enabled_post_types', array( - 'type' => 'array', - 'sanitize_callback' => array($this, 'sanitize_post_types'), - 'default' => array_keys(igny8_get_supported_post_types()) - )); - - register_setting('igny8_bridge_controls', 'igny8_enable_woocommerce', array( - 'type' => 'boolean', - 'sanitize_callback' => array($this, 'sanitize_boolean'), - 'default' => class_exists('WooCommerce') ? 1 : 0 - )); - - register_setting('igny8_bridge_controls', 'igny8_control_mode', array( - 'type' => 'string', - 'sanitize_callback' => array($this, 'sanitize_control_mode'), - 'default' => 'mirror' - )); - - register_setting('igny8_bridge_controls', 'igny8_enabled_modules', array( - 'type' => 'array', - 'sanitize_callback' => array($this, 'sanitize_modules'), - 'default' => array_keys(igny8_get_available_modules()) - )); - } - - /** - * Enqueue admin scripts and styles - * - * @param string $hook Current admin page hook - */ - public function enqueue_scripts($hook) { - // Enqueue on settings page - if ($hook === 'settings_page_igny8-settings') { - wp_enqueue_style( - 'igny8-admin-style', - IGNY8_BRIDGE_PLUGIN_URL . 'admin/assets/css/admin.css', - array(), - IGNY8_BRIDGE_VERSION - ); - - wp_enqueue_script( - 'igny8-admin-script', - IGNY8_BRIDGE_PLUGIN_URL . 'admin/assets/js/admin.js', - array('jquery'), - IGNY8_BRIDGE_VERSION, - true - ); - - wp_localize_script('igny8-admin-script', 'igny8Admin', array( - 'ajaxUrl' => admin_url('admin-ajax.php'), - 'nonce' => wp_create_nonce('igny8_admin_nonce'), - )); - } - - // Enqueue on post/page/product list pages - if (strpos($hook, 'edit.php') !== false) { - $screen = get_current_screen(); - if ($screen && in_array($screen->post_type, array('post', 'page', 'product', ''))) { - wp_enqueue_style( - 'igny8-admin-style', - IGNY8_BRIDGE_PLUGIN_URL . 'admin/assets/css/admin.css', - array(), - IGNY8_BRIDGE_VERSION - ); - - wp_enqueue_script( - 'igny8-admin-script', - IGNY8_BRIDGE_PLUGIN_URL . 'admin/assets/js/admin.js', - array('jquery'), - IGNY8_BRIDGE_VERSION, - true - ); - - wp_localize_script('igny8-admin-script', 'igny8Admin', array( - 'ajaxUrl' => admin_url('admin-ajax.php'), - 'nonce' => wp_create_nonce('igny8_admin_nonce'), - )); - } - } - } - - /** - * Render settings page - */ - public function render_settings_page() { - // Handle form submission (use wp_verify_nonce to avoid wp_die on failure) - if (isset($_POST['igny8_connect'])) { - if (empty($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'igny8_settings_nonce')) { - add_settings_error( - 'igny8_settings', - 'igny8_nonce', - __('Security check failed. Please refresh the page and try again.', 'igny8-bridge'), - 'error' - ); - } else { - $this->handle_connection(); - } - } - - // Handle revoke API key (use wp_verify_nonce) - if (isset($_POST['igny8_revoke_api_key'])) { - if (empty($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'igny8_revoke_api_key')) { - add_settings_error( - 'igny8_settings', - 'igny8_nonce_revoke', - __('Security check failed. Could not revoke API key.', 'igny8-bridge'), - 'error' - ); - } else { - self::revoke_api_key(); - add_settings_error( - 'igny8_settings', - 'igny8_api_key_revoked', - __('API key revoked and removed from this site.', 'igny8-bridge'), - 'updated' - ); - } - } - - // Handle webhook secret regeneration (use wp_verify_nonce) - if (isset($_POST['igny8_regenerate_secret'])) { - if (empty($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'igny8_regenerate_secret')) { - add_settings_error( - 'igny8_settings', - 'igny8_nonce_regen', - __('Security check failed. Could not regenerate secret.', 'igny8-bridge'), - 'error' - ); - } else { - $new_secret = igny8_regenerate_webhook_secret(); - add_settings_error( - 'igny8_settings', - 'igny8_secret_regenerated', - __('Webhook secret regenerated. Update it in your IGNY8 SaaS app settings.', 'igny8-bridge'), - 'updated' - ); - } - } - - // Include settings template - include IGNY8_BRIDGE_PLUGIN_DIR . 'admin/settings.php'; - } - - /** - * Handle API connection - */ - private function handle_connection() { - $email = sanitize_email($_POST['igny8_email'] ?? ''); - $password = $_POST['igny8_password'] ?? ''; - $api_key = sanitize_text_field($_POST['igny8_api_key'] ?? ''); - - // Require email, password AND API key per updated policy - if (empty($email) || empty($password) || empty($api_key)) { - add_settings_error( - 'igny8_settings', - 'igny8_error', - __('Email, password and API key are all required to establish the connection.', 'igny8-bridge'), - 'error' - ); - return; - } - - // First, attempt login with email/password - $api = new Igny8API(); - - if (!$api->login($email, $password)) { - add_settings_error( - 'igny8_settings', - 'igny8_error', - __('Failed to connect to IGNY8 API with provided credentials.', 'igny8-bridge'), - 'error' - ); - return; - } - - // Store email - update_option('igny8_email', $email); - - // Store API key securely and also set access token to the API key for subsequent calls if desired - if (function_exists('igny8_store_secure_option')) { - igny8_store_secure_option('igny8_api_key', $api_key); - igny8_store_secure_option('igny8_access_token', $api_key); - } else { - update_option('igny8_api_key', $api_key); - update_option('igny8_access_token', $api_key); - } - - // Try to get site ID (if available) using the authenticated client - $site_response = $api->get('/system/sites/'); - if ($site_response['success'] && !empty($site_response['results'])) { - $site = $site_response['results'][0]; - update_option('igny8_site_id', $site['id']); - } - - add_settings_error( - 'igny8_settings', - 'igny8_connected', - __('Successfully connected to IGNY8 API and stored API key.', 'igny8-bridge'), - 'updated' - ); - } - - /** - * Revoke stored API key (secure delete) - * - * Public so tests can call it directly. - */ - public static function revoke_api_key() { - if (function_exists('igny8_delete_secure_option')) { - igny8_delete_secure_option('igny8_api_key'); - igny8_delete_secure_option('igny8_access_token'); - igny8_delete_secure_option('igny8_refresh_token'); - } else { - delete_option('igny8_api_key'); - delete_option('igny8_access_token'); - delete_option('igny8_refresh_token'); - } - - // Also clear token-issued timestamps - delete_option('igny8_token_refreshed_at'); - delete_option('igny8_access_token_issued'); - } - - /** - * Test API connection (AJAX handler) - */ - public static function test_connection() { - check_ajax_referer('igny8_admin_nonce', 'nonce'); - - if (!current_user_can('manage_options')) { - wp_send_json_error(array('message' => 'Unauthorized')); - } - - if (!igny8_is_connection_enabled()) { - wp_send_json_error(array('message' => 'Connection is disabled. Enable sync operations to test.')); - } - - $api = new Igny8API(); - - if (!$api->is_authenticated()) { - wp_send_json_error(array('message' => 'Not authenticated')); - } - - // Test with a simple API call - $response = $api->get('/planner/keywords/?page_size=1'); - - if ($response['success']) { - $checked_at = current_time('timestamp'); - update_option('igny8_last_api_health_check', $checked_at); - wp_send_json_success(array( - 'message' => 'Connection successful', - 'data' => $response, - 'checked_at' => $checked_at - )); - } else { - wp_send_json_error(array( - 'message' => 'Connection failed', - 'error' => $response['error'] - )); - } - } - - /** - * Sync posts to IGNY8 (AJAX handler) - */ - public static function sync_posts() { - check_ajax_referer('igny8_admin_nonce', 'nonce'); - - if (!current_user_can('manage_options')) { - wp_send_json_error(array('message' => 'Unauthorized')); - } - - if (!igny8_is_connection_enabled()) { - wp_send_json_error(array('message' => 'Connection is disabled. Enable sync operations first.')); - } - - $result = igny8_batch_sync_post_statuses(); - - wp_send_json_success(array( - 'message' => sprintf('Synced %d posts, %d failed', $result['synced'], $result['failed']), - 'data' => $result - )); - } - - /** - * Sync taxonomies (AJAX handler) - */ - public static function sync_taxonomies() { - check_ajax_referer('igny8_admin_nonce', 'nonce'); - - if (!current_user_can('manage_options')) { - wp_send_json_error(array('message' => 'Unauthorized')); - } - - if (!igny8_is_connection_enabled()) { - wp_send_json_error(array('message' => 'Connection is disabled. Enable sync operations first.')); - } - - $api = new Igny8API(); - if (!$api->is_authenticated()) { - wp_send_json_error(array('message' => 'Not authenticated')); - } - - // Sync sectors and clusters from IGNY8 - $sectors_result = igny8_sync_igny8_sectors_to_wp(); - $clusters_result = igny8_sync_igny8_clusters_to_wp(); - - wp_send_json_success(array( - 'message' => sprintf('Synced %d sectors, %d clusters', - $sectors_result['synced'] ?? 0, - $clusters_result['synced'] ?? 0 - ), - 'data' => array( - 'sectors' => $sectors_result, - 'clusters' => $clusters_result - ) - )); - } - - /** - * Sync from IGNY8 (AJAX handler) - */ - public static function sync_from_igny8() { - check_ajax_referer('igny8_admin_nonce', 'nonce'); - - if (!current_user_can('manage_options')) { - wp_send_json_error(array('message' => 'Unauthorized')); - } - - if (!igny8_is_connection_enabled()) { - wp_send_json_error(array('message' => 'Connection is disabled. Enable sync operations first.')); - } - - $result = igny8_sync_igny8_tasks_to_wp(); - - if ($result['success']) { - wp_send_json_success(array( - 'message' => sprintf('Created %d posts, updated %d posts', - $result['created'], - $result['updated'] - ), - 'data' => $result - )); - } else { - wp_send_json_error(array( - 'message' => $result['error'] ?? 'Sync failed' - )); - } - } - - /** - * Collect and send site data (AJAX handler) - */ - public static function collect_site_data() { - check_ajax_referer('igny8_admin_nonce', 'nonce'); - - if (!current_user_can('manage_options')) { - wp_send_json_error(array('message' => 'Unauthorized')); - } - - if (!igny8_is_connection_enabled()) { - wp_send_json_error(array('message' => 'Connection is disabled. Enable sync operations first.')); - } - - $site_id = get_option('igny8_site_id'); - if (!$site_id) { - wp_send_json_error(array('message' => 'Site ID not set')); - } - - $result = igny8_send_site_data_to_igny8($site_id); - - if ($result) { - wp_send_json_success(array( - 'message' => 'Site data collected and sent successfully', - 'data' => $result - )); - } else { - wp_send_json_error(array('message' => 'Failed to send site data')); - } - } - - /** - * Get sync statistics (AJAX handler) - */ - public static function get_stats() { - check_ajax_referer('igny8_admin_nonce', 'nonce'); - - if (!current_user_can('manage_options')) { - wp_send_json_error(array('message' => 'Unauthorized')); - } - - global $wpdb; - - // Count synced posts - $synced_posts = $wpdb->get_var(" - SELECT COUNT(DISTINCT post_id) - FROM {$wpdb->postmeta} - WHERE meta_key = '_igny8_task_id' - "); - - // Get last sync time - $last_sync = get_option('igny8_last_site_sync', 0); - $last_sync_formatted = $last_sync ? date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $last_sync) : 'Never'; - - wp_send_json_success(array( - 'synced_posts' => intval($synced_posts), - 'last_sync' => $last_sync_formatted - )); - } - - /** - * Sanitize post types option - * - * @param mixed $value Raw value - * @return array - */ - public function sanitize_post_types($value) { - $supported = array_keys(igny8_get_supported_post_types()); - - if (!is_array($value)) { - return $supported; - } - - $clean = array(); - foreach ($value as $post_type) { - $post_type = sanitize_key($post_type); - if (in_array($post_type, $supported, true)) { - $clean[] = $post_type; - } - } - - return !empty($clean) ? $clean : $supported; - } - - /** - * Sanitize boolean option - * - * @param mixed $value Raw value - * @return int - */ - public function sanitize_boolean($value) { - return $value ? 1 : 0; - } - - /** - * Sanitize control mode - * - * @param mixed $value Raw value - * @return string - */ - public function sanitize_control_mode($value) { - $value = is_string($value) ? strtolower($value) : 'mirror'; - return in_array($value, array('mirror', 'hybrid'), true) ? $value : 'mirror'; - } - - /** - * Sanitize module toggles - * - * @param mixed $value Raw value - * @return array - */ - public function sanitize_modules($value) { - $supported = array_keys(igny8_get_available_modules()); - - if (!is_array($value)) { - return $supported; - } - - $clean = array(); - foreach ($value as $module) { - $module = sanitize_key($module); - if (in_array($module, $supported, true)) { - $clean[] = $module; - } - } - - return !empty($clean) ? $clean : $supported; - } -} - -// Register AJAX handlers -add_action('wp_ajax_igny8_test_connection', array('Igny8Admin', 'test_connection')); -add_action('wp_ajax_igny8_sync_posts', array('Igny8Admin', 'sync_posts')); -add_action('wp_ajax_igny8_sync_taxonomies', array('Igny8Admin', 'sync_taxonomies')); -add_action('wp_ajax_igny8_sync_from_igny8', array('Igny8Admin', 'sync_from_igny8')); -add_action('wp_ajax_igny8_collect_site_data', array('Igny8Admin', 'collect_site_data')); -add_action('wp_ajax_igny8_get_stats', array('Igny8Admin', 'get_stats')); - diff --git a/igny8-wp-integration-plugin/admin/class-post-meta-boxes.php b/igny8-wp-integration-plugin/admin/class-post-meta-boxes.php deleted file mode 100644 index d0d6ebcb..00000000 --- a/igny8-wp-integration-plugin/admin/class-post-meta-boxes.php +++ /dev/null @@ -1,469 +0,0 @@ - admin_url('admin-ajax.php'), - 'nonce' => wp_create_nonce('igny8_post_editor_nonce'), - )); - } - - /** - * Render Planner Brief meta box - */ - public function render_planner_brief_box($post) { - $task_id = get_post_meta($post->ID, '_igny8_task_id', true); - $brief = get_post_meta($post->ID, '_igny8_task_brief', true); - $brief_cached_at = get_post_meta($post->ID, '_igny8_brief_cached_at', true); - $cluster_id = get_post_meta($post->ID, '_igny8_cluster_id', true); - - if (!$task_id && !$cluster_id) { - echo '

'; - _e('This post is not linked to an IGNY8 task or cluster.', 'igny8-bridge'); - echo '

'; - return; - } - - wp_nonce_field('igny8_post_editor_nonce', 'igny8_post_editor_nonce'); - ?> -
- -
- - -

- - - -
- -
- - - -
- - -
    - -
  • - -
- -

- -
- - - -
- - '; - foreach ($keywords as $keyword) { - echo '' . esc_html(trim($keyword)) . ''; - } - echo ''; - ?> -
- - - -
- - -
- - -

- - - -

- -

- -
- -

- -

- -
- -

- - - - - -

- - - ID, '_igny8_task_id', true); - $optimizer_job_id = get_post_meta($post->ID, '_igny8_optimizer_job_id', true); - $optimizer_status = get_post_meta($post->ID, '_igny8_optimizer_status', true); - - if (!$task_id) { - echo '

'; - _e('This post is not linked to an IGNY8 task.', 'igny8-bridge'); - echo '

'; - return; - } - - wp_nonce_field('igny8_post_editor_nonce', 'igny8_post_editor_nonce'); - ?> -
- -
-

- - -

- - -

- - - - -

- - -

- -

-
- -

- -

- -
- -

- -

- - - 'Unauthorized')); - } - - if (!igny8_is_connection_enabled()) { - wp_send_json_error(array('message' => 'Connection is disabled. Enable sync operations first.')); - } - - $post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0; - $task_id = isset($_POST['task_id']) ? intval($_POST['task_id']) : 0; - - if (!$post_id || !$task_id) { - wp_send_json_error(array('message' => 'Invalid post ID or task ID')); - } - - $api = new Igny8API(); - - if (!$api->is_authenticated()) { - wp_send_json_error(array('message' => 'Not authenticated')); - } - - // Try to fetch from Planner first - $response = $api->get("/planner/tasks/{$task_id}/brief/"); - - if (!$response['success']) { - // Fallback to Writer brief - $response = $api->get("/writer/tasks/{$task_id}/brief/"); - } - - if ($response['success'] && !empty($response['data'])) { - update_post_meta($post_id, '_igny8_task_brief', $response['data']); - update_post_meta($post_id, '_igny8_brief_cached_at', current_time('mysql')); - - wp_send_json_success(array( - 'message' => 'Brief fetched successfully', - 'brief' => $response['data'] - )); - } else { - wp_send_json_error(array( - 'message' => 'Failed to fetch brief: ' . ($response['error'] ?? 'Unknown error') - )); - } - } - - /** - * Refresh Planner task (AJAX handler) - */ - public static function refresh_planner_task() { - check_ajax_referer('igny8_post_editor_nonce', 'nonce'); - - if (!current_user_can('edit_posts')) { - wp_send_json_error(array('message' => 'Unauthorized')); - } - - if (!igny8_is_connection_enabled()) { - wp_send_json_error(array('message' => 'Connection is disabled. Enable sync operations first.')); - } - - $post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0; - $task_id = isset($_POST['task_id']) ? intval($_POST['task_id']) : 0; - - if (!$post_id || !$task_id) { - wp_send_json_error(array('message' => 'Invalid post ID or task ID')); - } - - $api = new Igny8API(); - - if (!$api->is_authenticated()) { - wp_send_json_error(array('message' => 'Not authenticated')); - } - - $response = $api->post("/planner/tasks/{$task_id}/refresh/", array( - 'wordpress_post_id' => $post_id, - 'reason' => 'reoptimize', - 'notes' => 'Requested refresh from WordPress editor' - )); - - if ($response['success']) { - wp_send_json_success(array( - 'message' => 'Refresh requested successfully', - 'data' => $response['data'] - )); - } else { - wp_send_json_error(array( - 'message' => 'Failed to request refresh: ' . ($response['error'] ?? 'Unknown error') - )); - } - } - - /** - * Create Optimizer job (AJAX handler) - */ - public static function create_optimizer_job() { - check_ajax_referer('igny8_post_editor_nonce', 'nonce'); - - if (!current_user_can('edit_posts')) { - wp_send_json_error(array('message' => 'Unauthorized')); - } - - if (!igny8_is_connection_enabled()) { - wp_send_json_error(array('message' => 'Connection is disabled. Enable sync operations first.')); - } - - $post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0; - $task_id = isset($_POST['task_id']) ? intval($_POST['task_id']) : 0; - $job_type = isset($_POST['job_type']) ? sanitize_text_field($_POST['job_type']) : 'audit'; - $priority = isset($_POST['priority']) ? sanitize_text_field($_POST['priority']) : 'normal'; - - if (!$post_id || !$task_id) { - wp_send_json_error(array('message' => 'Invalid post ID or task ID')); - } - - $api = new Igny8API(); - - if (!$api->is_authenticated()) { - wp_send_json_error(array('message' => 'Not authenticated')); - } - - $response = $api->post("/optimizer/jobs/", array( - 'post_id' => $post_id, - 'task_id' => $task_id, - 'job_type' => $job_type, - 'priority' => $priority - )); - - if ($response['success'] && !empty($response['data'])) { - $job_id = $response['data']['id'] ?? $response['data']['job_id'] ?? null; - - if ($job_id) { - update_post_meta($post_id, '_igny8_optimizer_job_id', $job_id); - update_post_meta($post_id, '_igny8_optimizer_status', $response['data']['status'] ?? 'pending'); - update_post_meta($post_id, '_igny8_optimizer_job_created_at', current_time('mysql')); - } - - wp_send_json_success(array( - 'message' => 'Optimizer job created successfully', - 'job_id' => $job_id, - 'data' => $response['data'] - )); - } else { - wp_send_json_error(array( - 'message' => 'Failed to create optimizer job: ' . ($response['error'] ?? 'Unknown error') - )); - } - } - - /** - * Get Optimizer job status (AJAX handler) - */ - public static function get_optimizer_status() { - check_ajax_referer('igny8_post_editor_nonce', 'nonce'); - - if (!current_user_can('edit_posts')) { - wp_send_json_error(array('message' => 'Unauthorized')); - } - - if (!igny8_is_connection_enabled()) { - wp_send_json_error(array('message' => 'Connection is disabled. Enable sync operations first.')); - } - - $post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0; - $job_id = isset($_POST['job_id']) ? intval($_POST['job_id']) : 0; - - if (!$post_id || !$job_id) { - wp_send_json_error(array('message' => 'Invalid post ID or job ID')); - } - - $api = new Igny8API(); - - if (!$api->is_authenticated()) { - wp_send_json_error(array('message' => 'Not authenticated')); - } - - $response = $api->get("/optimizer/jobs/{$job_id}/"); - - if ($response['success'] && !empty($response['data'])) { - $status = $response['data']['status'] ?? 'unknown'; - update_post_meta($post_id, '_igny8_optimizer_status', $status); - - if (!empty($response['data']['score_changes'])) { - update_post_meta($post_id, '_igny8_optimizer_score_changes', $response['data']['score_changes']); - } - - if (!empty($response['data']['recommendations'])) { - update_post_meta($post_id, '_igny8_optimizer_recommendations', $response['data']['recommendations']); - } - - wp_send_json_success(array( - 'message' => 'Status retrieved successfully', - 'status' => $status, - 'data' => $response['data'] - )); - } else { - wp_send_json_error(array( - 'message' => 'Failed to get status: ' . ($response['error'] ?? 'Unknown error') - )); - } - } -} - -// Initialize -new Igny8PostMetaBoxes(); - diff --git a/igny8-wp-integration-plugin/admin/settings.php b/igny8-wp-integration-plugin/admin/settings.php deleted file mode 100644 index 59c6b358..00000000 --- a/igny8-wp-integration-plugin/admin/settings.php +++ /dev/null @@ -1,584 +0,0 @@ - 10)); - $two_way_sync = (int) get_option('igny8_enable_two_way_sync', 1); - -?> - -
-

- - -
-

-
-
- -

-
- -
-
-

- -
- - - - - - - - - - - - - - - -
- - - -

- -

-
- - - -

- -

- - - - - - -
- - - -

- -

-
- - - -
- - -
-

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - -
- - - -

- -

-
- - - -
- - -
- -

- - -

-
-
-

-
-
-
-
-

- -

-
-
-
-
-

- -

-
-
-
-
-

- -

-
-
-
-
-

- -

-
-
-
-
-

- -

-
-
-
-
-

- -

-
-
-
-
-

- -

-
-
-
-
-

- -

-
-
-
-
-
-
-
- -
-

-

- - - -

-

- -

-
- - -
-

- -
- - - - - - - - - - - - - - - - - - - -
- $label) : ?> - -
- -

- -

-
- $module_label) : ?> - -
- -

- -

-
- -
- -
- - -

- -

- -
- - -
- -

- -

-
- - -
-

- - - - - - - - - - -
- - -

- -

-
- - -
- - -
-

- -

-
-
- -
-

- - -

- -

- - - - - - - - - - - - - - - - - - - -
- -

- -
- -
-

- - - - - - - - - - - - - - - - - - - -
- - - -
- -

- -
- - -
-

-

- -

-

- -

-
- - -
-

- - -
-

-
- -

-
- - -
- - - - -
- -
-
- -
-

- -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- diff --git a/igny8-wp-integration-plugin/data/link-graph.php b/igny8-wp-integration-plugin/data/link-graph.php deleted file mode 100644 index 55011eff..00000000 --- a/igny8-wp-integration-plugin/data/link-graph.php +++ /dev/null @@ -1,192 +0,0 @@ -post_content; - $source_url = get_permalink($post_id); - $site_url = get_site_url(); - $links = array(); - - // Match all anchor tags with href attributes - preg_match_all('/]+href=["\']([^"\']+)["\'][^>]*>(.*?)<\/a>/is', $content, $matches, PREG_SET_ORDER); - - foreach ($matches as $match) { - $href = $match[1]; - $anchor = strip_tags($match[2]); - - // Skip empty anchors - if (empty(trim($anchor))) { - continue; - } - - // Only process internal links - if (strpos($href, $site_url) === 0 || strpos($href, '/') === 0) { - // Convert relative URLs to absolute - if (strpos($href, '/') === 0 && strpos($href, '//') !== 0) { - $href = $site_url . $href; - } - - // Normalize URL (remove trailing slash, fragments, query params for matching) - $target_url = rtrim($href, '/'); - - // Skip if source and target are the same - if ($source_url === $target_url) { - continue; - } - - $links[] = array( - 'source_url' => $source_url, - 'target_url' => $target_url, - 'anchor' => trim($anchor), - 'post_id' => $post_id - ); - } - } - - return $links; -} - -/** - * Extract link graph from all posts - * - * @param array $post_ids Optional array of post IDs to process. If empty, processes all enabled posts. - * @return array Link graph array - */ -function igny8_extract_link_graph($post_ids = array()) { - // Skip if connection is disabled - if (!igny8_is_connection_enabled()) { - return array(); - } - - if (function_exists('igny8_is_module_enabled') && !igny8_is_module_enabled('linker')) { - return array(); - } - - $enabled_post_types = igny8_get_enabled_post_types(); - - if (empty($post_ids)) { - // Get all published posts of enabled types - $query_args = array( - 'post_type' => $enabled_post_types, - 'post_status' => 'publish', - 'posts_per_page' => -1, - 'fields' => 'ids', - 'suppress_filters' => true - ); - - $post_ids = get_posts($query_args); - } - - $link_graph = array(); - $processed = 0; - - foreach ($post_ids as $post_id) { - $links = igny8_extract_post_links($post_id); - - if (!empty($links)) { - $link_graph = array_merge($link_graph, $links); - } - - $processed++; - - // Limit processing to prevent timeout (can be increased or made configurable) - if ($processed >= 1000) { - break; - } - } - - return $link_graph; -} - -/** - * Send link graph to IGNY8 Linker module - * - * @param int $site_id IGNY8 site ID - * @param array $link_graph Link graph array (optional, will extract if not provided) - * @return array|false Response data or false on failure - */ -function igny8_send_link_graph_to_igny8($site_id, $link_graph = null) { - // Skip if connection is disabled - if (!igny8_is_connection_enabled()) { - return false; - } - - if (function_exists('igny8_is_module_enabled') && !igny8_is_module_enabled('linker')) { - return false; - } - - $api = new Igny8API(); - - if (!$api->is_authenticated()) { - return false; - } - - // Extract link graph if not provided - if ($link_graph === null) { - $link_graph = igny8_extract_link_graph(); - } - - if (empty($link_graph)) { - return array('success' => true, 'message' => 'No links found', 'links_count' => 0); - } - - // Send in batches (max 500 links per batch) - $batch_size = 500; - $batches = array_chunk($link_graph, $batch_size); - $total_sent = 0; - $errors = array(); - - foreach ($batches as $batch) { - $response = $api->post("/linker/link-map/", array( - 'site_id' => $site_id, - 'links' => $batch, - 'total_links' => count($link_graph), - 'batch_number' => count($batches) > 1 ? (count($batches) - count($batches) + array_search($batch, $batches) + 1) : 1, - 'total_batches' => count($batches) - )); - - if ($response['success']) { - $total_sent += count($batch); - } else { - $errors[] = $response['error'] ?? 'Unknown error'; - } - } - - if ($total_sent > 0) { - update_option('igny8_last_link_graph_sync', current_time('timestamp')); - update_option('igny8_last_link_graph_count', $total_sent); - - return array( - 'success' => true, - 'links_sent' => $total_sent, - 'total_links' => count($link_graph), - 'batches' => count($batches), - 'errors' => $errors - ); - } - - return false; -} - diff --git a/igny8-wp-integration-plugin/data/semantic-mapping.php b/igny8-wp-integration-plugin/data/semantic-mapping.php deleted file mode 100644 index 1aeb58aa..00000000 --- a/igny8-wp-integration-plugin/data/semantic-mapping.php +++ /dev/null @@ -1,225 +0,0 @@ - false, 'error' => 'Connection disabled'); - } - - $api = new Igny8API(); - - if (!$api->is_authenticated()) { - return array('success' => false, 'error' => 'Not authenticated'); - } - - // Extract semantic structure from site data - $semantic_map = array( - 'sectors' => array(), - 'clusters' => array(), - 'keywords' => array() - ); - - // Map taxonomies to sectors - foreach ($site_data['taxonomies'] as $tax_name => $tax_data) { - if ($tax_data['taxonomy']['hierarchical']) { - // Hierarchical taxonomies (categories) become sectors - $sector = array( - 'name' => $tax_data['taxonomy']['label'], - 'slug' => $tax_data['taxonomy']['name'], - 'description' => $tax_data['taxonomy']['description'], - 'source' => 'wordpress_taxonomy', - 'source_id' => $tax_name - ); - - // Map terms to clusters - $clusters = array(); - foreach ($tax_data['terms'] as $term) { - $clusters[] = array( - 'name' => $term['name'], - 'slug' => $term['slug'], - 'description' => $term['description'], - 'source' => 'wordpress_term', - 'source_id' => $term['id'] - ); - - // Extract keywords from posts in this term - $keywords = igny8_extract_keywords_from_term_posts($term['id'], $tax_name); - $semantic_map['keywords'] = array_merge($semantic_map['keywords'], $keywords); - } - - $sector['clusters'] = $clusters; - $semantic_map['sectors'][] = $sector; - } - } - - // Map WooCommerce product categories to sectors - if (!empty($site_data['product_categories'])) { - $product_sector = array( - 'name' => 'Products', - 'slug' => 'products', - 'description' => 'WooCommerce product categories', - 'source' => 'woocommerce', - 'clusters' => array() - ); - - foreach ($site_data['product_categories'] as $category) { - $product_sector['clusters'][] = array( - 'name' => $category['name'], - 'slug' => $category['slug'], - 'description' => $category['description'], - 'source' => 'woocommerce_category', - 'source_id' => $category['id'] - ); - } - - $semantic_map['sectors'][] = $product_sector; - } - - // Send semantic map to IGNY8 - $response = $api->post("/planner/sites/{$site_id}/semantic-map/", array( - 'semantic_map' => $semantic_map, - 'site_data' => $site_data - )); - - return $response; -} - -/** - * Extract keywords from posts associated with a taxonomy term - * - * @param int $term_id Term ID - * @param string $taxonomy Taxonomy name - * @return array Formatted keywords array - */ -function igny8_extract_keywords_from_term_posts($term_id, $taxonomy) { - $args = array( - 'post_type' => 'any', - 'posts_per_page' => -1, - 'tax_query' => array( - array( - 'taxonomy' => $taxonomy, - 'field' => 'term_id', - 'terms' => $term_id - ) - ) - ); - - $query = new WP_Query($args); - $keywords = array(); - - if ($query->have_posts()) { - while ($query->have_posts()) { - $query->the_post(); - - // Extract keywords from post title and content - $title_words = str_word_count(get_the_title(), 1); - $content_words = str_word_count(strip_tags(get_the_content()), 1); - - // Combine and get unique keywords - $all_words = array_merge($title_words, $content_words); - $unique_words = array_unique(array_map('strtolower', $all_words)); - - // Filter out common words (stop words) - $stop_words = array('the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by'); - $keywords = array_merge($keywords, array_diff($unique_words, $stop_words)); - } - wp_reset_postdata(); - } - - // Format keywords - $formatted_keywords = array(); - foreach (array_unique($keywords) as $keyword) { - if (strlen($keyword) > 3) { // Only keywords longer than 3 characters - $formatted_keywords[] = array( - 'keyword' => $keyword, - 'source' => 'wordpress_post', - 'source_term_id' => $term_id - ); - } - } - - return $formatted_keywords; -} - -/** - * Complete workflow: Fetch site data → Map to semantic strategy → Restructure content - * - * @param int $site_id IGNY8 site ID - * @return array|false Analysis result or false on failure - */ -function igny8_analyze_and_restructure_site($site_id) { - $api = new Igny8API(); - - if (!$api->is_authenticated()) { - return false; - } - - // Step 1: Collect all site data - $site_data = igny8_collect_site_data(); - - // Step 2: Send to IGNY8 for analysis - $analysis_response = $api->post("/system/sites/{$site_id}/analyze/", array( - 'site_data' => $site_data, - 'analysis_type' => 'full_site_restructure' - )); - - if (!$analysis_response['success']) { - return false; - } - - $analysis_id = $analysis_response['data']['analysis_id'] ?? null; - - // Step 3: Map to semantic strategy - $mapping_response = igny8_map_site_to_semantic_strategy($site_id, $site_data); - - if (!$mapping_response['success']) { - return false; - } - - // Step 4: Get restructuring recommendations - $recommendations_response = $api->get("/system/sites/{$site_id}/recommendations/"); - - if (!$recommendations_response['success']) { - return false; - } - - // Get keywords count from mapping response - $keywords_count = 0; - if (isset($mapping_response['data']['keywords'])) { - $keywords_count = count($mapping_response['data']['keywords']); - } - - return array( - 'analysis_id' => $analysis_id, - 'semantic_map' => $mapping_response['data'] ?? null, - 'recommendations' => $recommendations_response['data'] ?? null, - 'site_data_summary' => array( - 'total_posts' => count($site_data['posts']), - 'total_taxonomies' => count($site_data['taxonomies']), - 'total_products' => count($site_data['products'] ?? array()), - 'total_keywords' => $keywords_count - ) - ); -} - diff --git a/igny8-wp-integration-plugin/data/site-collection.php b/igny8-wp-integration-plugin/data/site-collection.php deleted file mode 100644 index e60f90f2..00000000 --- a/igny8-wp-integration-plugin/data/site-collection.php +++ /dev/null @@ -1,579 +0,0 @@ - 'publish', - 'after' => null, - 'max_pages' => 5, - ); - $args = wp_parse_args($args, $defaults); - - $post_type_object = get_post_type_object($post_type); - $rest_base = ($post_type_object && !empty($post_type_object->rest_base)) ? $post_type_object->rest_base : $post_type; - - $base_url = sprintf('%s/wp-json/wp/v2/%s', get_site_url(), $rest_base); - - $query_args = array( - 'per_page' => min($per_page, 100), - 'status' => $args['status'], - 'orderby' => 'modified', - 'order' => 'desc', - ); - - if (!empty($args['after'])) { - $query_args['after'] = gmdate('c', $args['after']); - } - - $formatted_posts = array(); - $page = 1; - - do { - $query_args['page'] = $page; - $response = wp_remote_get(add_query_arg($query_args, $base_url)); - - if (is_wp_error($response)) { - break; - } - - $posts = json_decode(wp_remote_retrieve_body($response), true); - - if (!is_array($posts) || empty($posts)) { - break; - } - - foreach ($posts as $post) { - $content = $post['content']['rendered'] ?? ''; - $word_count = str_word_count(strip_tags($content)); - - $formatted_posts[] = array( - 'id' => $post['id'], - 'title' => html_entity_decode($post['title']['rendered'] ?? ''), - 'content' => $content, - 'excerpt' => $post['excerpt']['rendered'] ?? '', - 'status' => $post['status'] ?? 'draft', - 'url' => $post['link'] ?? '', - 'published' => $post['date'] ?? '', - 'modified' => $post['modified'] ?? '', - 'author' => $post['author'] ?? 0, - 'post_type' => $post['type'] ?? $post_type, - 'taxonomies' => array( - 'categories' => $post['categories'] ?? array(), - 'tags' => $post['tags'] ?? array(), - ), - 'meta' => array( - 'word_count' => $word_count, - 'reading_time' => $word_count ? ceil($word_count / 200) : 0, - 'featured_media' => $post['featured_media'] ?? 0, - ) - ); - } - - if (count($posts) < $query_args['per_page']) { - break; - } - - $page++; - } while ($page <= $args['max_pages']); - - return $formatted_posts; -} - -/** - * Fetch all available post types from WordPress - * - * @return array|false Post types array or false on failure - */ -function igny8_fetch_all_post_types() { - $wp_response = wp_remote_get(get_site_url() . '/wp-json/wp/v2/types'); - - if (is_wp_error($wp_response)) { - return false; - } - - $types = json_decode(wp_remote_retrieve_body($wp_response), true); - - if (!is_array($types)) { - return false; - } - - $post_types = array(); - foreach ($types as $type_name => $type_data) { - if ($type_data['public']) { - $post_types[] = array( - 'name' => $type_name, - 'label' => $type_data['name'], - 'description' => $type_data['description'] ?? '', - 'rest_base' => $type_data['rest_base'] ?? $type_name - ); - } - } - - return $post_types; -} - -/** - * Fetch all posts from all post types - * - * @param int $per_page Posts per page - * @return array All posts - */ -function igny8_fetch_all_wordpress_posts($per_page = 100) { - $post_types = igny8_fetch_all_post_types(); - - if (!$post_types) { - return array(); - } - - $all_posts = array(); - foreach ($post_types as $type) { - $posts = igny8_fetch_wordpress_posts($type['name'], $per_page); - if ($posts) { - $all_posts = array_merge($all_posts, $posts); - } - } - - return $all_posts; -} - -/** - * Fetch all taxonomies from WordPress - * - * @return array|false Taxonomies array or false on failure - */ -function igny8_fetch_wordpress_taxonomies() { - $wp_response = wp_remote_get(get_site_url() . '/wp-json/wp/v2/taxonomies'); - - if (is_wp_error($wp_response)) { - return false; - } - - $taxonomies = json_decode(wp_remote_retrieve_body($wp_response), true); - - if (!is_array($taxonomies)) { - return false; - } - - $formatted_taxonomies = array(); - foreach ($taxonomies as $tax_name => $tax_data) { - if ($tax_data['public']) { - $formatted_taxonomies[] = array( - 'name' => $tax_name, - 'label' => $tax_data['name'], - 'description' => $tax_data['description'] ?? '', - 'hierarchical' => $tax_data['hierarchical'], - 'rest_base' => $tax_data['rest_base'] ?? $tax_name, - 'object_types' => $tax_data['types'] ?? array() - ); - } - } - - return $formatted_taxonomies; -} - -/** - * Fetch all terms for a specific taxonomy - * - * @param string $taxonomy Taxonomy name - * @param int $per_page Terms per page - * @return array|false Formatted terms array or false on failure - */ -function igny8_fetch_taxonomy_terms($taxonomy, $per_page = 100) { - $taxonomy_obj = get_taxonomy($taxonomy); - $rest_base = ($taxonomy_obj && !empty($taxonomy_obj->rest_base)) ? $taxonomy_obj->rest_base : $taxonomy; - - $base_url = sprintf('%s/wp-json/wp/v2/%s', get_site_url(), $rest_base); - - $formatted_terms = array(); - $page = 1; - - do { - $response = wp_remote_get(add_query_arg(array( - 'per_page' => min($per_page, 100), - 'page' => $page - ), $base_url)); - - if (is_wp_error($response)) { - break; - } - - $terms = json_decode(wp_remote_retrieve_body($response), true); - - if (!is_array($terms) || empty($terms)) { - break; - } - - foreach ($terms as $term) { - $formatted_terms[] = array( - 'id' => $term['id'], - 'name' => $term['name'], - 'slug' => $term['slug'], - 'description' => $term['description'] ?? '', - 'count' => $term['count'], - 'parent' => $term['parent'] ?? 0, - 'taxonomy' => $taxonomy, - 'url' => $term['link'] ?? '' - ); - } - - if (count($terms) < min($per_page, 100)) { - break; - } - - $page++; - } while (true); - - return $formatted_terms; -} - -/** - * Fetch all terms from all taxonomies - * - * @param int $per_page Terms per page - * @return array All terms organized by taxonomy - */ -function igny8_fetch_all_taxonomy_terms($per_page = 100) { - $taxonomies = igny8_fetch_wordpress_taxonomies(); - - if (!$taxonomies) { - return array(); - } - - $all_terms = array(); - foreach ($taxonomies as $taxonomy) { - $terms = igny8_fetch_taxonomy_terms($taxonomy['rest_base'], $per_page); - if ($terms) { - $all_terms[$taxonomy['name']] = $terms; - } - } - - return $all_terms; -} - -/** - * Collect all WordPress site data for IGNY8 semantic mapping - * - * @return array Complete site data - */ -function igny8_collect_site_data($args = array()) { - // Skip if connection is disabled - if (!igny8_is_connection_enabled()) { - return array('disabled' => true, 'reason' => 'connection_disabled'); - } - - if (function_exists('igny8_is_module_enabled') && !igny8_is_module_enabled('sites')) { - return array('disabled' => true); - } - - $settings = igny8_get_site_scan_settings($args); - - $site_data = array( - 'site_url' => get_site_url(), - 'site_name' => get_bloginfo('name'), - 'site_description' => get_bloginfo('description'), - 'collected_at' => current_time('mysql'), - 'settings' => $settings, - 'posts' => array(), - 'taxonomies' => array(), - 'products' => array(), - 'product_categories' => array(), - 'product_attributes' => array() - ); - - foreach ((array) $settings['post_types'] as $post_type) { - if (!post_type_exists($post_type) || !igny8_is_post_type_enabled($post_type)) { - continue; - } - - $posts = igny8_fetch_wordpress_posts($post_type, $settings['per_page'], array( - 'after' => $settings['since'], - 'status' => 'publish' - )); - - if ($posts) { - $site_data['posts'] = array_merge($site_data['posts'], $posts); - } - } - - $tracked_taxonomies = array('category', 'post_tag', 'igny8_sectors', 'igny8_clusters'); - foreach ($tracked_taxonomies as $taxonomy) { - if (!taxonomy_exists($taxonomy)) { - continue; - } - - $terms = igny8_fetch_taxonomy_terms($taxonomy, 100); - if ($terms) { - $tax_obj = get_taxonomy($taxonomy); - $site_data['taxonomies'][$taxonomy] = array( - 'taxonomy' => array( - 'name' => $taxonomy, - 'label' => $tax_obj ? $tax_obj->label : $taxonomy, - 'description' => $tax_obj->description ?? '', - 'hierarchical' => $tax_obj ? $tax_obj->hierarchical : false, - ), - 'terms' => $terms - ); - } - } - - if (!empty($settings['include_products']) && function_exists('igny8_is_woocommerce_active') && igny8_is_woocommerce_active()) { - require_once IGNY8_BRIDGE_PLUGIN_DIR . 'data/woocommerce.php'; - - $products = igny8_fetch_woocommerce_products(100); - if ($products) { - $site_data['products'] = $products; - } - - $product_categories = igny8_fetch_product_categories(100); - if ($product_categories) { - $site_data['product_categories'] = $product_categories; - } - - $product_attributes = igny8_fetch_product_attributes(); - if ($product_attributes) { - $site_data['product_attributes'] = $product_attributes; - } - } - - // Extract link graph if Linker module is enabled - if (function_exists('igny8_is_module_enabled') && igny8_is_module_enabled('linker')) { - $post_ids = wp_list_pluck($site_data['posts'], 'id'); - $link_graph = igny8_extract_link_graph($post_ids); - - if (!empty($link_graph)) { - $site_data['link_graph'] = $link_graph; - } - } - - $site_data['summary'] = array( - 'posts' => count($site_data['posts']), - 'taxonomies' => count($site_data['taxonomies']), - 'products' => count($site_data['products']), - 'links' => isset($site_data['link_graph']) ? count($site_data['link_graph']) : 0 - ); - - update_option('igny8_last_site_snapshot', array( - 'timestamp' => current_time('timestamp'), - 'summary' => $site_data['summary'] - )); - - return $site_data; -} - -/** - * Send WordPress site data to IGNY8 for semantic strategy mapping - * - * @param int $site_id IGNY8 site ID - * @return array|false Response data or false on failure - */ -function igny8_send_site_data_to_igny8($site_id, $site_data = null, $args = array()) { - // Skip if connection is disabled - if (!igny8_is_connection_enabled()) { - return false; - } - - $api = new Igny8API(); - - if (!$api->is_authenticated()) { - return false; - } - - // Collect all site data if not provided - if (empty($site_data)) { - $site_data = igny8_collect_site_data($args); - } - - if (empty($site_data) || isset($site_data['disabled'])) { - return false; - } - - // Send to IGNY8 API - $response = $api->post("/system/sites/{$site_id}/import/", array( - 'site_data' => $site_data, - 'import_type' => $args['mode'] ?? 'full_site_scan' - )); - - if ($response['success']) { - // Store import ID for tracking - update_option('igny8_last_site_import_id', $response['data']['import_id'] ?? null); - update_option('igny8_last_site_sync', current_time('timestamp')); - - // Send link graph separately to Linker module if available - if (!empty($site_data['link_graph']) && function_exists('igny8_is_module_enabled') && igny8_is_module_enabled('linker')) { - $link_result = igny8_send_link_graph_to_igny8($site_id, $site_data['link_graph']); - if ($link_result) { - error_log(sprintf('IGNY8: Sent %d links to Linker module', $link_result['links_sent'] ?? 0)); - } - } - - return $response['data']; - } else { - error_log("IGNY8: Failed to send site data: " . ($response['error'] ?? 'Unknown error')); - return false; - } -} - -/** - * Sync only changed posts/taxonomies since last sync - * - * @param int $site_id IGNY8 site ID - * @return array|false Sync result or false on failure - */ -function igny8_sync_incremental_site_data($site_id, $settings = array()) { - // Skip if connection is disabled - if (!igny8_is_connection_enabled()) { - return array('synced' => 0, 'message' => 'Connection disabled'); - } - - $api = new Igny8API(); - - if (!$api->is_authenticated()) { - return false; - } - - $settings = igny8_get_site_scan_settings(wp_parse_args($settings, array('mode' => 'incremental'))); - $since = $settings['since'] ?? intval(get_option('igny8_last_site_sync', 0)); - - $formatted_posts = array(); - - foreach ((array) $settings['post_types'] as $post_type) { - if (!post_type_exists($post_type) || !igny8_is_post_type_enabled($post_type)) { - continue; - } - - $query_args = array( - 'post_type' => $post_type, - 'post_status' => array('publish', 'pending', 'draft', 'future'), - 'posts_per_page' => -1, - 'orderby' => 'modified', - 'order' => 'DESC', - 'suppress_filters' => true, - ); - - if ($since) { - $query_args['date_query'] = array( - array( - 'column' => 'post_modified_gmt', - 'after' => gmdate('Y-m-d H:i:s', $since) - ) - ); - } - - $posts = get_posts($query_args); - - foreach ($posts as $post) { - $word_count = str_word_count(strip_tags($post->post_content)); - - $formatted_posts[] = array( - 'id' => $post->ID, - 'title' => get_the_title($post), - 'content' => $post->post_content, - 'status' => $post->post_status, - 'modified' => $post->post_modified_gmt, - 'post_type' => $post->post_type, - 'url' => get_permalink($post), - 'taxonomies' => array( - 'categories' => wp_get_post_terms($post->ID, 'category', array('fields' => 'ids')), - 'tags' => wp_get_post_terms($post->ID, 'post_tag', array('fields' => 'ids')), - ), - 'meta' => array( - 'task_id' => get_post_meta($post->ID, '_igny8_task_id', true), - 'cluster_id' => get_post_meta($post->ID, '_igny8_cluster_id', true), - 'sector_id' => get_post_meta($post->ID, '_igny8_sector_id', true), - 'word_count' => $word_count, - ) - ); - } - } - - if (empty($formatted_posts)) { - return array('synced' => 0, 'message' => 'No changes since last sync'); - } - - $response = $api->post("/system/sites/{$site_id}/sync/", array( - 'posts' => $formatted_posts, - 'sync_type' => 'incremental', - 'last_sync' => $since, - 'post_types' => $settings['post_types'] - )); - - if ($response['success']) { - update_option('igny8_last_site_sync', current_time('timestamp')); - update_option('igny8_last_incremental_site_sync', array( - 'timestamp' => current_time('timestamp'), - 'count' => count($formatted_posts) - )); - - return array( - 'synced' => count($formatted_posts), - 'message' => 'Incremental sync completed' - ); - } - - return false; -} - -/** - * Run a full site scan and semantic mapping - * - * @param int $site_id IGNY8 site ID - * @param array $settings Scan settings - * @return array|false - */ -function igny8_perform_full_site_scan($site_id, $settings = array()) { - $site_data = igny8_collect_site_data($settings); - - if (empty($site_data) || isset($site_data['disabled'])) { - return false; - } - - $import = igny8_send_site_data_to_igny8($site_id, $site_data, array('mode' => 'full_site_scan')); - - if (!$import) { - return false; - } - - update_option('igny8_last_full_site_scan', current_time('timestamp')); - - // Map to semantic strategy (requires Planner module) - if (!function_exists('igny8_is_module_enabled') || igny8_is_module_enabled('planner')) { - $map_response = igny8_map_site_to_semantic_strategy($site_id, $site_data); - if (!empty($map_response['success'])) { - update_option('igny8_last_semantic_map', current_time('timestamp')); - update_option('igny8_last_semantic_map_summary', array( - 'sectors' => count($map_response['data']['sectors'] ?? array()), - 'keywords' => count($map_response['data']['keywords'] ?? array()) - )); - } - } - - // Send link graph to Linker module if available - if (!empty($site_data['link_graph']) && function_exists('igny8_is_module_enabled') && igny8_is_module_enabled('linker')) { - $link_result = igny8_send_link_graph_to_igny8($site_id, $site_data['link_graph']); - if ($link_result) { - error_log(sprintf('IGNY8: Sent %d links to Linker module during full scan', $link_result['links_sent'] ?? 0)); - } - } - - return $import; -} - diff --git a/igny8-wp-integration-plugin/data/woocommerce.php b/igny8-wp-integration-plugin/data/woocommerce.php deleted file mode 100644 index 7f01218e..00000000 --- a/igny8-wp-integration-plugin/data/woocommerce.php +++ /dev/null @@ -1,226 +0,0 @@ - $headers - )); - - if (is_wp_error($wp_response)) { - return false; - } - - $products = json_decode(wp_remote_retrieve_body($wp_response), true); - - if (!is_array($products)) { - return false; - } - - $formatted_products = array(); - foreach ($products as $product) { - $formatted_products[] = array( - 'id' => $product['id'], - 'name' => $product['name'], - 'slug' => $product['slug'], - 'sku' => $product['sku'], - 'type' => $product['type'], - 'status' => $product['status'], - 'description' => $product['description'], - 'short_description' => $product['short_description'], - 'price' => $product['price'], - 'regular_price' => $product['regular_price'], - 'sale_price' => $product['sale_price'], - 'on_sale' => $product['on_sale'], - 'stock_status' => $product['stock_status'], - 'stock_quantity' => $product['stock_quantity'], - 'categories' => $product['categories'] ?? array(), - 'tags' => $product['tags'] ?? array(), - 'images' => $product['images'] ?? array(), - 'attributes' => $product['attributes'] ?? array(), - 'variations' => $product['variations'] ?? array(), - 'url' => $product['permalink'] - ); - } - - return $formatted_products; -} - -/** - * Fetch WooCommerce product categories - * - * @param int $per_page Categories per page - * @return array|false Formatted categories array or false on failure - */ -function igny8_fetch_product_categories($per_page = 100) { - if (!igny8_is_woocommerce_active()) { - return false; - } - - $consumer_key = get_option('woocommerce_api_consumer_key', ''); - $consumer_secret = get_option('woocommerce_api_consumer_secret', ''); - - $headers = array(); - if ($consumer_key && $consumer_secret) { - $headers['Authorization'] = 'Basic ' . base64_encode($consumer_key . ':' . $consumer_secret); - } - - $wp_response = wp_remote_get(sprintf( - '%s/wp-json/wc/v3/products/categories?per_page=%d', - get_site_url(), - $per_page - ), array( - 'headers' => $headers - )); - - if (is_wp_error($wp_response)) { - return false; - } - - $categories = json_decode(wp_remote_retrieve_body($wp_response), true); - - if (!is_array($categories)) { - return false; - } - - $formatted_categories = array(); - foreach ($categories as $category) { - $formatted_categories[] = array( - 'id' => $category['id'], - 'name' => $category['name'], - 'slug' => $category['slug'], - 'description' => $category['description'] ?? '', - 'count' => $category['count'], - 'parent' => $category['parent'] ?? 0, - 'image' => $category['image']['src'] ?? null - ); - } - - return $formatted_categories; -} - -/** - * Fetch WooCommerce product attributes - * - * @return array|false Formatted attributes array or false on failure - */ -function igny8_fetch_product_attributes() { - if (!igny8_is_woocommerce_active()) { - return false; - } - - $consumer_key = get_option('woocommerce_api_consumer_key', ''); - $consumer_secret = get_option('woocommerce_api_consumer_secret', ''); - - $headers = array(); - if ($consumer_key && $consumer_secret) { - $headers['Authorization'] = 'Basic ' . base64_encode($consumer_key . ':' . $consumer_secret); - } - - $wp_response = wp_remote_get( - get_site_url() . '/wp-json/wc/v3/products/attributes', - array( - 'headers' => $headers - ) - ); - - if (is_wp_error($wp_response)) { - return false; - } - - $attributes = json_decode(wp_remote_retrieve_body($wp_response), true); - - if (!is_array($attributes)) { - return false; - } - - $formatted_attributes = array(); - foreach ($attributes as $attribute) { - // Get attribute terms - $terms_response = wp_remote_get(sprintf( - '%s/wp-json/wc/v3/products/attributes/%d/terms', - get_site_url(), - $attribute['id'] - ), array( - 'headers' => $headers - )); - - $terms = array(); - if (!is_wp_error($terms_response)) { - $terms_data = json_decode(wp_remote_retrieve_body($terms_response), true); - if (is_array($terms_data)) { - foreach ($terms_data as $term) { - $terms[] = array( - 'id' => $term['id'], - 'name' => $term['name'], - 'slug' => $term['slug'] - ); - } - } - } - - $formatted_attributes[] = array( - 'id' => $attribute['id'], - 'name' => $attribute['name'], - 'slug' => $attribute['slug'], - 'type' => $attribute['type'], - 'order_by' => $attribute['order_by'], - 'has_archives' => $attribute['has_archives'], - 'terms' => $terms - ); - } - - return $formatted_attributes; -} - diff --git a/igny8-wp-integration-plugin/docs/PHASE_5_IMPLEMENTATION_SUMMARY.md b/igny8-wp-integration-plugin/docs/PHASE_5_IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index eec33324..00000000 --- a/igny8-wp-integration-plugin/docs/PHASE_5_IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,185 +0,0 @@ -# Phase 5 Implementation Summary - -## Overview -Phase 5 implementation adds Planner, Linker, and Optimizer module hooks to the WordPress bridge plugin. - -## Implemented Features - -### 5.1 Planner Briefs Display & Refresh Actions ✅ - -**Files Created:** -- `admin/class-post-meta-boxes.php` - Meta boxes for post editor -- `admin/assets/js/post-editor.js` - JavaScript for AJAX interactions - -**Features:** -- **Planner Brief Meta Box** in post editor sidebar - - Displays cached brief with title, content, outline, keywords, and tone - - Shows cache timestamp - - "Fetch Brief" button to load from IGNY8 API - - "Request Refresh" button to trigger Planner refresh - -**API Endpoints Used:** -- `GET /planner/tasks/{id}/brief/` - Fetch Planner brief (with fallback to Writer brief) -- `POST /planner/tasks/{id}/refresh/` - Request Planner task refresh - -**Meta Fields Added:** -- `_igny8_task_brief` - Cached brief data -- `_igny8_brief_cached_at` - Brief cache timestamp - -**AJAX Handlers:** -- `igny8_fetch_planner_brief` - Fetches and caches brief -- `igny8_refresh_planner_task` - Requests Planner refresh - ---- - -### 5.2 Link Graph Export ✅ - -**Files Created:** -- `data/link-graph.php` - Link graph extraction and export - -**Features:** -- **Link Extraction** from post content - - Extracts all internal links (anchor tags) - - Captures source URL, target URL, and anchor text - - Filters to only internal links (same domain) - - Normalizes URLs for consistency - -- **Link Graph Collection** - - Processes all enabled post types - - Extracts links from published posts - - Configurable batch processing (max 1000 posts per run) - -- **Automatic Export During Site Scans** - - Integrated into `igny8_collect_site_data()` - - Included in site data payload - - Also sent separately to Linker module endpoint - -**API Endpoints Used:** -- `POST /linker/link-map/` - Send link graph to Linker module - -**Functions:** -- `igny8_extract_post_links($post_id)` - Extract links from single post -- `igny8_extract_link_graph($post_ids)` - Extract links from multiple posts -- `igny8_send_link_graph_to_igny8($site_id, $link_graph)` - Send to IGNY8 API - -**Integration Points:** -- `igny8_collect_site_data()` - Includes link graph in site data -- `igny8_send_site_data_to_igny8()` - Sends link graph after site import -- `igny8_perform_full_site_scan()` - Sends link graph during full scans - -**Options Stored:** -- `igny8_last_link_graph_sync` - Last sync timestamp -- `igny8_last_link_graph_count` - Number of links sent - ---- - -### 5.4 Optimizer Triggers ✅ - -**Features:** -- **Optimizer Meta Box** in post editor sidebar - - Displays current optimizer job ID and status - - "Request Optimization" button to create new job - - "Check Status" button to fetch latest job status - - Shows score changes and recommendations when available - -**API Endpoints Used:** -- `POST /optimizer/jobs/` - Create optimizer job -- `GET /optimizer/jobs/{id}/` - Get optimizer job status - -**Meta Fields Added:** -- `_igny8_optimizer_job_id` - Optimizer job ID -- `_igny8_optimizer_status` - Job status (pending, processing, completed, failed) -- `_igny8_optimizer_score_changes` - Score changes from optimization -- `_igny8_optimizer_recommendations` - Optimization recommendations -- `_igny8_optimizer_job_created_at` - Job creation timestamp - -**AJAX Handlers:** -- `igny8_create_optimizer_job` - Creates new optimizer job -- `igny8_get_optimizer_status` - Fetches job status and updates meta - -**Job Parameters:** -- `post_id` - WordPress post ID -- `task_id` - IGNY8 task ID -- `job_type` - Type of job (default: 'audit') -- `priority` - Job priority (default: 'normal') - ---- - -## Module Toggle Support - -All Phase 5 features respect the module toggle settings: -- **Planner** module must be enabled for brief fetching/refresh -- **Linker** module must be enabled for link graph export -- **Optimizer** module must be enabled for optimizer jobs - -Features also check `igny8_is_connection_enabled()` to ensure sync operations are active. - ---- - -## UI/UX Features - -### Post Editor Meta Boxes -- Clean, organized display in sidebar -- Real-time AJAX updates -- Success/error message display -- Auto-refresh after operations -- Disabled state when connection is off - -### JavaScript Enhancements -- Loading states on buttons -- Confirmation dialogs for destructive actions -- Error handling and user feedback -- Non-blocking AJAX requests - ---- - -## Integration with Existing Code - -### Modified Files: -- `igny8-bridge.php` - Added meta boxes class loading -- `includes/functions.php` - Added optimizer meta field registrations -- `data/site-collection.php` - Integrated link graph extraction - -### New Dependencies: -- None (uses existing Igny8API class) - ---- - -## Testing Checklist - -- [ ] Planner brief displays correctly in post editor -- [ ] Fetch brief button works and caches data -- [ ] Request refresh button triggers Planner refresh -- [ ] Link graph extraction works on posts with links -- [ ] Link graph is included in site scans -- [ ] Link graph is sent to Linker endpoint -- [ ] Optimizer job creation works -- [ ] Optimizer status check works -- [ ] All features respect module toggles -- [ ] All features respect connection enabled toggle -- [ ] Meta boxes only show for posts with task IDs -- [ ] Error handling works correctly - ---- - -## Notes - -1. **Link Graph Processing**: Currently limited to 1000 posts per run to prevent timeouts. Can be increased or made configurable. - -2. **Brief Caching**: Briefs are cached in post meta to reduce API calls. Cache can be refreshed manually. - -3. **Optimizer Jobs**: Jobs are created asynchronously. Status must be checked manually or via webhook (Phase 6). - -4. **Module Dependencies**: All features check module enablement before executing. - -5. **Connection Toggle**: All features respect the master connection toggle added earlier. - ---- - -## Next Steps (Phase 6) - -Phase 5.3 (Accept link recommendations via webhook) is deferred to Phase 6, which will implement: -- REST endpoint `/wp-json/igny8/v1/event` with shared secret -- Webhook handler for link recommendations -- Link insertion queue system - diff --git a/igny8-wp-integration-plugin/docs/PHASE_6_IMPLEMENTATION_SUMMARY.md b/igny8-wp-integration-plugin/docs/PHASE_6_IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index 827c88b9..00000000 --- a/igny8-wp-integration-plugin/docs/PHASE_6_IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,298 +0,0 @@ -# Phase 6 Implementation Summary - -## Overview -Phase 6 implementation adds webhook support and remote control capabilities, allowing IGNY8 SaaS to send events to WordPress. - -## Implemented Features - -### 6.1 REST Route Registration with Shared Secret ✅ - -**Files Created:** -- `includes/class-igny8-webhooks.php` - Main webhook handler class - -**Features:** -- **REST Endpoint**: `/wp-json/igny8/v1/event` (POST) -- **Shared Secret Authentication**: HMAC-SHA256 signature verification -- **Connection Check**: All webhook handlers verify `igny8_is_connection_enabled()` before processing - -**Security:** -- Webhook secret stored in WordPress options (auto-generated on first use) -- Signature verification via `X-IGNY8-Signature` header -- Secret can be regenerated from settings page -- Failed authentication attempts are logged - -**Functions:** -- `igny8_get_webhook_secret()` - Get or generate webhook secret -- `igny8_regenerate_webhook_secret()` - Regenerate secret - ---- - -### 6.2 SaaS Event Handlers ✅ - -**Event Types Supported:** - -#### 1. Task Published (`task_published`, `task_completed`) -- Creates or updates WordPress post from IGNY8 task -- Fetches full task data from Writer API -- Respects enabled post types -- Updates post status if task is completed - -#### 2. Link Recommendation (`link_recommendation`, `insert_link`) -- Queues link insertion for processing -- Validates required parameters (post_id, target_url, anchor) -- Respects Linker module toggle -- Processes links asynchronously via cron - -#### 3. Optimizer Request (`optimizer_request`, `optimizer_job_completed`) -- Updates optimizer job status -- Stores score changes and recommendations -- Updates post meta with optimizer data -- Respects Optimizer module toggle - -**Event Handler Flow:** -1. Verify connection is enabled -2. Verify module is enabled (if applicable) -3. Validate event data -4. Process event -5. Log activity -6. Return unified JSON response - ---- - -### 6.3 Webhook Activity Logging ✅ - -**Files Created:** -- `includes/class-igny8-webhook-logs.php` - Logging functions - -**Features:** -- **Log Storage**: WordPress options (last 500 logs) -- **Log Fields**: - - Event type - - Event data - - IP address - - User agent - - Status (received, processed, failed) - - Response data - - Timestamps (received_at, processed_at) - - Error messages - -**Functions:** -- `igny8_log_webhook_activity()` - Log webhook receipt -- `igny8_update_webhook_log()` - Update log with processing result -- `igny8_get_webhook_logs()` - Retrieve logs with filtering -- `igny8_clear_old_webhook_logs()` - Cleanup old logs - -**UI Display:** -- Recent webhook activity table in settings page -- Shows last 10 webhook events -- Displays event type, status, and timestamp - ---- - -### Link Queue System ✅ - -**Files Created:** -- `includes/class-igny8-link-queue.php` - Link insertion queue - -**Features:** -- **Queue Storage**: WordPress options -- **Queue Processing**: Cron-based (processes 10 items per run) -- **Retry Logic**: Up to 3 attempts per link -- **Status Tracking**: pending, completed, failed - -**Link Insertion Logic:** -1. Finds anchor text in post content -2. Wraps anchor with link tag -3. Avoids duplicate links -4. Falls back to appending link if anchor not found - -**Queue Management:** -- Automatic processing via cron -- Manual trigger available -- Queue size limit (1000 items) -- Status tracking per item - ---- - -## Connection Checks - -**All handlers check connection status:** - -✅ **Webhook Handler** (`verify_webhook_secret`) -- Checks `igny8_is_connection_enabled()` before allowing webhook - -✅ **Event Handlers** (`handle_webhook`, `handle_task_published`, etc.) -- Double-checks connection enabled before processing -- Returns error if connection disabled - -✅ **Link Queue** (`igny8_queue_link_insertion`, `igny8_process_link_queue`) -- Checks connection enabled before queuing/processing - -✅ **REST API Endpoints** (existing endpoints) -- Updated to check connection enabled -- Returns 403 if connection disabled - ---- - -## Settings UI Enhancements - -**New Sections Added:** - -1. **Webhook Configuration** - - Webhook URL display with copy button - - Webhook secret display with copy button - - Regenerate secret button - -2. **Link Queue** - - Shows pending links count - - Displays queue table (last 10 items) - - Shows post ID, anchor, target URL, status - -3. **Recent Webhook Activity** - - Shows last 10 webhook events - - Displays event type, status, timestamp - - Color-coded status badges - ---- - -## Security Features - -1. **HMAC Signature Verification** - - Uses SHA-256 HMAC - - Compares request body signature - - Prevents replay attacks - -2. **Connection Toggle Protection** - - All endpoints check connection status - - Webhooks rejected if connection disabled - - Clear error messages - -3. **Module Toggle Respect** - - Events only processed if module enabled - - Graceful error responses - -4. **Input Validation** - - All parameters sanitized - - Required fields validated - - Type checking - ---- - -## API Response Format - -All webhook responses follow unified JSON format: - -**Success:** -```json -{ - "success": true, - "message": "Event processed", - "data": { ... } -} -``` - -**Error:** -```json -{ - "success": false, - "error": "Error message", - "code": "error_code" -} -``` - ---- - -## Integration Points - -### Modified Files: -- `igny8-bridge.php` - Added webhook classes loading -- `includes/functions.php` - Added webhook secret functions -- `includes/class-igny8-rest-api.php` - Added connection checks -- `admin/settings.php` - Added webhook UI sections -- `admin/class-admin.php` - Added secret regeneration handler - -### New Dependencies: -- None (uses existing WordPress functions) - ---- - -## Testing Checklist - -- [ ] Webhook endpoint accessible at `/wp-json/igny8/v1/event` -- [ ] Signature verification works correctly -- [ ] Invalid signatures are rejected -- [ ] Connection disabled blocks webhooks -- [ ] Task published event creates/updates posts -- [ ] Link recommendation queues links -- [ ] Link queue processes links correctly -- [ ] Optimizer events update post meta -- [ ] Webhook logs are created and updated -- [ ] Settings UI displays webhook info -- [ ] Secret regeneration works -- [ ] All events respect module toggles -- [ ] All events respect connection toggle - ---- - -## Notes - -1. **Webhook Secret**: Auto-generated on first use. Must be configured in IGNY8 SaaS app. - -2. **Link Queue**: Processes 10 items per cron run to prevent timeouts. Can be adjusted. - -3. **Log Retention**: Keeps last 500 logs. Older logs can be cleared manually. - -4. **Signature Header**: IGNY8 SaaS must send `X-IGNY8-Signature` header with HMAC-SHA256 signature of request body. - -5. **Connection Toggle**: All webhook handlers check connection status before processing. This ensures no data is processed when connection is disabled. - -6. **Module Toggles**: Each event type checks if its module is enabled before processing. - ---- - -## Webhook Payload Examples - -### Task Published -```json -{ - "event": "task_published", - "data": { - "task_id": 123, - "status": "completed" - } -} -``` - -### Link Recommendation -```json -{ - "event": "link_recommendation", - "data": { - "post_id": 456, - "target_url": "https://example.com/page", - "anchor": "example link", - "priority": "normal" - } -} -``` - -### Optimizer Request -```json -{ - "event": "optimizer_job_completed", - "data": { - "post_id": 456, - "job_id": 789, - "status": "completed", - "score_changes": { ... }, - "recommendations": [ ... ] - } -} -``` - ---- - -## Next Steps - -Phase 6 is complete. All webhook functionality is implemented with proper security, logging, and connection checks. - diff --git a/igny8-wp-integration-plugin/docs/STATUS_SYNC_DOCUMENTATION.md b/igny8-wp-integration-plugin/docs/STATUS_SYNC_DOCUMENTATION.md deleted file mode 100644 index 14591a62..00000000 --- a/igny8-wp-integration-plugin/docs/STATUS_SYNC_DOCUMENTATION.md +++ /dev/null @@ -1,249 +0,0 @@ -# Status Sync & Content ID Documentation - -**Last Updated**: 2025-10-17 - ---- - -## Overview - -This document explains how the plugin handles status synchronization and content_id tracking between IGNY8 and WordPress. - ---- - -## Status Mapping - -### IGNY8 → WordPress - -When content arrives from IGNY8, status is mapped as follows: - -| IGNY8 Status | WordPress Status | Description | -|--------------|------------------|-------------| -| `completed` | `publish` | Content is published | -| `draft` | `draft` | Content is draft | -| `pending` | `pending` | Content pending review | -| `scheduled` | `future` | Content scheduled | -| `archived` | `trash` | Content archived | - -### WordPress → IGNY8 - -When WordPress post status changes, it's mapped back to IGNY8: - -| WordPress Status | IGNY8 Status | Description | -|------------------|--------------|-------------| -| `publish` | `completed` | Post is published | -| `draft` | `draft` | Post is draft | -| `pending` | `pending` | Post pending review | -| `private` | `completed` | Post is private (published) | -| `trash` | `archived` | Post is deleted | -| `future` | `scheduled` | Post is scheduled | - ---- - -## Content ID Tracking - -### Storage - -The plugin stores IGNY8 `content_id` in post meta: -- **Meta Key**: `_igny8_content_id` -- **Type**: Integer -- **REST API**: Available via `/wp-json/wp/v2/posts?meta_key=_igny8_content_id&meta_value=123` - -### Task ID Tracking - -The plugin also stores IGNY8 `task_id`: -- **Meta Key**: `_igny8_task_id` -- **Type**: Integer -- **REST API**: Available via `/wp-json/wp/v2/posts?meta_key=_igny8_task_id&meta_value=456` - ---- - -## Response to IGNY8 - -When content is created/updated in WordPress, the plugin responds to IGNY8 with: - -```json -{ - "assigned_post_id": 123, - "post_url": "https://example.com/post/", - "wordpress_status": "publish", - "status": "completed", - "synced_at": "2025-10-17 12:00:00", - "post_type": "post", - "content_type": "post", - "content_id": 789 -} -``` - -### Response Fields - -- **`assigned_post_id`**: WordPress post ID -- **`post_url`**: Full URL to the post -- **`wordpress_status`**: Actual WordPress status (publish/pending/draft) -- **`status`**: IGNY8 mapped status (completed/pending/draft) -- **`synced_at`**: Timestamp of sync -- **`post_type`**: WordPress post type -- **`content_type`**: IGNY8 content type -- **`content_id`**: IGNY8 content ID (if provided) - ---- - -## REST API Endpoints - -The plugin provides REST API endpoints for IGNY8 to query WordPress: - -### 1. Get Post by Content ID - -**Endpoint**: `GET /wp-json/igny8/v1/post-by-content-id/{content_id}` - -**Response**: -```json -{ - "success": true, - "data": { - "post_id": 123, - "title": "Post Title", - "status": "publish", - "wordpress_status": "publish", - "igny8_status": "completed", - "url": "https://example.com/post/", - "post_type": "post", - "content_id": 789, - "task_id": 456, - "last_synced": "2025-10-17 12:00:00" - } -} -``` - -### 2. Get Post by Task ID - -**Endpoint**: `GET /wp-json/igny8/v1/post-by-task-id/{task_id}` - -**Response**: Same format as above - -### 3. Get Post Status by Content ID - -**Endpoint**: `GET /wp-json/igny8/v1/post-status/{content_id}` - -**Response**: -```json -{ - "success": true, - "data": { - "post_id": 123, - "wordpress_status": "publish", - "igny8_status": "completed", - "status_mapping": { - "publish": "completed", - "draft": "draft", - "pending": "pending", - "private": "completed", - "trash": "archived", - "future": "scheduled" - }, - "content_id": 789, - "url": "https://example.com/post/", - "last_synced": "2025-10-17 12:00:00" - } -} -``` - ---- - -## Status Flow - -### When Content Arrives from IGNY8 - -1. **Receive** content with `content_type`, `status`, `content_id`, `task_id` -2. **Map** IGNY8 status to WordPress status -3. **Create** WordPress post with mapped status -4. **Store** `content_id` and `task_id` in post meta -5. **Respond** to IGNY8 with: - - WordPress post ID - - WordPress actual status - - IGNY8 mapped status - - Post URL - - Content ID - -### When WordPress Status Changes - -1. **Detect** status change via `save_post` or `transition_post_status` hook -2. **Get** `task_id` and `content_id` from post meta -3. **Map** WordPress status to IGNY8 status -4. **Update** IGNY8 task with: - - WordPress actual status - - IGNY8 mapped status - - Post URL - - Content ID (if available) - - Sync timestamp - ---- - -## Available Meta Fields - -All fields are available for IGNY8 to read via REST API: - -- `_igny8_task_id` - IGNY8 task ID -- `_igny8_content_id` - IGNY8 content ID -- `_igny8_cluster_id` - IGNY8 cluster ID -- `_igny8_sector_id` - IGNY8 sector ID -- `_igny8_keyword_ids` - Array of keyword IDs -- `_igny8_wordpress_status` - WordPress post status -- `_igny8_last_synced` - Last sync timestamp - ---- - -## Query Examples - -### Via WordPress REST API - -```bash -# Get post by content_id -GET /wp-json/wp/v2/posts?meta_key=_igny8_content_id&meta_value=123 - -# Get post by task_id -GET /wp-json/wp/v2/posts?meta_key=_igny8_task_id&meta_value=456 - -# Get all IGNY8 posts -GET /wp-json/wp/v2/posts?meta_key=_igny8_task_id -``` - -### Via IGNY8 REST API Endpoints - -```bash -# Get post by content_id (with status info) -GET /wp-json/igny8/v1/post-by-content-id/123 - -# Get post status only -GET /wp-json/igny8/v1/post-status/123 - -# Get post by task_id -GET /wp-json/igny8/v1/post-by-task-id/456 -``` - ---- - -## Authentication - -REST API endpoints require: -- IGNY8 API authentication (access token) -- Authorization header: `Bearer {access_token}` - -Or internal use when IGNY8 is connected. - ---- - -## Status Flags Available - -✅ **Task ID** - Stored and queryable -✅ **Content ID** - Stored and queryable -✅ **WordPress Status** - Stored and sent to IGNY8 -✅ **IGNY8 Status** - Mapped and sent to IGNY8 -✅ **Post Type** - Stored and sent to IGNY8 -✅ **Content Type** - Stored and sent to IGNY8 -✅ **Sync Timestamp** - Stored and sent to IGNY8 -✅ **Post URL** - Sent to IGNY8 - ---- - -**All status information is available for IGNY8 to read and query!** - diff --git a/igny8-wp-integration-plugin/docs/STYLE_GUIDE.md b/igny8-wp-integration-plugin/docs/STYLE_GUIDE.md deleted file mode 100644 index 059cd8ee..00000000 --- a/igny8-wp-integration-plugin/docs/STYLE_GUIDE.md +++ /dev/null @@ -1,186 +0,0 @@ -# Style Guide - IGNY8 WordPress Bridge - -**Last Updated**: 2025-10-17 - ---- - -## CSS Architecture - -### No Inline CSS Policy - -✅ **All styles are in `admin/assets/css/admin.css`** -❌ **No inline `style=""` attributes** -❌ **No `