diff --git a/igy8-wp-plugin/admin/assets/css/admin.css b/igy8-wp-plugin/admin/assets/css/admin.css new file mode 100644 index 00000000..3ab514a8 --- /dev/null +++ b/igy8-wp-plugin/admin/assets/css/admin.css @@ -0,0 +1,627 @@ +/** + * Admin Styles - IGNY8 Bridge + * Updated with IGNY8 brand colors and modern UI + * + * @package Igny8Bridge + */ + +/* ============================================ + IGNY8 Brand Colors + ============================================ */ +:root { + --igny8-primary: #3B82F6; + --igny8-primary-hover: #2563EB; + --igny8-success: #10B981; + --igny8-warning: #F59E0B; + --igny8-error: #EF4444; + --igny8-purple: #8B5CF6; + --igny8-gray: #6B7280; + --igny8-light-gray: #F3F4F6; +} + +/* ============================================ + Container & Layout + ============================================ */ + +.igny8-settings-container { + max-width: 1400px; +} + +.igny8-settings-card { + background: #fff; + border: 1px solid #E5E7EB; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + padding: 24px; + margin: 24px 0; + border-radius: 8px; +} + +.igny8-settings-card h2 { + margin-top: 0; + padding-bottom: 12px; + border-bottom: 2px solid var(--igny8-light-gray); + color: #111827; + font-size: 20px; + font-weight: 600; +} + +/* ============================================ + Toggle Switch + ============================================ */ + +.igny8-toggle-wrapper { + display: flex; + align-items: center; + gap: 12px; +} + +.igny8-toggle-input { + position: relative; + width: 48px; + height: 24px; + -webkit-appearance: none; + appearance: none; + background: var(--igny8-gray); + outline: none; + border-radius: 24px; + cursor: pointer; + transition: 0.3s; +} + +.igny8-toggle-input:checked { + background: var(--igny8-success); +} + +.igny8-toggle-input::before { + content: ''; + position: absolute; + width: 20px; + height: 20px; + border-radius: 50%; + top: 2px; + left: 2px; + background: #fff; + transition: 0.3s; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} + +.igny8-toggle-input:checked::before { + left: 26px; +} + +/* ============================================ + Sync Operations Grid + ============================================ */ + +.igny8-sync-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 20px; + margin-top: 20px; +} + +.igny8-sync-card { + background: #fff; + border: 2px solid #E5E7EB; + border-radius: 12px; + padding: 24px; + transition: all 0.3s ease; +} + +.igny8-sync-card:hover { + border-color: var(--igny8-primary); + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + transform: translateY(-2px); +} + +.igny8-sync-card-highlight { + border-color: var(--igny8-primary); + background: linear-gradient(135deg, #EFF6FF 0%, #DBEAFE 100%); +} + +.igny8-sync-icon { + width: 48px; + height: 48px; + background: var(--igny8-primary); + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 16px; + color: white; +} + +.igny8-sync-card h3 { + margin: 0 0 8px 0; + font-size: 16px; + font-weight: 600; + color: #111827; +} + +.igny8-sync-description { + font-size: 14px; + color: #6B7280; + line-height: 1.5; + margin-bottom: 12px; +} + +.igny8-sync-meta { + font-size: 12px; + color: #9CA3AF; + margin-bottom: 16px; +} + +.igny8-sync-time { + display: inline-flex; + align-items: center; + gap: 4px; +} + +.igny8-sync-button { + width: 100%; + height: 40px; + background: var(--igny8-primary) !important; + border-color: var(--igny8-primary) !important; + color: white !important; + font-weight: 500 !important; + border-radius: 8px !important; + transition: all 0.2s ease !important; +} + +.igny8-sync-button:hover:not(:disabled) { + background: var(--igny8-primary-hover) !important; + border-color: var(--igny8-primary-hover) !important; + transform: translateY(-1px); + box-shadow: 0 4px 6px -1px rgba(59, 130, 246, 0.3); +} + +.igny8-sync-button:disabled { + opacity: 0.5 !important; + cursor: not-allowed !important; +} + +.button-loading { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; +} + +/* ============================================ + Statistics Cards + ============================================ */ + +.igny8-stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 20px; + margin-top: 20px; +} + +.igny8-stat-card { + background: linear-gradient(135deg, #ffffff 0%, #f9fafb 100%); + border: 1px solid #E5E7EB; + border-radius: 12px; + padding: 20px; + display: flex; + align-items: flex-start; + gap: 16px; + transition: all 0.3s ease; +} + +.igny8-stat-card:hover { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + transform: translateY(-2px); +} + +.igny8-stat-icon { + width: 48px; + height: 48px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.igny8-stat-content { + flex: 1; +} + +.igny8-stat-label { + font-size: 12px; + color: #6B7280; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 4px; + font-weight: 500; +} + +.igny8-stat-value { + font-size: 28px; + font-weight: 700; + color: #111827; + line-height: 1.2; + margin-bottom: 4px; +} + +.igny8-stat-meta { + font-size: 12px; + color: #9CA3AF; +} + +/* ============================================ + Semantic Summary + ============================================ */ + +.igny8-semantic-summary { + margin-top: 24px; + padding: 20px; + background: linear-gradient(135deg, #F3E8FF 0%, #E9D5FF 100%); + border-radius: 12px; + border: 1px solid #D8B4FE; +} + +.igny8-semantic-summary h3 { + margin: 0 0 16px 0; + font-size: 16px; + font-weight: 600; + color: #6B21A8; +} + +.igny8-semantic-stats { + display: flex; + gap: 32px; + flex-wrap: wrap; +} + +.igny8-semantic-stat { + display: flex; + flex-direction: column; + gap: 4px; +} + +.igny8-semantic-stat .value { + font-size: 24px; + font-weight: 700; + color: #7C3AED; +} + +.igny8-semantic-stat .label { + font-size: 12px; + color: #8B5CF6; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +/* ============================================ + Status Indicators + ============================================ */ + +.igny8-status-connected { + color: var(--igny8-success); + font-weight: 600; +} + +.igny8-status-disconnected { + color: var(--igny8-error); + font-weight: 600; +} +/* ============================================ + API Connection Form + ============================================ */ + +.igny8-api-connection-form { + background: linear-gradient(135deg, #ffffff 0%, #f9fafb 100%); + border: 2px solid #E5E7EB; + border-radius: 12px; + padding: 32px; + margin: 0 0 24px 0; +} + +.igny8-api-form-group { + margin-bottom: 20px; +} + +.igny8-api-form-group label { + display: block; + margin-bottom: 8px; + font-weight: 600; + color: #111827; + font-size: 14px; +} + +.igny8-api-form-group input[type="text"], +.igny8-api-form-group input[type="password"] { + width: 100%; + padding: 12px 14px; + border: 1px solid #D1D5DB; + border-radius: 8px; + font-size: 14px; + transition: all 0.2s ease; + font-family: 'Courier New', monospace; + background-color: #fff; +} + +.igny8-api-form-group input[type="text"]:focus, +.igny8-api-form-group input[type="password"]:focus { + outline: none; + border-color: var(--igny8-primary); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); +} + +.igny8-api-form-group input[type="text"]:disabled { + background-color: #F3F4F6; + color: #9CA3AF; + cursor: not-allowed; +} + +.igny8-api-form-description { + font-size: 13px; + color: #6B7280; + margin-top: 6px; + line-height: 1.5; +} + +.igny8-api-form-description a { + color: var(--igny8-primary); + text-decoration: none; + font-weight: 500; +} + +.igny8-api-form-description a:hover { + text-decoration: underline; +} + +.igny8-connection-actions { + display: flex; + gap: 12px; + flex-wrap: wrap; + margin-top: 24px; +} + +.igny8-connection-actions .button { + border-radius: 8px; + padding: 10px 20px; + font-weight: 500; + border: none; + cursor: pointer; + transition: all 0.2s ease; + height: auto; +} + +.igny8-connection-actions .button-primary { + background: var(--igny8-primary) !important; + color: white !important; +} + +.igny8-connection-actions .button-primary:hover { + background: var(--igny8-primary-hover) !important; + transform: translateY(-1px); + box-shadow: 0 4px 6px -1px rgba(59, 130, 246, 0.3); +} + +.igny8-connection-actions .button-secondary { + background: #E5E7EB !important; + color: #111827 !important; +} + +.igny8-connection-actions .button-secondary:hover { + background: #D1D5DB !important; +} + +.igny8-connection-status-display { + padding: 20px; + background: linear-gradient(135deg, #F9FAFB 0%, #F3F4F6 100%); + border: 1px solid #E5E7EB; + border-radius: 12px; + margin-bottom: 20px; +} + +.igny8-connection-status-display .igny8-status-label { + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.05em; + color: #6B7280; + margin-bottom: 8px; + font-weight: 500; +} + +.igny8-connection-status-display .igny8-status-value { + font-size: 20px; + font-weight: 700; + display: flex; + align-items: center; + gap: 8px; +} + +.igny8-status-indicator { + width: 12px; + height: 12px; + border-radius: 50%; + display: inline-block; +} + +.igny8-status-indicator.connected { + background-color: var(--igny8-success); +} + +.igny8-status-indicator.disconnected { + background-color: var(--igny8-gray); +} + +.igny8-api-key-display { + display: flex; + align-items: center; + gap: 12px; + padding: 12px; + background-color: #F3F4F6; + border-radius: 8px; + word-break: break-all; +} + +.igny8-api-key-mask { + font-family: 'Courier New', monospace; + color: #6B7280; + font-size: 14px; +} + +/* ============================================ + Diagnostics + ============================================ */ + +.igny8-diagnostics-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 16px; + margin-top: 20px; +} + +.igny8-diagnostic-item { + padding: 16px; + background: linear-gradient(135deg, #F9FAFB 0%, #F3F4F6 100%); + border: 1px solid #E5E7EB; + border-radius: 8px; + transition: all 0.2s ease; +} + +.igny8-diagnostic-item:hover { + border-color: var(--igny8-primary); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); +} + +.igny8-diagnostic-label { + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.05em; + color: #6B7280; + margin-bottom: 8px; + font-weight: 500; +} + +.igny8-diagnostic-value { + font-size: 16px; + font-weight: 600; + color: #111827; +} + +.igny8-diagnostic-item .description { + margin: 6px 0 0; + color: #9CA3AF; + font-size: 12px; +} + +/* ============================================ + Sync Status Messages + ============================================ */ + +.igny8-sync-status { + margin-top: 20px; + padding: 16px; + border-radius: 8px; + display: none; + font-size: 14px; +} + +.igny8-sync-status.igny8-sync-status-success { + background-color: #D1FAE5; + border: 1px solid #6EE7B7; + color: #065F46; + display: block; +} + +.igny8-sync-status.igny8-sync-status-error { + background-color: #FEE2E2; + border: 1px solid #FCA5A5; + color: #991B1B; + display: block; +} + +.igny8-sync-status.igny8-sync-status-loading { + background-color: #DBEAFE; + border: 1px solid #93C5FD; + color: #1E40AF; + display: block; +} + +/* ============================================ + Loading States + ============================================ */ + +.igny8-loading { + opacity: 0.6; + pointer-events: none; +} + +@keyframes igny8-spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* ============================================ + Messages & Notifications + ============================================ */ + +.igny8-message { + padding: 16px; + margin: 15px 0; + border-left: 4px solid; + background: #fff; + border-radius: 4px; +} + +.igny8-message.igny8-message-success { + border-color: var(--igny8-success); + background-color: #F0FDF4; + color: #065F46; +} + +.igny8-message.igny8-message-error { + border-color: var(--igny8-error); + background-color: #FEF2F2; + color: #991B1B; +} + +.igny8-message.igny8-message-info { + border-color: var(--igny8-primary); + background-color: #EFF6FF; + color: #1E40AF; +} + +.igny8-message.igny8-message-warning { + border-color: var(--igny8-warning); + background-color: #FFFBEB; + color: #92400E; +} + +/* ============================================ + Responsive + ============================================ */ + +@media (max-width: 782px) { + .igny8-sync-grid { + grid-template-columns: 1fr; + } + + .igny8-stats-grid { + grid-template-columns: 1fr; + } + + .igny8-diagnostics-grid { + grid-template-columns: 1fr; + } +} + +@media (max-width: 600px) { + .igny8-settings-card { + padding: 16px; + } + + .igny8-sync-card { + padding: 16px; + } + + .igny8-stat-card { + flex-direction: column; + } +} diff --git a/igy8-wp-plugin/admin/assets/js/admin.js b/igy8-wp-plugin/admin/assets/js/admin.js new file mode 100644 index 00000000..fe4fe4f7 --- /dev/null +++ b/igy8-wp-plugin/admin/assets/js/admin.js @@ -0,0 +1,188 @@ +/** + * 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('✓ ' + (response.data.message || 'Connection successful') + ''); + } else { + var errorMsg = response.data.message || 'Connection failed'; + var httpStatus = response.data.http_status || ''; + var fullMsg = errorMsg; + if (httpStatus) { + fullMsg += ' (HTTP ' + httpStatus + ')'; + } + $result.html('✗ ' + fullMsg + ''); + + // Log full error to console for debugging + console.error('IGNY8 Connection Test Failed:', response.data); + } + }, + error: function(xhr, status, error) { + $result.html('✗ Request failed: ' + error + ''); + console.error('IGNY8 AJAX Error:', xhr, status, error); + }, + 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/igy8-wp-plugin/admin/assets/js/post-editor.js b/igy8-wp-plugin/admin/assets/js/post-editor.js new file mode 100644 index 00000000..2be154c1 --- /dev/null +++ b/igy8-wp-plugin/admin/assets/js/post-editor.js @@ -0,0 +1,200 @@ +/** + * 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/igy8-wp-plugin/admin/class-admin-columns.php b/igy8-wp-plugin/admin/class-admin-columns.php new file mode 100644 index 00000000..06916a8b --- /dev/null +++ b/igy8-wp-plugin/admin/class-admin-columns.php @@ -0,0 +1,306 @@ +'; + echo esc_html($taxonomy); + echo ''; + } else { + echo '—'; + } + } + + /** + * Render attribute column + * + * @param int $post_id Post ID + */ + private function render_attribute_column($post_id) { + $attribute = get_post_meta($post_id, '_igny8_attribute_id', true); + + if ($attribute) { + echo ''; + echo esc_html($attribute); + echo ''; + } else { + echo '—'; + } + } + + /** + * Add custom columns + * + * @param array $columns Existing columns + * @return array Modified columns + */ + public function add_columns($columns) { + $new_columns = array(); + + foreach ($columns as $key => $value) { + $new_columns[$key] = $value; + + if ($key === 'title') { + $new_columns['igny8_taxonomy'] = __('Taxonomy', 'igny8-bridge'); + $new_columns['igny8_attribute'] = __('Attribute', '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_taxonomy': + $this->render_taxonomy_column($post_id); + break; + + case 'igny8_attribute': + $this->render_attribute_column($post_id); + break; + } + } + + /** + * 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/igy8-wp-plugin/admin/class-admin.php b/igy8-wp-plugin/admin/class-admin.php new file mode 100644 index 00000000..f83f23b8 --- /dev/null +++ b/igy8-wp-plugin/admin/class-admin.php @@ -0,0 +1,619 @@ + '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_enabled_taxonomies', array( + 'type' => 'array', + 'sanitize_callback' => array($this, 'sanitize_taxonomies'), + 'default' => array('category', 'post_tag', 'product_cat', 'igny8_sectors', 'igny8_clusters') + )); + + 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' + ); + } + } + + // Webhook secret regeneration removed - using API key only + + // Include settings template + include IGNY8_BRIDGE_PLUGIN_DIR . 'admin/settings.php'; + } + + /** + * Handle API connection - API key only + * Calls /v1/integration/integrations/test-connection/ endpoint + */ + private function handle_connection() { + $api_key = sanitize_text_field($_POST['igny8_api_key'] ?? ''); + $site_id = sanitize_text_field($_POST['igny8_site_id'] ?? ''); + + // API key is required + if (empty($api_key)) { + add_settings_error( + 'igny8_settings', + 'igny8_error', + __('API key is required to connect to IGNY8.', 'igny8-bridge'), + 'error' + ); + return; + } + + // Site ID is required + if (empty($site_id)) { + add_settings_error( + 'igny8_settings', + 'igny8_error', + __('Site ID is required. Create a site in IGNY8 app first.', 'igny8-bridge'), + 'error' + ); + return; + } + + // Get site URL + $site_url = get_site_url(); + + // Test connection using the correct integration test endpoint + $api = new Igny8API(); + + $test_response = $api->post('/v1/integration/integrations/test-connection/', array( + 'site_id' => (int) $site_id, + 'api_key' => $api_key, + 'site_url' => $site_url + )); + + if (!$test_response['success']) { + $error_message = $test_response['error'] ?? 'Unknown error'; + + // Provide more user-friendly message for throttling errors + if (isset($test_response['http_status']) && $test_response['http_status'] === 429) { + $error_message = __('Rate limit exceeded. The plugin will automatically retry, but if this persists, please wait a moment and try again.', 'igny8-bridge'); + } + + add_settings_error( + 'igny8_settings', + 'igny8_error', + sprintf( + __('Failed to connect to IGNY8 API: %s', 'igny8-bridge'), + $error_message + ), + 'error' + ); + return; + } + + // Store API key securely + 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); + } + + // Store site ID + update_option('igny8_site_id', sanitize_text_field($site_id)); + + add_settings_error( + 'igny8_settings', + 'igny8_connected', + __('Successfully connected to IGNY8 API. Site registered.', 'igny8-bridge'), + 'updated' + ); + + // Sync site structure to backend (post types, taxonomies, etc.) + igny8_sync_site_structure_to_backend(); + } + + /** + * 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')); + } + + // Get site ID + $site_id = get_option('igny8_site_id'); + if (empty($site_id)) { + wp_send_json_error(array('message' => 'Site ID not configured. Connect to IGNY8 first.')); + } + + // Test connection using the integration test endpoint + $api_key = function_exists('igny8_get_secure_option') ? igny8_get_secure_option('igny8_api_key') : get_option('igny8_api_key'); + + $test_response = $api->post('/v1/integration/integrations/test-connection/', array( + 'site_id' => (int) $site_id, + 'api_key' => $api_key, + 'site_url' => get_site_url() + )); + + if ($test_response['success']) { + $checked_at = current_time('timestamp'); + update_option('igny8_last_api_health_check', $checked_at); + wp_send_json_success(array( + 'message' => __('Connection successful! IGNY8 API is responsive.', 'igny8-bridge'), + 'checked_at' => $checked_at + )); + return; + } + + // Connection failed + $error_message = $test_response['error'] ?? 'Unknown error'; + wp_send_json_error(array( + 'message' => __('Connection failed: ', 'igny8-bridge') . $error_message, + 'http_status' => $test_response['http_status'] ?? 0, + )); + } + + /** + * 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 taxonomies option + * + * @param mixed $value Raw value + * @return array + */ + public function sanitize_taxonomies($value) { + $supported = array_keys(igny8_get_supported_taxonomies()); + + if (!is_array($value)) { + return array('category', 'post_tag', 'product_cat', 'igny8_sectors', 'igny8_clusters'); + } + + $clean = array(); + foreach ($value as $taxonomy) { + $taxonomy = sanitize_key($taxonomy); + if (in_array($taxonomy, $supported, true)) { + $clean[] = $taxonomy; + } + } + + // Return defaults if nothing selected + return !empty($clean) ? $clean : array('category', 'post_tag'); + } + + /** + * 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/igy8-wp-plugin/admin/class-post-meta-boxes.php b/igy8-wp-plugin/admin/class-post-meta-boxes.php new file mode 100644 index 00000000..d0d6ebcb --- /dev/null +++ b/igy8-wp-plugin/admin/class-post-meta-boxes.php @@ -0,0 +1,469 @@ + 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'); + ?> ++ +
+ ++ +
+ ++ + + + + +
+ + + 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/igy8-wp-plugin/admin/settings.php b/igy8-wp-plugin/admin/settings.php new file mode 100644 index 00000000..cf791246 --- /dev/null +++ b/igy8-wp-plugin/admin/settings.php @@ -0,0 +1,771 @@ + 10)); + $two_way_sync = (int) get_option('igny8_enable_two_way_sync', 1); + +?> + +
+
+
+
+
+ : + +
+ ++ +
++ +
++ +
++ +
++ +
++ +
++ +
++ +
++ + + +
++ +
++ +
+| + |
+
+
+ + + + |
+
|---|---|
| + |
+
+
+
+ + + + |
+
+ +
++ +
+
+
+
+
+ publish, + $page_count->publish, + $product_count ? sprintf(', %d products', $product_count->publish) : '' + ); ?> +
+ + ++ +
+ + ++ +
+ + ++ +
+ + +API keys are used to authentication requests to the tailadmin API