Files
igny8/igny8-wp-plugin-for-reference-olny/core/admin/ajax.php
2025-11-09 10:27:02 +00:00

5224 lines
195 KiB
PHP

<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : ajax.php
* @location : /core/admin/ajax.php
* @type : AJAX Handler
* @scope : Global
* @allowed : AJAX endpoints, database operations, validation functions
* @reusability : Globally Reusable
* @notes : Central AJAX handler for all admin operations and data processing
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Include global helpers for table data functions
require_once plugin_dir_path(__FILE__) . 'global-helpers.php';
require_once plugin_dir_path(__FILE__) . '../../modules/components/table-tpl.php';
require_once plugin_dir_path(__FILE__) . '../../ai/modules-ai.php';
/**
* View keywords in a cluster (for modal display)
*
* @param int $cluster_id Cluster ID to view keywords for
* @return array ['success' => bool, 'keywords' => array, 'message' => string]
*/
function igny8_view_cluster_keywords($cluster_id) {
global $wpdb;
if (empty($cluster_id) || !is_numeric($cluster_id)) {
return ['success' => false, 'keywords' => [], 'message' => 'Invalid cluster ID provided'];
}
$cluster_id = intval($cluster_id);
// Verify cluster exists
$cluster = $wpdb->get_row($wpdb->prepare(
"SELECT cluster_name FROM {$wpdb->prefix}igny8_clusters WHERE id = %d",
$cluster_id
));
if (!$cluster) {
return ['success' => false, 'keywords' => [], 'message' => 'Cluster not found'];
}
// Get keywords in this cluster
$keywords = $wpdb->get_results($wpdb->prepare(
"SELECT id, keyword, search_volume, difficulty, intent, status
FROM {$wpdb->prefix}igny8_keywords
WHERE cluster_id = %d
ORDER BY keyword ASC",
$cluster_id
), ARRAY_A);
return [
'success' => true,
'keywords' => $keywords,
'cluster_name' => $cluster->cluster_name,
'message' => "Found " . count($keywords) . " keywords in cluster"
];
}
/**
* Create draft from idea
*
* @param int $idea_id Idea ID to create draft from
* @return array ['success' => bool, 'draft_id' => int, 'message' => string]
*/
function igny8_create_draft_from_idea($idea_id) {
global $wpdb;
if (empty($idea_id) || !is_numeric($idea_id)) {
return ['success' => false, 'draft_id' => 0, 'message' => 'Invalid idea ID provided'];
}
$idea_id = intval($idea_id);
// Get idea details
$idea = $wpdb->get_row($wpdb->prepare(
"SELECT idea_title, idea_description, content_structure, content_type, keyword_cluster_id, estimated_word_count
FROM {$wpdb->prefix}igny8_content_ideas
WHERE id = %d",
$idea_id
));
if (!$idea) {
return ['success' => false, 'draft_id' => 0, 'message' => 'Idea not found'];
}
// Create draft record in wp_igny8_tasks (which serves as drafts)
$result = $wpdb->insert(
$wpdb->prefix . 'igny8_tasks',
[
'title' => $idea->idea_title,
'description' => $idea->idea_description,
'status' => 'draft',
'content_structure' => $idea->content_structure,
'content_type' => $idea->content_type,
'cluster_id' => $idea->keyword_cluster_id,
'keywords' => json_encode([]), // Will be populated from cluster if needed
'schedule_at' => null,
'assigned_post_id' => null,
'created_at' => current_time('mysql'),
'updated_at' => current_time('mysql')
],
['%s', '%s', '%s', '%s', '%s', '%d', '%s', '%s', '%d', '%s', '%s']
);
if ($result === false) {
return ['success' => false, 'draft_id' => 0, 'message' => 'Failed to create draft from idea'];
}
$draft_id = $wpdb->insert_id;
return [
'success' => true,
'draft_id' => $draft_id,
'message' => "Successfully created draft from idea: {$idea->idea_title}"
];
}
/**
* =============================================
* UNIFIED DATA VALIDATION LAYER
* =============================================
*/
/**
* Unified record validation function for all Planner module tables
*
* @param string $table_id Table ID (e.g., 'planner_keywords', 'planner_clusters')
* @param array $data Array of field data to validate
* @return array ['valid' => bool, 'error' => string|null]
*/
function igny8_validate_record($table_id, $data) {
global $wpdb;
// Define validation rules for each table
$validation_rules = igny8_get_validation_rules($table_id);
if (!$validation_rules) {
return ['valid' => false, 'error' => 'Invalid table ID provided'];
}
// Validate each field
foreach ($validation_rules as $field => $rules) {
$value = $data[$field] ?? '';
// Skip validation if field is not provided and not required
if (empty($value) && !$rules['required']) {
continue;
}
// Required field validation
if ($rules['required'] && empty($value)) {
return ['valid' => false, 'error' => ucfirst($field) . ' is required'];
}
// Skip further validation if field is empty and not required
if (empty($value)) {
continue;
}
// Type-specific validations
if (isset($rules['type'])) {
$validation_result = igny8_validate_field_by_type($field, $value, $rules);
if (!$validation_result['valid']) {
return $validation_result;
}
}
// Enum validation
if (isset($rules['enum'])) {
if (!in_array($value, $rules['enum'])) {
return ['valid' => false, 'error' => ucfirst($field) . ' must be one of: ' . implode(', ', $rules['enum'])];
}
}
// Range validation
if (isset($rules['min']) && is_numeric($value)) {
if (floatval($value) < $rules['min']) {
return ['valid' => false, 'error' => ucfirst($field) . ' must be at least ' . $rules['min']];
}
}
if (isset($rules['max']) && is_numeric($value)) {
if (floatval($value) > $rules['max']) {
return ['valid' => false, 'error' => ucfirst($field) . ' must be at most ' . $rules['max']];
}
}
// Length validation
if (isset($rules['max_length'])) {
if (strlen($value) > $rules['max_length']) {
return ['valid' => false, 'error' => ucfirst($field) . ' cannot exceed ' . $rules['max_length'] . ' characters'];
}
}
// Foreign key validation
if (isset($rules['foreign_key'])) {
$fk_result = igny8_validate_foreign_key($rules['foreign_key'], $value);
if (!$fk_result['valid']) {
return $fk_result;
}
}
}
return ['valid' => true];
}
/**
* Get validation rules for a specific table
*
* @param string $table_id Table ID
* @return array|null Validation rules array or null if not found
*/
function igny8_get_validation_rules($table_id) {
$rules = [
'planner_keywords' => [
'keyword' => [
'required' => true,
'type' => 'text',
'max_length' => 255,
'no_html' => true
],
'search_volume' => [
'required' => false,
'type' => 'numeric',
'min' => 0
],
'difficulty' => [
'required' => false,
'type' => 'numeric_or_text',
'min' => 0,
'max' => 100,
'text_options' => ['Very Easy', 'Easy', 'Medium', 'Hard', 'Very Hard']
],
'cpc' => [
'required' => false,
'type' => 'decimal',
'min' => 0
],
'intent' => [
'required' => false,
'enum' => ['informational', 'navigational', 'transactional', 'commercial']
],
'status' => [
'required' => true,
'enum' => ['unmapped', 'mapped', 'queued', 'published']
],
'cluster_id' => [
'required' => false,
'type' => 'integer',
'foreign_key' => [
'table' => 'igny8_clusters',
'column' => 'id'
]
]
],
'planner_clusters' => [
'cluster_name' => [
'required' => true,
'type' => 'text',
'max_length' => 255,
'no_html' => true
],
'sector_id' => [
'required' => false,
'type' => 'integer'
],
'status' => [
'required' => true,
'enum' => ['active', 'inactive', 'archived']
]
],
'planner_ideas' => [
'idea_title' => [
'required' => true,
'type' => 'text',
'max_length' => 255,
'no_html' => true
],
'idea_description' => [
'required' => false,
'type' => 'text',
'no_html' => true
],
'content_structure' => [
'required' => true,
'enum' => ['cluster_hub', 'landing_page', 'guide_tutorial', 'how_to', 'comparison', 'review', 'top_listicle', 'question', 'product_description', 'service_page', 'home_page']
],
'content_type' => [
'required' => true,
'enum' => ['post', 'product', 'page', 'CPT']
],
'keyword_cluster_id' => [
'required' => false,
'type' => 'integer',
'foreign_key' => [
'table' => 'igny8_clusters',
'column' => 'id'
]
],
'status' => [
'required' => true,
'enum' => ['new', 'scheduled', 'published']
],
'estimated_word_count' => [
'required' => false,
'type' => 'integer',
'min' => 0
],
'source' => [
'required' => true,
'enum' => ['AI', 'Manual']
],
'target_keywords' => [
'required' => false,
'type' => 'text',
'no_html' => false
],
'tasks_count' => [
'required' => false,
'type' => 'integer',
'min' => 0
]
],
'writer_tasks' => [
'title' => [
'required' => true,
'type' => 'text',
'max_length' => 255,
'no_html' => true
],
'description' => [
'required' => false,
'type' => 'text',
'no_html' => true
],
'status' => [
'required' => true,
'enum' => ['pending', 'in_progress', 'completed', 'cancelled', 'draft', 'queued', 'review', 'published']
],
'priority' => [
'required' => true,
'enum' => ['high', 'medium', 'low']
],
'content_structure' => [
'required' => false,
'enum' => ['cluster_hub', 'landing_page', 'guide_tutorial', 'how_to', 'comparison', 'review', 'top_listicle', 'question', 'product_description', 'service_page', 'home_page']
],
'content_type' => [
'required' => false,
'enum' => ['post', 'product', 'page', 'CPT']
],
'cluster_id' => [
'required' => false,
'type' => 'integer',
'foreign_key' => [
'table' => 'igny8_clusters',
'column' => 'id'
]
],
'idea_id' => [
'required' => false,
'type' => 'integer',
'foreign_key' => [
'table' => 'igny8_content_ideas',
'column' => 'id'
]
],
'keywords' => [
'required' => false,
'type' => 'text'
],
'word_count' => [
'required' => false,
'type' => 'integer',
'min' => 0
],
'due_date' => [
'required' => false,
'type' => 'datetime'
],
'schedule_at' => [
'required' => false,
'type' => 'datetime'
],
'assigned_post_id' => [
'required' => false,
'type' => 'integer',
'foreign_key' => [
'table' => 'wp_posts',
'column' => 'ID'
]
],
'ai_writer' => [
'required' => false,
'enum' => ['ai', 'human']
],
]
];
return $rules[$table_id] ?? null;
}
/**
* Validate field by type
*
* @param string $field Field name
* @param mixed $value Field value
* @param array $rules Validation rules
* @return array ['valid' => bool, 'error' => string|null]
*/
function igny8_validate_field_by_type($field, $value, $rules) {
switch ($rules['type']) {
case 'text':
// Special handling for target_keywords field
if ($field === 'target_keywords') {
// Allow any format for target_keywords - arrays, strings, etc.
if (is_array($value)) {
$value = implode(', ', array_filter($value));
} elseif (!is_string($value)) {
$value = (string) $value;
}
// No HTML validation for target_keywords
return ['valid' => true];
}
// Convert to string if not already
if (!is_string($value)) {
$value = (string) $value;
}
// Check for HTML content if not allowed
if (isset($rules['no_html']) && $rules['no_html'] && strip_tags($value) !== $value) {
return ['valid' => false, 'error' => ucfirst($field) . ' cannot contain HTML'];
}
break;
case 'numeric':
case 'integer':
if (!is_numeric($value)) {
return ['valid' => false, 'error' => ucfirst($field) . ' must be a number'];
}
if ($rules['type'] === 'integer' && intval($value) != $value) {
return ['valid' => false, 'error' => ucfirst($field) . ' must be a whole number'];
}
break;
case 'decimal':
if (!is_numeric($value)) {
return ['valid' => false, 'error' => ucfirst($field) . ' must be a decimal number'];
}
break;
case 'numeric_or_text':
// Handle difficulty field that can be text or numeric
if (isset($rules['text_options']) && in_array($value, $rules['text_options'])) {
// Valid text option, convert to numeric for range validation
$difficulty_map = [
'Very Easy' => 10,
'Easy' => 30,
'Medium' => 50,
'Hard' => 70,
'Very Hard' => 90
];
$value = $difficulty_map[$value] ?? 0;
} elseif (!is_numeric($value)) {
return ['valid' => false, 'error' => ucfirst($field) . ' must be a number or valid difficulty level'];
}
break;
default:
// Unknown type, skip validation
break;
}
return ['valid' => true];
}
/**
* Validate foreign key reference
*
* @param array $fk_config Foreign key configuration
* @param mixed $value Value to validate
* @return array ['valid' => bool, 'error' => string|null]
*/
function igny8_validate_foreign_key($fk_config, $value) {
global $wpdb;
if (empty($value) || !is_numeric($value)) {
return ['valid' => false, 'error' => 'Invalid reference ID'];
}
$table = $fk_config['table'];
$column = $fk_config['column'];
// Handle WordPress posts table
if ($table === 'posts') {
$table = $wpdb->posts;
} else {
$table = $wpdb->prefix . $table;
}
$exists = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM `{$table}` WHERE `{$column}` = %d",
intval($value)
));
if (!$exists) {
return ['valid' => false, 'error' => 'Referenced record does not exist'];
}
return ['valid' => true];
}
require_once plugin_dir_path(__FILE__) . '../../ai/prompts-library.php';
// Include monitor helpers for diagnostic tracking
require_once plugin_dir_path(__FILE__) . '../../debug/monitor-helpers.php';
// Unified AJAX handler for all table data loading
add_action('wp_ajax_igny8_get_table_data', 'igny8_get_table_data');
// AJAX handler for setting global flags (for debugging)
add_action('wp_ajax_igny8_set_global_flag', 'igny8_set_global_flag');
// AJAX handler for refreshing debug panel
// AJAX handler for toggling debug monitoring
add_action('wp_ajax_igny8_toggle_debug_monitoring', 'igny8_toggle_debug_monitoring');
// AJAX handlers for save and delete operations
add_action('wp_ajax_igny8_save_table_record', 'igny8_save_table_record');
add_action('wp_ajax_igny8_delete_table_records', 'igny8_delete_table_records');
// AJAX handlers for inline forms
add_action('wp_ajax_igny8_save_form_record', 'igny8_save_form_record');
add_action('wp_ajax_igny8_delete_single_record', 'igny8_delete_single_record');
add_action('wp_ajax_igny8_delete_bulk_records', 'igny8_delete_bulk_records');
// MOVED TO: flows/sync-ajax.php
// AJAX handler for keyword imports with workflow automation
// add_action('wp_ajax_igny8_import_keywords', 'igny8_ajax_import_keywords');
// MOVED TO: flows/sync-ajax.php
// AJAX handlers for Planner → Writer Bridge
// add_action('wp_ajax_igny8_create_task_from_idea', 'igny8_create_task_from_idea_ajax');
// add_action('wp_ajax_igny8_bulk_create_tasks_from_ideas', 'igny8_bulk_create_tasks_from_ideas_ajax');
// AJAX handlers for Personalization Module
// igny8_get_fields actions moved to ai/openai-api.php
// Personalization AJAX actions moved to ai/openai-api.php
add_action('wp_ajax_igny8_test_ajax', 'igny8_test_ajax_callback');
add_action('wp_ajax_nopriv_igny8_test_ajax', 'igny8_test_ajax_callback');
// Simple test handler for debug
function igny8_test_ajax_callback() {
wp_send_json_success('AJAX is working!');
}
// AJAX handler for saving text input (debug)
add_action('wp_ajax_igny8_save_ajax_text', 'igny8_ajax_save_ajax_text');
function igny8_ajax_save_ajax_text() {
try {
// Check if all required data is present
if (!isset($_POST['ajax_text_input'])) {
wp_send_json_error('Missing text input');
return;
}
if (!isset($_POST['ajax_text_nonce'])) {
wp_send_json_error('Missing security token');
return;
}
// Verify nonce
if (!wp_verify_nonce($_POST['ajax_text_nonce'], 'ajax_text_action')) {
wp_send_json_error('Security check failed');
return;
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
return;
}
// Get and sanitize form data
$text_input = sanitize_text_field($_POST['ajax_text_input']);
// Save text input to WordPress options
$result = update_option('igny8_ajax_test_text', $text_input);
if ($result === false) {
wp_send_json_error('Failed to save to database');
return;
}
wp_send_json_success('Text saved successfully: ' . $text_input);
} catch (Exception $e) {
wp_send_json_error('Server error: ' . $e->getMessage());
}
}
// AJAX handler for testing Runware API connection
add_action('wp_ajax_igny8_test_runware_connection', 'igny8_ajax_test_runware_connection');
// Legacy AJAX handlers (redirected to unified handler)
add_action('wp_ajax_igny8_planner_keywords_ajax', 'igny8_ajax_load_table_data');
add_action('wp_ajax_igny8_planner_clusters_ajax', 'igny8_ajax_load_table_data');
add_action('wp_ajax_igny8_planner_ideas_ajax', 'igny8_ajax_load_table_data');
add_action('wp_ajax_igny8_writer_tasks_ajax', 'igny8_ajax_load_table_data');
add_action('wp_ajax_igny8_writer_drafts_ajax', 'igny8_ajax_load_table_data');
add_action('wp_ajax_igny8_writer_published_ajax', 'igny8_ajax_load_table_data');
/**
* AJAX handler for bulk publishing drafts
*/
add_action('wp_ajax_igny8_bulk_publish_drafts', 'igny8_ajax_bulk_publish_drafts');
/**
* Save New Content Decision Setting
*/
add_action('wp_ajax_igny8_save_new_content_decision', 'igny8_ajax_save_new_content_decision');
function igny8_ajax_save_new_content_decision() {
try {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_writer_settings')) {
wp_send_json_error(['message' => 'Security check failed']);
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$new_content_action = sanitize_text_field($_POST['new_content_action'] ?? 'draft');
// Validate the value
if (!in_array($new_content_action, ['draft', 'publish'])) {
wp_send_json_error(['message' => 'Invalid content action']);
}
// Save the setting
update_option('igny8_new_content_action', $new_content_action);
// Debug logging
error_log('Igny8 DEBUG: Saving new content action: ' . $new_content_action);
$saved_value = get_option('igny8_new_content_action', 'not_saved');
error_log('Igny8 DEBUG: Verified saved value: ' . $saved_value);
error_log('Igny8 DEBUG: All WordPress options with igny8: ' . print_r(get_option('igny8_new_content_action'), true));
wp_send_json_success([
'message' => 'New content decision saved successfully',
'action' => $new_content_action,
'debug_saved' => $saved_value
]);
} catch (Exception $e) {
wp_send_json_error(['message' => 'Error: ' . $e->getMessage()]);
}
}
function igny8_ajax_bulk_publish_drafts() {
try {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_writer_settings')) {
wp_send_json_error(['message' => 'Security check failed']);
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$task_ids = $_POST['task_ids'] ?? [];
if (empty($task_ids) || !is_array($task_ids)) {
wp_send_json_error(['message' => 'No tasks selected']);
}
global $wpdb;
$published = 0;
$failed = 0;
foreach ($task_ids as $task_id) {
$task_id = intval($task_id);
// Get task details
$task = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}igny8_tasks WHERE id = %d",
$task_id
));
if (!$task || !$task->assigned_post_id) {
$failed++;
continue;
}
// Update WordPress post status to publish
$result = wp_update_post([
'ID' => $task->assigned_post_id,
'post_status' => 'publish'
]);
if (!is_wp_error($result) && $result) {
// Update task status to completed
$wpdb->update(
$wpdb->prefix . 'igny8_tasks',
['status' => 'completed'],
['id' => $task_id],
['%s'],
['%d']
);
// Trigger keyword status update
do_action('igny8_post_published', $task->assigned_post_id);
$published++;
} else {
$failed++;
}
}
wp_send_json_success([
'message' => "Published {$published} drafts, {$failed} failed",
'published' => $published,
'failed' => $failed
]);
} catch (Exception $e) {
error_log('Igny8 Bulk Publish Error: ' . $e->getMessage());
wp_send_json_error(['message' => 'Error publishing drafts: ' . $e->getMessage()]);
}
}
add_action('wp_ajax_igny8_writer_templates_ajax', 'igny8_ajax_load_table_data');
add_action('wp_ajax_igny8_render_form_row', 'igny8_render_form_row');
add_action('wp_ajax_igny8_get_row_data', 'igny8_get_row_data');
add_action('wp_ajax_igny8_test_ajax', 'igny8_test_ajax');
/**
* Secure AJAX endpoint for table data loading
* Phase-1: Security Backbone & Table Skeletons
*/
function igny8_get_table_data() {
// Note: TABLE_AJAX_REQUEST_SENT is set by JavaScript when request is initiated
// Verify nonce using check_ajax_referer
if (!check_ajax_referer('igny8_ajax_nonce', 'nonce', true)) {
if (function_exists('igny8_debug_state')) {
igny8_debug_state('AJAX_NONCE_VALIDATED', false, 'Nonce verification failed');
}
wp_send_json_error('Security check failed');
}
// Debug state: Nonce validated
if (function_exists('igny8_debug_state')) {
igny8_debug_state('AJAX_NONCE_VALIDATED', true, 'Nonce verification passed');
}
// Check user capabilities
if (!current_user_can('manage_options')) {
if (function_exists('igny8_debug_state')) {
igny8_debug_state('USER_CAPABILITY_OK', false, 'User lacks manage_options capability');
}
wp_send_json_error('Insufficient permissions');
}
// Debug state: User capability OK
if (function_exists('igny8_debug_state')) {
igny8_debug_state('USER_CAPABILITY_OK', true, 'User has required capabilities');
}
// Get and sanitize parameters
$tableId = sanitize_text_field($_POST['table'] ?? '');
$filters_raw = $_POST['filters'] ?? [];
$page = intval($_POST['page'] ?? 1);
$per_page = intval($_POST['per_page'] ?? get_option('igny8_records_per_page', 20));
// Decode filters if it's a JSON string
if (is_string($filters_raw)) {
// First try to decode as-is
$filters = json_decode($filters_raw, true);
if (json_last_error() !== JSON_ERROR_NONE) {
error_log("DEBUG: JSON decode error: " . json_last_error_msg());
error_log("DEBUG: Raw string was: " . $filters_raw);
// Try to fix escaped quotes and decode again
$fixed_string = stripslashes($filters_raw);
error_log("DEBUG: Fixed string: " . $fixed_string);
$filters = json_decode($fixed_string, true);
if (json_last_error() !== JSON_ERROR_NONE) {
error_log("DEBUG: JSON decode error after fix: " . json_last_error_msg());
$filters = [];
}
}
if ($filters === null) {
$filters = [];
}
} else {
$filters = $filters_raw;
}
// Validate required parameters
if (empty($tableId)) {
wp_send_json_error('Required parameter missing: table');
}
// Extract submodule name from tableId for debug tracking
// tableId format: module_submodule (e.g., planner_keywords, writer_templates)
$submodule_name = '';
if (strpos($tableId, '_') !== false) {
$parts = explode('_', $tableId, 2);
$submodule_name = $parts[1] ?? '';
}
// Store the actual submodule name for debug detection
$GLOBALS['igny8_ajax_submodule_name'] = $submodule_name;
// Mark that AJAX request was actually sent
$GLOBALS['igny8_ajax_request_sent'] = true;
// Debug state: AJAX response OK
// Get table data using the data fetching function
$table_data = igny8_fetch_table_data($tableId, $filters, $page, $per_page);
// Store AJAX response for event detection
$GLOBALS['igny8_last_ajax_response'] = [
'data' => $table_data,
'table_id' => $tableId,
'timestamp' => time()
];
if (function_exists('igny8_debug_state')) {
igny8_debug_state('TABLE_AJAX_RESPONSE_OK', true, 'AJAX response prepared successfully');
}
// Add debug query to table data
$table_data['debug_query'] = $GLOBALS['igny8_debug_query'] ?? 'No query executed';
// Return actual table data
wp_send_json_success($table_data);
}
/**
* Legacy AJAX handler for loading table data (redirected to new endpoint)
*/
function igny8_ajax_load_table_data() {
// Debug state: AJAX request sent
if (function_exists('igny8_debug_state')) {
igny8_debug_state('TABLE_AJAX_REQUEST_SENT', true, 'AJAX request initiated');
}
// Verify nonce
if (!wp_verify_nonce($_POST['security'], 'igny8_ajax_nonce')) {
if (function_exists('igny8_debug_state')) {
igny8_debug_state('AJAX_NONCE_VALIDATED', false, 'Nonce verification failed');
}
wp_send_json_error('Security check failed');
return;
}
// Debug state: Nonce validated
if (function_exists('igny8_debug_state')) {
igny8_debug_state('AJAX_NONCE_VALIDATED', true, 'Nonce verification passed');
}
// Check user capabilities
if (!current_user_can('manage_options')) {
if (function_exists('igny8_debug_state')) {
igny8_debug_state('USER_CAPABILITY_OK', false, 'User lacks manage_options capability');
}
wp_send_json_error('Insufficient permissions');
return;
}
// Debug state: User capability OK
if (function_exists('igny8_debug_state')) {
igny8_debug_state('USER_CAPABILITY_OK', true, 'User has required capabilities');
}
// Get parameters
$module = sanitize_text_field($_POST['module'] ?? '');
$tab = sanitize_text_field($_POST['tab'] ?? '');
$page = intval($_POST['page'] ?? 1);
$per_page = intval($_POST['per_page'] ?? 20);
// Prepare response
$response = [
'success' => true,
'data' => [
'items' => [],
'total' => 0,
'page' => $page,
'per_page' => $per_page,
'total_pages' => 0
]
];
// Debug state: AJAX response OK
if (function_exists('igny8_debug_state')) {
igny8_debug_state('TABLE_AJAX_RESPONSE_OK', true, 'AJAX response prepared successfully');
}
// Return JSON response
wp_send_json_success($response);
}
/**
* Save table record (insert/update)
*/
function igny8_save_table_record() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce')) {
wp_send_json_error('Security check failed');
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
global $wpdb;
$table_id = sanitize_text_field($_POST['table_id'] ?? '');
$record_data = $_POST['record_data'] ?? [];
$record_id = intval($_POST['record_id'] ?? 0);
if (empty($table_id)) {
wp_send_json_error('Table ID is required');
}
// Get actual table name
try {
$table_name = igny8_get_table_name($table_id);
} catch (InvalidArgumentException $e) {
wp_send_json_error('Invalid table ID: ' . $e->getMessage());
}
// Sanitize record data
$sanitized_data = [];
foreach ($record_data as $key => $value) {
$sanitized_data[sanitize_key($key)] = sanitize_text_field($value);
}
if (empty($sanitized_data)) {
wp_send_json_error('No data provided');
}
// Validate record data before database operations
$validation = igny8_validate_record($table_id, $sanitized_data);
if (!$validation['valid']) {
wp_send_json_error(['message' => $validation['error']]);
}
// Add timestamps
$sanitized_data['updated_at'] = current_time('mysql');
try {
if ($record_id > 0) {
// Update existing record
$result = $wpdb->update(
$table_name,
$sanitized_data,
['id' => $record_id],
array_fill(0, count($sanitized_data), '%s'),
['%d']
);
if ($result === false) {
wp_send_json_error('Failed to update record');
}
// Trigger taxonomy term update after cluster is updated
if ($table_id === 'planner_clusters' && $action_type === 'edit' && $record_id > 0) {
do_action('igny8_cluster_updated', $record_id);
error_log("Igny8: Triggered igny8_cluster_updated for cluster ID $record_id");
}
$message = 'Record updated successfully';
} else {
// Insert new record
$sanitized_data['created_at'] = current_time('mysql');
$result = $wpdb->insert(
$table_name,
$sanitized_data,
array_fill(0, count($sanitized_data), '%s')
);
if ($result === false) {
wp_send_json_error('Failed to insert record');
}
$record_id = $wpdb->insert_id;
// Trigger taxonomy term creation after new cluster is saved
if ($table_id === 'planner_clusters' && $action_type === 'add' && $record_id > 0) {
do_action('igny8_cluster_added', $record_id);
error_log("Igny8: Triggered igny8_cluster_added for cluster ID $record_id");
}
$message = 'Record created successfully';
}
// Trigger workflow automation after successful save
$workflow_result = null;
if ($record_id > 0) {
switch ($table_id) {
case 'planner_clusters':
// Trigger auto-idea generation when cluster is created
if ($record_id > 0 && isset($sanitized_data['created_at'])) { // New record created
$workflow_result = igny8_workflow_triggers('cluster_created', ['cluster_id' => $record_id]);
}
break;
}
}
// Prepare response with workflow result
$response = [
'message' => $message,
'record_id' => $record_id
];
if ($workflow_result && is_array($workflow_result) && isset($workflow_result['success']) && $workflow_result['success']) {
$response['workflow_message'] = $workflow_result['message'];
if (isset($workflow_result['clusters_created'])) {
$response['workflow_data'] = [
'clusters_created' => $workflow_result['clusters_created'],
'cluster_ids' => $workflow_result['cluster_ids'] ?? []
];
}
if (isset($workflow_result['ideas_created'])) {
$response['workflow_data'] = [
'ideas_created' => $workflow_result['ideas_created']
];
}
if (isset($workflow_result['suggestions'])) {
$response['workflow_data'] = [
'suggestions' => $workflow_result['suggestions']
];
}
}
wp_send_json_success($response);
} catch (Exception $e) {
wp_send_json_error('Database error: ' . $e->getMessage());
}
}
/**
* Delete table records
*/
function igny8_delete_table_records() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce')) {
wp_send_json_error('Security check failed');
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
global $wpdb;
$table_id = sanitize_text_field($_POST['table_id'] ?? '');
$record_ids = $_POST['record_ids'] ?? [];
if (empty($table_id)) {
wp_send_json_error('Table ID is required');
}
if (empty($record_ids) || !is_array($record_ids)) {
wp_send_json_error('No records selected for deletion');
}
// Get actual table name
$table_name = igny8_get_table_name($table_id);
if (!$table_name) {
wp_send_json_error('Invalid table ID');
}
// Sanitize record IDs
$sanitized_ids = array_map('intval', $record_ids);
$sanitized_ids = array_filter($sanitized_ids, function($id) {
return $id > 0;
});
if (empty($sanitized_ids)) {
wp_send_json_error('No valid record IDs provided');
}
try {
// Build placeholders for IN clause
$placeholders = implode(',', array_fill(0, count($sanitized_ids), '%d'));
$result = $wpdb->query($wpdb->prepare(
"DELETE FROM `{$table_name}` WHERE id IN ({$placeholders})",
$sanitized_ids
));
if ($result === false) {
wp_send_json_error('Failed to delete records');
}
wp_send_json_success([
'message' => "Successfully deleted {$result} record(s)",
'deleted_count' => $result
]);
} catch (Exception $e) {
wp_send_json_error('Database error: ' . $e->getMessage());
}
}
/**
* AJAX handler for saving form records (add/edit)
*/
function igny8_save_form_record() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce')) {
wp_send_json_error('Security check failed');
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
// Get parameters
$table_id = sanitize_text_field($_POST['table_id'] ?? '');
$action_type = sanitize_text_field($_POST['action_type'] ?? 'add');
$record_id = intval($_POST['record_id'] ?? 0);
if (empty($table_id)) {
wp_send_json_error('Table ID required');
}
// Get table name
try {
$table_name = igny8_get_table_name($table_id);
} catch (InvalidArgumentException $e) {
wp_send_json_error('Invalid table ID: ' . $e->getMessage());
}
// Get form configuration
require_once plugin_dir_path(__FILE__) . '../../modules/config/forms-config.php';
$config = igny8_get_form_config($table_id);
if (!$config) {
wp_send_json_error('Form configuration not found');
}
// Collect form data
$form_data = [];
foreach ($config['fields'] as $field) {
$field_name = $field['name'];
$field_type = $field['type'];
if ($field_type === 'multiselect') {
$values = $_POST[$field_name] ?? [];
$form_data[$field_name] = is_array($values) ? implode(',', $values) : '';
} else {
$form_data[$field_name] = sanitize_text_field($_POST[$field_name] ?? '');
}
}
// Handle special field processing
if (isset($form_data['difficulty']) && !is_numeric($form_data['difficulty'])) {
// Convert difficulty text to numeric
$difficulty_map = [
'Very Easy' => 10,
'Easy' => 30,
'Medium' => 50,
'Hard' => 70,
'Very Hard' => 90
];
$form_data['difficulty'] = $difficulty_map[$form_data['difficulty']] ?? 0;
}
// Handle target_keywords field - store as comma-separated text
if (isset($form_data['target_keywords']) && !empty($form_data['target_keywords'])) {
if (is_array($form_data['target_keywords'])) {
$keywords = array_map('trim', $form_data['target_keywords']);
$keywords = array_filter($keywords); // Remove empty values
$form_data['target_keywords'] = implode(', ', $keywords);
} else {
$keywords = array_map('trim', explode(',', $form_data['target_keywords']));
$keywords = array_filter($keywords); // Remove empty values
$form_data['target_keywords'] = implode(', ', $keywords);
}
} elseif (isset($form_data['target_keywords'])) {
$form_data['target_keywords'] = null; // Set to null if empty
}
// Validate form data before database operations
$validation = igny8_validate_record($table_id, $form_data);
if (!$validation['valid']) {
wp_send_json_error(['message' => $validation['error']]);
}
global $wpdb;
if ($action_type === 'add') {
// Insert new record
$result = $wpdb->insert($table_name, $form_data);
if ($result === false) {
wp_send_json_error('Failed to add record');
}
$new_id = $wpdb->insert_id;
// Update cluster metrics if this is a keywords record
if ($table_id === 'planner_keywords' && isset($form_data['cluster_id'])) {
do_action('igny8_keyword_added', $new_id, $form_data['cluster_id']);
}
// Trigger cluster-added automation for new clusters (same as test page)
if ($table_id === 'planner_clusters') {
do_action('igny8_cluster_added', $new_id);
}
wp_send_json_success([
'message' => 'Record added successfully',
'id' => $new_id
]);
} elseif ($action_type === 'edit') {
if (!$record_id) {
wp_send_json_error('Record ID required for edit');
}
// Update existing record
$result = $wpdb->update($table_name, $form_data, ['id' => $record_id]);
if ($result === false) {
wp_send_json_error('Failed to update record');
}
// Update cluster metrics if this is a keywords record
if ($table_id === 'planner_keywords' && isset($form_data['cluster_id'])) {
do_action('igny8_keyword_updated', $record_id, $form_data['cluster_id']);
}
// Trigger cluster taxonomy term update for cluster updates
if ($table_id === 'planner_clusters') {
do_action('igny8_cluster_updated', $record_id);
}
wp_send_json_success([
'message' => 'Record updated successfully',
'id' => $record_id
]);
} else {
wp_send_json_error('Invalid action type');
}
}
/**
* AJAX handler for deleting single record
*/
function igny8_delete_single_record() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce')) {
wp_send_json_error('Security check failed');
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
// Get parameters
$table_id = sanitize_text_field($_POST['table_id'] ?? '');
$record_id = intval($_POST['record_id'] ?? 0);
if (empty($table_id) || !$record_id) {
wp_send_json_error('Table ID and Record ID required');
}
// Get table name
$table_name = igny8_get_table_name($table_id);
if (!$table_name) {
wp_send_json_error('Invalid table ID');
}
global $wpdb;
// Handle cluster deletion - clean up keyword relationships
if ($table_id === 'planner_clusters') {
// Before deleting cluster, unmap all keywords from this cluster
$unmapped_count = $wpdb->query($wpdb->prepare(
"UPDATE {$wpdb->prefix}igny8_keywords
SET cluster_id = NULL, status = 'unmapped', updated_at = CURRENT_TIMESTAMP
WHERE cluster_id = %d",
$record_id
));
if ($unmapped_count !== false) {
// Log the unmapping
error_log("Igny8: Unmapped {$unmapped_count} keywords from deleted cluster ID {$record_id}");
}
}
// Get cluster_id before deleting for metrics update
$cluster_id = null;
if ($table_id === 'planner_keywords') {
$cluster_id = $wpdb->get_var($wpdb->prepare(
"SELECT cluster_id FROM {$table_name} WHERE id = %d",
$record_id
));
}
// Delete the record
$result = $wpdb->delete($table_name, ['id' => $record_id]);
if ($result === false) {
wp_send_json_error('Failed to delete record');
}
// Update cluster metrics if this was a keywords or mapping record
if ($cluster_id) {
if ($table_id === 'planner_keywords') {
do_action('igny8_keyword_deleted', $record_id, $cluster_id);
}
}
wp_send_json_success([
'message' => 'Record deleted successfully',
'id' => $record_id
]);
}
/**
* MOVED TO: flows/sync-ajax.php
* AJAX handler for bulk deleting records
*/
/*
function igny8_delete_bulk_records() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce')) {
wp_send_json_error('Security check failed');
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
// Get parameters
$table_id = sanitize_text_field($_POST['table_id'] ?? '');
$record_ids = $_POST['record_ids'] ?? [];
if (empty($table_id) || empty($record_ids) || !is_array($record_ids)) {
wp_send_json_error('Table ID and Record IDs required');
}
// Get table name
$table_name = igny8_get_table_name($table_id);
if (!$table_name) {
wp_send_json_error('Invalid table ID');
}
// Sanitize IDs
$record_ids = array_map('intval', $record_ids);
$record_ids = array_filter($record_ids, function($id) { return $id > 0; });
if (empty($record_ids)) {
wp_send_json_error('No valid record IDs provided');
}
global $wpdb;
// Build placeholders for IN clause
$placeholders = implode(',', array_fill(0, count($record_ids), '%d'));
// Delete the records
$result = $wpdb->query($wpdb->prepare(
"DELETE FROM `{$table_name}` WHERE id IN ({$placeholders})",
$record_ids
));
if ($result === false) {
wp_send_json_error('Failed to delete records');
}
wp_send_json_success([
'message' => $result . ' records deleted successfully',
'deleted_count' => $result
]);
}
*/
/**
* AJAX handler for rendering form rows
*/
function igny8_render_form_row() {
// Error reporting disabled for clean JSON responses
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce')) {
wp_send_json_error('Security check failed');
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
// Get parameters
$table_id = sanitize_text_field($_POST['table_id'] ?? '');
$mode = sanitize_text_field($_POST['mode'] ?? 'add');
$record_data_json = wp_unslash($_POST['record_data'] ?? '{}');
if (empty($table_id)) {
wp_send_json_error('Table ID required');
}
// Parse record data
$record_data = json_decode($record_data_json, true) ?? [];
// Include forms config first
$forms_config_path = plugin_dir_path(__FILE__) . '../../modules/config/forms-config.php';
if (!file_exists($forms_config_path)) {
wp_send_json_error('Forms config not found: ' . $forms_config_path);
}
require_once $forms_config_path;
// Include forms template
$forms_template_path = plugin_dir_path(__FILE__) . '../../modules/components/forms-tpl.php';
if (!file_exists($forms_template_path)) {
wp_send_json_error('Forms template not found: ' . $forms_template_path);
}
require_once $forms_template_path;
// Render form row
try {
$form_html = igny8_render_inline_form_row($table_id, $mode, $record_data);
if ($form_html) {
wp_send_json_success($form_html);
} else {
wp_send_json_error('Failed to render form row');
}
} catch (Exception $e) {
wp_send_json_error('PHP Error: ' . $e->getMessage());
}
}
/**
* Debug AJAX handler - Toggle debug monitoring
*/
/**
* AJAX handler for getting row data for edit form
*/
function igny8_get_row_data() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce')) {
wp_send_json_error('Security check failed');
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
// Get parameters
$table_id = sanitize_text_field($_POST['table_id'] ?? '');
$row_id = intval($_POST['row_id'] ?? 0);
if (empty($table_id) || !$row_id) {
wp_send_json_error('Table ID and Row ID required');
}
// Get table name
$table_name = igny8_get_table_name($table_id);
if (!$table_name) {
wp_send_json_error('Invalid table ID');
}
global $wpdb;
// Check if table exists
$table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'");
if (!$table_exists) {
wp_send_json_error("Table $table_name does not exist");
}
// Get row data
$row_data = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$table_name} WHERE id = %d",
$row_id
), ARRAY_A);
if (!$row_data) {
wp_send_json_error('Record not found');
}
// ===========================================================
// FIX: Convert numeric difficulty value into text label for edit form
// ===========================================================
// This ensures that when editing a record, the "Difficulty" dropdown
// correctly pre-selects the current label (e.g., "Hard") instead of
// defaulting to "Select Difficulty".
if (isset($row_data['difficulty']) && is_numeric($row_data['difficulty'])) {
if (function_exists('igny8_get_difficulty_range_name')) {
$row_data['difficulty'] = igny8_get_difficulty_range_name($row_data['difficulty']);
}
}
// ===========================================================
// covered_keywords is already in comma-separated format, no conversion needed
wp_send_json_success($row_data);
}
/**
* AJAX handler for toggling debug monitoring
*/
function igny8_toggle_debug_monitoring() {
// Verify nonce for security
if (!check_ajax_referer('igny8_ajax_nonce', 'nonce', true)) {
wp_send_json_error(['message' => 'Security check failed.']);
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'You do not have permission to perform this action.']);
}
$is_enabled = isset($_POST['is_enabled']) ? (bool) $_POST['is_enabled'] : false;
// Debug logging
error_log("DEBUG: Toggle AJAX - is_enabled: " . ($is_enabled ? 'true' : 'false'));
error_log("DEBUG: Toggle AJAX - POST data: " . print_r($_POST, true));
// Update the option in the database
$result = update_option('igny8_debug_enabled', $is_enabled);
error_log("DEBUG: Toggle AJAX - update_option result: " . ($result ? 'success' : 'no change'));
wp_send_json_success([
'message' => 'Debug monitoring status updated.',
'is_enabled' => $is_enabled
]);
}
/**
* Test AJAX endpoint
*/
function igny8_test_ajax() {
wp_send_json_success('AJAX is working!');
}
/**
* AJAX handler for viewing cluster keywords (modal display)
*/
add_action('wp_ajax_igny8_view_cluster_keywords', 'igny8_ajax_view_cluster_keywords');
function igny8_ajax_view_cluster_keywords() {
// Verify nonce for security
if (!check_ajax_referer('igny8_ajax_nonce', 'nonce', true)) {
wp_send_json_error(['message' => 'Security check failed.']);
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'You do not have permission to perform this action.']);
}
// Get parameters
$cluster_id = intval($_POST['cluster_id'] ?? 0);
if (empty($cluster_id)) {
wp_send_json_error(['message' => 'No cluster ID provided.']);
}
// Call view function
$result = igny8_view_cluster_keywords($cluster_id);
if ($result['success']) {
wp_send_json_success([
'keywords' => $result['keywords'],
'cluster_name' => $result['cluster_name'],
'message' => $result['message']
]);
} else {
wp_send_json_error(['message' => $result['message']]);
}
}
/**
* AJAX handler for creating draft from idea
*/
add_action('wp_ajax_igny8_create_draft_from_idea', 'igny8_ajax_create_draft_from_idea');
function igny8_ajax_create_draft_from_idea() {
// Verify nonce for security
if (!check_ajax_referer('igny8_ajax_nonce', 'nonce', true)) {
wp_send_json_error(['message' => 'Security check failed.']);
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'You do not have permission to perform this action.']);
}
// Get parameters
$idea_id = intval($_POST['idea_id'] ?? 0);
if (empty($idea_id)) {
wp_send_json_error(['message' => 'No idea ID provided.']);
}
// Call create draft function
$result = igny8_create_draft_from_idea($idea_id);
if ($result['success']) {
wp_send_json_success([
'message' => $result['message'],
'draft_id' => $result['draft_id']
]);
} else {
wp_send_json_error(['message' => $result['message']]);
}
}
/**
* AJAX handler for mapping cluster to keywords
*/
add_action('wp_ajax_igny8_map_cluster_to_keywords', 'igny8_ajax_map_cluster_to_keywords');
function igny8_ajax_map_cluster_to_keywords() {
// Verify nonce for security
if (!check_ajax_referer('igny8_ajax_nonce', 'nonce', true)) {
wp_send_json_error(['message' => 'Security check failed.']);
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'You do not have permission to perform this action.']);
}
// Get parameters
$cluster_id = intval($_POST['cluster_id'] ?? 0);
$keyword_ids = $_POST['keyword_ids'] ?? [];
if (empty($cluster_id)) {
wp_send_json_error(['message' => 'No cluster ID provided.']);
}
if (empty($keyword_ids) || !is_array($keyword_ids)) {
wp_send_json_error(['message' => 'No keywords provided for mapping.']);
}
// Call map function
$result = igny8_map_cluster_to_keywords($cluster_id, $keyword_ids);
if ($result['success']) {
wp_send_json_success([
'message' => $result['message'],
'mapped_count' => $result['mapped_count']
]);
} else {
wp_send_json_error(['message' => $result['message']]);
}
}
// =========================================
// Planner Settings AJAX Handlers
// =========================================
/**
* Get parent sectors (sectors with no parent)
*/
add_action('wp_ajax_igny8_get_parent_sectors', 'igny8_ajax_get_parent_sectors');
function igny8_ajax_get_parent_sectors() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_planner_settings')) {
wp_send_json_error(['message' => 'Security check failed']);
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
// Check if sectors taxonomy exists, if not try to register it
if (!taxonomy_exists('sectors')) {
// Try to register the taxonomy
if (function_exists('igny8_register_taxonomies')) {
igny8_register_taxonomies();
}
// Check again
if (!taxonomy_exists('sectors')) {
wp_send_json_error(['message' => 'Sectors taxonomy not registered. Please create sample sectors first.']);
}
}
// Get parent sectors (terms with parent = 0)
$parent_sectors = get_terms([
'taxonomy' => 'sectors',
'parent' => 0,
'hide_empty' => false,
'orderby' => 'name',
'order' => 'ASC'
]);
if (is_wp_error($parent_sectors)) {
wp_send_json_error(['message' => 'Error retrieving parent sectors: ' . $parent_sectors->get_error_message()]);
}
$sectors_data = [];
foreach ($parent_sectors as $sector) {
$sectors_data[] = [
'id' => $sector->term_id,
'name' => $sector->name,
'slug' => $sector->slug
];
}
wp_send_json_success($sectors_data);
}
/**
* Get child sectors for a specific parent
*/
add_action('wp_ajax_igny8_get_child_sectors', 'igny8_ajax_get_child_sectors');
function igny8_ajax_get_child_sectors() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_planner_settings')) {
wp_send_json_error(['message' => 'Security check failed']);
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$parent_id = intval($_POST['parent_id']);
if (!$parent_id) {
wp_send_json_error(['message' => 'Invalid parent ID']);
}
// Check if sectors taxonomy exists, if not try to register it
if (!taxonomy_exists('sectors')) {
// Try to register the taxonomy
if (function_exists('igny8_register_taxonomies')) {
igny8_register_taxonomies();
}
// Check again
if (!taxonomy_exists('sectors')) {
wp_send_json_error(['message' => 'Sectors taxonomy not registered. Please create sample sectors first.']);
}
}
// Get child sectors
$child_sectors = get_terms([
'taxonomy' => 'sectors',
'parent' => $parent_id,
'hide_empty' => false,
'orderby' => 'name',
'order' => 'ASC'
]);
if (is_wp_error($child_sectors)) {
wp_send_json_error(['message' => 'Error retrieving child sectors: ' . $child_sectors->get_error_message()]);
}
$sectors_data = [];
foreach ($child_sectors as $sector) {
$sectors_data[] = [
'id' => $sector->term_id,
'name' => $sector->name,
'slug' => $sector->slug
];
}
wp_send_json_success($sectors_data);
}
/**
* Save sector selection
*/
add_action('wp_ajax_igny8_save_sector_selection', 'igny8_ajax_save_sector_selection');
function igny8_ajax_save_sector_selection() {
try {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_planner_settings')) {
wp_send_json_error(['message' => 'Security check failed']);
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$parent_id = intval($_POST['parent_id']);
$children_count = intval($_POST['children_count']);
if (!$parent_id || $children_count <= 0) {
wp_send_json_error(['message' => 'Invalid data provided']);
}
// Build children array from individual form fields
$children = [];
for ($i = 0; $i < $children_count; $i++) {
$child_id = intval($_POST["child_{$i}_id"]);
$child_name = sanitize_text_field($_POST["child_{$i}_name"]);
if ($child_id && $child_name) {
$children[] = [
'id' => $child_id,
'name' => $child_name
];
}
}
if (empty($children)) {
wp_send_json_error(['message' => 'No valid children data received']);
}
// Check if sectors taxonomy exists, if not try to register it
if (!taxonomy_exists('sectors')) {
// Try to register the taxonomy
if (function_exists('igny8_register_taxonomies')) {
igny8_register_taxonomies();
}
// Check again
if (!taxonomy_exists('sectors')) {
wp_send_json_error(['message' => 'Sectors taxonomy not registered. Please create sample sectors first.']);
}
}
// Get parent sector info
$parent_sector = get_term($parent_id, 'sectors');
if (is_wp_error($parent_sector) || !$parent_sector) {
$error_msg = is_wp_error($parent_sector) ? $parent_sector->get_error_message() : 'Sector not found';
wp_send_json_error(['message' => 'Invalid parent sector: ' . $error_msg]);
}
// Validate children sectors
$validated_children = [];
foreach ($children as $child) {
if (isset($child['id']) && isset($child['name'])) {
$child_term = get_term($child['id'], 'sectors');
if (!is_wp_error($child_term) && $child_term && $child_term->parent == $parent_id) {
$validated_children[] = [
'id' => $child['id'],
'name' => $child['name']
];
}
}
}
if (empty($validated_children)) {
wp_send_json_error(['message' => 'No valid child sectors selected. Please ensure the selected sectors exist and belong to the parent sector.']);
}
// Save to user options
$selection_data = [
'parent' => [
'id' => $parent_sector->term_id,
'name' => $parent_sector->name
],
'children' => $validated_children,
'saved_at' => current_time('mysql')
];
$user_id = get_current_user_id();
update_user_meta($user_id, 'igny8_planner_sector_selection', $selection_data);
wp_send_json_success($selection_data);
} catch (Exception $e) {
error_log('DEBUG: Exception in save_sector_selection: ' . $e->getMessage());
wp_send_json_error(['message' => 'Server error: ' . $e->getMessage()]);
}
}
/**
* Get saved sector selection
*/
add_action('wp_ajax_igny8_get_saved_sector_selection', 'igny8_ajax_get_saved_sector_selection');
function igny8_ajax_get_saved_sector_selection() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_planner_settings')) {
wp_send_json_error(['message' => 'Security check failed']);
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$user_id = get_current_user_id();
$saved_selection = get_user_meta($user_id, 'igny8_planner_sector_selection', true);
if (empty($saved_selection)) {
wp_send_json_success(null);
}
// Validate that the saved sectors still exist
$parent_id = $saved_selection['parent']['id'];
$parent_sector = get_term($parent_id, 'sectors');
if (is_wp_error($parent_sector) || !$parent_sector) {
// Parent sector no longer exists, clear the selection
delete_user_meta($user_id, 'igny8_planner_sector_selection');
wp_send_json_success(null);
}
// Validate children
$valid_children = [];
foreach ($saved_selection['children'] as $child) {
$child_term = get_term($child['id'], 'sectors');
if (!is_wp_error($child_term) && $child_term && $child_term->parent == $parent_id) {
$valid_children[] = $child;
}
}
if (empty($valid_children)) {
// No valid children, clear the selection
delete_user_meta($user_id, 'igny8_planner_sector_selection');
wp_send_json_success(null);
}
// Update with validated data
$saved_selection['children'] = $valid_children;
update_user_meta($user_id, 'igny8_planner_sector_selection', $saved_selection);
wp_send_json_success($saved_selection);
}
/**
* Get AI operation progress
*/
add_action('wp_ajax_igny8_get_ai_progress', 'igny8_ajax_get_ai_progress');
function igny8_ajax_get_ai_progress() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_planner_settings')) {
wp_send_json_error(['message' => 'Security check failed']);
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$operation = sanitize_text_field($_POST['operation'] ?? '');
$session_id = sanitize_text_field($_POST['session_id'] ?? '');
if (empty($operation) || empty($session_id)) {
wp_send_json_error(['message' => 'Missing operation or session ID']);
}
// Get recent AI logs for this operation
$ai_logs = get_option('igny8_ai_logs', []);
$operation_logs = array_filter($ai_logs, function($log) use ($operation, $session_id) {
return isset($log['action']) && $log['action'] === $operation &&
isset($log['details']) && strpos($log['details'], $session_id) !== false;
});
// Count completed items based on success logs
$completed = 0;
$total = 0;
$current_message = 'Processing...';
foreach ($operation_logs as $log) {
if (isset($log['event'])) {
if (strpos($log['event'], 'Complete') !== false || strpos($log['event'], 'Created') !== false) {
$completed++;
}
if (strpos($log['event'], 'Initiated') !== false) {
// Extract total count from initiation log
if (preg_match('/(\d+)\s+(keywords|clusters|ideas|tasks)/', $log['details'], $matches)) {
$total = intval($matches[1]);
}
}
}
}
wp_send_json_success([
'completed' => $completed,
'total' => $total,
'message' => $current_message,
'is_complete' => $completed >= $total && $total > 0
]);
}
/**
* Save AI Integration Settings
*/
add_action('wp_ajax_igny8_save_ai_integration_settings', 'igny8_ajax_save_ai_integration_settings');
function igny8_ajax_save_ai_integration_settings() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_planner_settings')) {
wp_send_json_error(['message' => 'Security check failed']);
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
// Get and sanitize form data
$planner_mode = sanitize_text_field($_POST['igny8_planner_mode'] ?? 'manual');
$clustering = sanitize_text_field($_POST['igny8_ai_clustering'] ?? 'enabled');
$ideas = sanitize_text_field($_POST['igny8_ai_ideas'] ?? 'enabled');
$mapping = sanitize_text_field($_POST['igny8_ai_mapping'] ?? 'enabled');
// Automation settings
$auto_cluster = sanitize_text_field($_POST['igny8_auto_cluster_enabled'] ?? 'disabled');
$auto_generate_ideas = sanitize_text_field($_POST['igny8_auto_generate_ideas_enabled'] ?? 'disabled');
$auto_queue = sanitize_text_field($_POST['igny8_auto_queue_enabled'] ?? 'disabled');
// Validate values
$valid_modes = ['manual', 'ai'];
$valid_values = ['enabled', 'disabled'];
if (!in_array($planner_mode, $valid_modes)) {
wp_send_json_error(['message' => 'Invalid planner mode']);
}
if (!in_array($clustering, $valid_values) || !in_array($ideas, $valid_values) || !in_array($mapping, $valid_values)) {
wp_send_json_error(['message' => 'Invalid setting values']);
}
if (!in_array($auto_cluster, $valid_values) || !in_array($auto_generate_ideas, $valid_values) || !in_array($auto_queue, $valid_values)) {
wp_send_json_error(['message' => 'Invalid automation setting values']);
}
// Save settings using new AI settings system
igny8_update_ai_setting('planner_mode', $planner_mode);
igny8_update_ai_setting('clustering', $clustering);
igny8_update_ai_setting('ideas', $ideas);
igny8_update_ai_setting('mapping', $mapping);
// Save automation settings
igny8_update_ai_setting('auto_cluster_enabled', $auto_cluster);
igny8_update_ai_setting('auto_generate_ideas_enabled', $auto_generate_ideas);
igny8_update_ai_setting('auto_queue_enabled', $auto_queue);
// Schedule/unschedule automation based on settings
igny8_manage_automation_schedules();
wp_send_json_success(['message' => 'AI Integration settings saved successfully']);
}
/**
* AI Integration Settings - Save Writer AI settings
*/
add_action('wp_ajax_igny8_save_writer_ai_settings', 'igny8_ajax_save_writer_ai_settings');
/**
* Regenerate CRON key
*/
add_action('wp_ajax_igny8_regenerate_cron_key', 'igny8_ajax_regenerate_cron_key');
function igny8_ajax_regenerate_cron_key() {
try {
// Verify nonce (use planner nonce since it's the same for both modules)
if (!wp_verify_nonce($_POST['nonce'], 'igny8_planner_settings') && !wp_verify_nonce($_POST['nonce'], 'igny8_writer_settings')) {
wp_send_json_error(['message' => 'Security check failed']);
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
// Generate new key
$new_key = wp_generate_password(32, false, false);
update_option('igny8_secure_cron_key', $new_key);
wp_send_json_success(['new_key' => $new_key, 'message' => 'CRON key regenerated successfully']);
} catch (Exception $e) {
wp_send_json_error(['message' => 'Error: ' . $e->getMessage()]);
}
}
function igny8_ajax_save_writer_ai_settings() {
try {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_writer_settings')) {
wp_send_json_error(['message' => 'Security check failed']);
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
// Get and sanitize form data
$writer_mode = sanitize_text_field($_POST['igny8_writer_mode'] ?? 'manual');
$content_generation = sanitize_text_field($_POST['igny8_content_generation'] ?? 'enabled');
// Writer automation settings
$auto_generate_content = sanitize_text_field($_POST['igny8_auto_generate_content_enabled'] ?? 'disabled');
$auto_publish_drafts = sanitize_text_field($_POST['igny8_auto_publish_drafts_enabled'] ?? 'disabled');
// Validate values
$valid_modes = ['manual', 'ai'];
$valid_values = ['enabled', 'disabled'];
if (!in_array($writer_mode, $valid_modes)) {
wp_send_json_error(['message' => 'Invalid writer mode']);
}
if (!in_array($content_generation, $valid_values)) {
wp_send_json_error(['message' => 'Invalid content generation setting']);
}
if (!in_array($auto_generate_content, $valid_values) || !in_array($auto_publish_drafts, $valid_values)) {
wp_send_json_error(['message' => 'Invalid automation setting values']);
}
// Save settings using new AI settings system
igny8_update_ai_setting('writer_mode', $writer_mode);
igny8_update_ai_setting('content_generation', $content_generation);
// Save Writer automation settings
igny8_update_ai_setting('auto_generate_content_enabled', $auto_generate_content);
igny8_update_ai_setting('auto_publish_drafts_enabled', $auto_publish_drafts);
// Schedule/unschedule Writer automation based on settings
igny8_manage_writer_automation_schedules();
wp_send_json_success(['message' => 'Writer AI settings saved successfully']);
} catch (Exception $e) {
wp_send_json_error(['message' => 'Error: ' . $e->getMessage()]);
}
}
/**
* Save Writer Content Generation Prompt
*/
add_action('wp_ajax_igny8_save_content_prompt', 'igny8_ajax_save_content_prompt');
function igny8_ajax_save_content_prompt() {
try {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_writer_settings')) {
wp_send_json_error(['message' => 'Security check failed']);
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
// Get and sanitize prompt data
$content_prompt = wp_unslash($_POST['igny8_content_generation_prompt'] ?? '');
// Save prompt using AI settings system
igny8_update_ai_setting('content_generation_prompt', $content_prompt);
wp_send_json_success(['message' => 'Content generation prompt saved successfully']);
} catch (Exception $e) {
wp_send_json_error(['message' => 'Error: ' . $e->getMessage()]);
}
}
/**
* AI Content Generation - Generate content from idea and keywords
*/
add_action('wp_ajax_igny8_ai_generate_content', 'igny8_ajax_ai_generate_content');
function igny8_ajax_ai_generate_content() {
try {
// Debug logging for CRON context
if (defined('DOING_CRON') && DOING_CRON) {
error_log("Igny8 AI Generation: Starting content generation process");
}
// Verify nonce - accept multiple nonce types for compatibility
$nonce_valid = wp_verify_nonce($_POST['nonce'], 'igny8_writer_settings') ||
wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce');
if (!$nonce_valid) {
if (defined('DOING_CRON') && DOING_CRON) {
error_log("Igny8 AI Generation: Security check failed - invalid nonce");
}
wp_send_json_error(['message' => 'Security check failed']);
}
// Check user permissions
if (!current_user_can('manage_options')) {
if (defined('DOING_CRON') && DOING_CRON) {
error_log("Igny8 AI Generation: Insufficient permissions");
}
wp_send_json_error(['message' => 'Insufficient permissions']);
}
// Check if AI mode is enabled
if (igny8_get_ai_setting('writer_mode', 'manual') !== 'ai') {
if (defined('DOING_CRON') && DOING_CRON) {
error_log("Igny8 AI Generation: AI mode is not enabled");
}
wp_send_json_error(['message' => 'AI mode is not enabled']);
}
// Get task ID
$task_id = absint($_POST['task_id'] ?? 0);
if (!$task_id) {
if (defined('DOING_CRON') && DOING_CRON) {
error_log("Igny8 AI Generation: Invalid task ID");
}
wp_send_json_error(['message' => 'Invalid task ID']);
}
if (defined('DOING_CRON') && DOING_CRON) {
error_log("Igny8 AI Generation: Processing task ID: " . $task_id);
}
global $wpdb;
// Get task details
$task = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}igny8_tasks WHERE id = %d",
$task_id
));
if (!$task) {
wp_send_json_error(['message' => 'Task not found']);
}
// Get idea details if available
$idea = null;
if ($task->idea_id) {
$idea = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}igny8_content_ideas WHERE id = %d",
$task->idea_id
));
}
// Get cluster details if available
$cluster = null;
if ($task->cluster_id) {
$cluster = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}igny8_clusters WHERE id = %d",
$task->cluster_id
));
}
// Get keywords - prefer target_keywords from idea, fallback to cluster keywords
$keywords = [];
if ($idea && !empty($idea->target_keywords)) {
// Use target_keywords from the idea (comma-separated format)
$target_keywords_array = array_map('trim', explode(',', $idea->target_keywords));
foreach ($target_keywords_array as $keyword) {
if (!empty($keyword)) {
$keywords[] = (object)['keyword' => $keyword];
}
}
} elseif ($task->cluster_id) {
// Fallback to all keywords from cluster
$keywords = $wpdb->get_results($wpdb->prepare(
"SELECT keyword FROM {$wpdb->prefix}igny8_keywords WHERE cluster_id = %d",
$task->cluster_id
));
}
// Get desktop quantity setting for image generation
$desktop_quantity = get_option('igny8_desktop_quantity', 1);
// Prepare data for AI processing
$ai_data = [
'idea' => $idea,
'cluster' => $cluster,
'keywords' => $keywords,
'task_id' => $task_id,
'desktop_quantity' => $desktop_quantity
];
// Generate session ID for progress tracking
$session_id = 'content_' . time() . '_' . wp_generate_password(8, false);
// Log AI request initiation
igny8_log_ai_event('AI Content Generation Initiated', 'writer', 'content_generation', 'info', 'Starting content generation for task', 'Task ID: ' . $task_id . ', Session: ' . $session_id);
if (defined('DOING_CRON') && DOING_CRON) {
error_log("Igny8 AI Generation: AI data prepared - Idea: " . ($idea ? 'Yes' : 'No') . ", Cluster: " . ($cluster ? 'Yes' : 'No') . ", Keywords: " . count($keywords));
}
// Get content generation prompt from database (same as prompts page)
$prompt_template = igny8_get_ai_setting('content_generation_prompt', igny8_content_generation_prompt());
// Check if prompt is loaded from database field
$db_prompt = igny8_get_ai_setting('content_generation_prompt', '');
if (empty($db_prompt)) {
if (defined('DOING_CRON') && DOING_CRON) {
error_log("Igny8 AI Generation: Wrong prompt detected - database field is empty");
}
wp_send_json_error(['message' => 'Wrong prompt detected - database field is empty']);
}
if (defined('DOING_CRON') && DOING_CRON) {
error_log("Igny8 AI Generation: Prompt template loaded from database, length: " . strlen($prompt_template));
}
// Process with AI
if (defined('DOING_CRON') && DOING_CRON) {
error_log("Igny8 AI Generation: Calling igny8_process_ai_request...");
}
$ai_result = igny8_process_ai_request('content_generation', $ai_data, $prompt_template);
if (defined('DOING_CRON') && DOING_CRON) {
error_log("Igny8 AI Generation: AI result received: " . ($ai_result ? 'Success' : 'Failed'));
if ($ai_result) {
error_log("Igny8 AI Generation: AI result type: " . gettype($ai_result));
if (is_array($ai_result)) {
error_log("Igny8 AI Generation: AI result keys: " . implode(', ', array_keys($ai_result)));
}
} else {
error_log("Igny8 AI Generation: AI result is false/null - checking why...");
}
}
if (!$ai_result) {
igny8_log_ai_event('AI Content Generation Failed', 'writer', 'content_generation', 'error', 'AI processing returned false', 'Check OpenAI API configuration');
wp_send_json_error(['message' => 'AI content generation failed - no result']);
}
// Parse and validate AI response
if (!isset($ai_result['content']) || !isset($ai_result['title'])) {
igny8_log_ai_event('AI Content Generation Failed', 'writer', 'content_generation', 'error', 'AI returned invalid response structure', 'Missing content or title fields');
wp_send_json_error(['message' => 'AI returned invalid content structure']);
}
// Save raw AI response content to tasks table
$wpdb->update(
$wpdb->prefix . 'igny8_tasks',
['raw_ai_response' => $ai_result['content']],
['id' => $task_id],
['%s'],
['%d']
);
// Get new content decision setting (will be used after successful post creation)
$new_content_action = get_option('igny8_new_content_action', 'draft');
// Debug logging
error_log('Igny8 DEBUG: Content decision setting: ' . $new_content_action);
// Prepare update data for task (status will be set after post creation)
$update_data = [
'description' => $ai_result['content'],
'updated_at' => current_time('mysql')
];
// Add title if provided
if (!empty($ai_result['title'])) {
$update_data['title'] = $ai_result['title'];
}
// Add meta fields if provided
if (!empty($ai_result['meta_title'])) {
$update_data['meta_title'] = $ai_result['meta_title'];
}
if (!empty($ai_result['meta_description'])) {
$update_data['meta_description'] = $ai_result['meta_description'];
}
if (!empty($ai_result['keywords'])) {
$update_data['keywords'] = $ai_result['keywords'];
}
if (!empty($ai_result['word_count'])) {
$update_data['word_count'] = $ai_result['word_count'];
}
// Add task_id to AI result for taxonomy association
$ai_result['task_id'] = $task_id;
// Create WordPress post from AI response
error_log("IGNY8 DEBUG: I AM ACTIVE AND RUNNING IN AJAX.PHP - About to call igny8_create_post_from_ai_response()");
$post_id = igny8_create_post_from_ai_response($ai_result);
error_log("IGNY8 DEBUG: I AM ACTIVE AND RUNNING IN AJAX.PHP - igny8_create_post_from_ai_response() returned: " . ($post_id ? $post_id : 'false'));
// Check taxonomy association results
$cluster_success = false;
$sector_success = false;
if ($post_id) {
// Determine task status based on content decision setting AFTER successful post creation
$task_status = ($new_content_action === 'publish') ? 'completed' : 'draft';
error_log('Igny8 DEBUG: Task status set to: ' . $task_status . ' after successful post creation');
// Link the WordPress post to the task
update_post_meta($post_id, '_igny8_task_id', $task_id);
// Update task with WordPress post ID and mark as completed
$wpdb->update(
$wpdb->prefix . 'igny8_tasks',
[
'assigned_post_id' => $post_id,
'status' => $task_status,
'updated_at' => current_time('mysql')
],
['id' => $task_id],
['%d', '%s', '%s'],
['%d']
);
// Update task with generated content
$wpdb->update(
$wpdb->prefix . 'igny8_tasks',
$update_data,
['id' => $task_id],
['%s', '%s', '%s', '%s'],
['%d']
);
// Check if taxonomies were actually associated
$cluster_terms = wp_get_object_terms($post_id, 'clusters');
$sector_terms = wp_get_object_terms($post_id, 'sectors');
$cluster_success = !empty($cluster_terms) && !is_wp_error($cluster_terms);
$sector_success = !empty($sector_terms) && !is_wp_error($sector_terms);
igny8_log_ai_event('WordPress Post Created', 'writer', 'content_generation', 'success', 'WordPress post created from AI content', 'Post ID: ' . $post_id . ', Task ID: ' . $task_id);
igny8_log_ai_event('AI Content Generation Complete', 'writer', 'content_generation', 'success', 'Content generated and saved to task', 'Task ID: ' . $task_id . ', Word count: ' . ($ai_result['word_count'] ?? 'unknown'));
} else {
// Log failure but DO NOT change task status - keep it as draft
igny8_log_ai_event('WordPress Post Creation Failed', 'writer', 'content_generation', 'error', 'Failed to create WordPress post from AI content', 'Task ID: ' . $task_id);
igny8_log_ai_event('AI Content Generation Failed', 'writer', 'content_generation', 'error', 'Content generation failed - post creation unsuccessful', 'Task ID: ' . $task_id);
}
// Check if this is a CRON request (no AJAX)
if (defined('DOING_CRON') || (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], 'wp-load.php') !== false)) {
// For CRON requests, don't send JSON, just return success
echo "<strong>Igny8 CRON: Content generation completed for task " . $task_id . "</strong><br>";
return;
}
wp_send_json_success([
'message' => 'Content generated successfully',
'content' => 'original post content',
'title' => $ai_result['title'] ?? $task->title,
'word_count' => $ai_result['word_count'] ?? 'unknown',
'meta_description' => $ai_result['meta_description'] ?? '',
'seo_score' => $ai_result['seo_score'] ?? 'unknown',
'post_id' => $post_id ?? null,
'post_edit_url' => $post_id ? get_edit_post_link($post_id) : null,
'task_status' => $task_status ?? 'failed',
'session_id' => $session_id
]);
} catch (Exception $e) {
igny8_log_ai_event('AI Content Generation Error', 'writer', 'content_generation', 'error', 'Exception during content generation', $e->getMessage());
wp_send_json_error(['message' => 'Error: ' . $e->getMessage()]);
}
}
/**
* AI Clustering - Process keywords into clusters
*/
add_action('wp_ajax_igny8_ai_cluster_keywords', 'igny8_ajax_ai_cluster_keywords');
function igny8_ajax_ai_cluster_keywords() {
// Add detailed logging for cron debugging
if (defined('DOING_AJAX') && DOING_AJAX) {
error_log('Igny8 AJAX: Function started - AJAX context detected');
} else {
error_log('Igny8 AJAX: Function started - Non-AJAX context (cron)');
}
// Ensure we're in an AJAX context and prevent HTML output
// Skip AJAX check for cron context (external cron URLs)
if (!wp_doing_ajax() && !defined('DOING_AJAX')) {
error_log('Igny8 AJAX: wp_doing_ajax() returned false, calling wp_die');
wp_die('Invalid request');
}
// If we're in cron context, we're good to proceed
if (defined('DOING_AJAX')) {
error_log('Igny8 AJAX: Cron context detected, bypassing AJAX validation');
}
error_log('Igny8 AJAX: Passed wp_doing_ajax() check');
try {
error_log('Igny8 AJAX: Starting nonce validation');
// Verify nonce - accept multiple nonce types for compatibility
$nonce_valid = wp_verify_nonce($_POST['nonce'], 'igny8_planner_settings') ||
wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce');
error_log('Igny8 AJAX: Nonce validation result: ' . ($nonce_valid ? 'VALID' : 'INVALID'));
if (!$nonce_valid) {
error_log('Igny8 AJAX: Nonce validation failed, sending error');
wp_send_json_error(['message' => 'Security check failed']);
}
error_log('Igny8 AJAX: Starting user permission check');
// Check user permissions
$user_can = current_user_can('manage_options');
error_log('Igny8 AJAX: User permission check result: ' . ($user_can ? 'HAS PERMISSION' : 'NO PERMISSION'));
if (!$user_can) {
error_log('Igny8 AJAX: User permission check failed, sending error');
wp_send_json_error(['message' => 'Insufficient permissions']);
}
error_log('Igny8 AJAX: Passed user permission check');
// Check if AI mode is enabled
error_log('Igny8 AJAX: Checking AI mode');
$planner_mode = igny8_get_ai_setting('planner_mode', 'manual');
error_log('Igny8 AJAX: Planner mode: ' . $planner_mode);
if ($planner_mode !== 'ai') {
error_log('Igny8 AJAX: AI mode check failed, sending error');
wp_send_json_error(['message' => 'AI mode is not enabled']);
}
error_log('Igny8 AJAX: Passed AI mode check');
// Check if clustering is enabled
error_log('Igny8 AJAX: Checking clustering setting');
$clustering_enabled = igny8_get_ai_setting('clustering', 'enabled');
error_log('Igny8 AJAX: Clustering setting: ' . $clustering_enabled);
if ($clustering_enabled !== 'enabled') {
error_log('Igny8 AJAX: Clustering check failed, sending error');
wp_send_json_error(['message' => 'Clustering feature is disabled']);
}
error_log('Igny8 AJAX: Passed clustering check');
// Check if sector is selected (reuse existing function)
error_log('Igny8 AJAX: Checking sector options');
$sector_options = igny8_get_sector_options();
error_log('Igny8 AJAX: Sector options count: ' . count($sector_options));
if (empty($sector_options)) {
error_log('Igny8 AJAX: No sectors found, sending error');
wp_send_json_error(['message' => 'You must select a Sector before performing Auto Clustering.']);
}
error_log('Igny8 AJAX: Passed sector check');
// Handle keyword_ids - it comes as JSON string from JavaScript
error_log('Igny8 AJAX: Processing keyword IDs');
$keyword_ids_raw = $_POST['keyword_ids'] ?? [];
error_log('Igny8 AJAX: Raw keyword IDs: ' . print_r($keyword_ids_raw, true));
if (is_string($keyword_ids_raw)) {
$keyword_ids = json_decode($keyword_ids_raw, true) ?: [];
error_log('Igny8 AJAX: Decoded keyword IDs: ' . print_r($keyword_ids, true));
} else {
$keyword_ids = $keyword_ids_raw;
}
$keyword_ids = array_map('intval', $keyword_ids);
error_log('Igny8 AJAX: Final keyword IDs: ' . print_r($keyword_ids, true));
if (empty($keyword_ids)) {
error_log('Igny8 AJAX: No keywords found, sending error');
wp_send_json_error(['message' => 'No keywords selected']);
}
error_log('Igny8 AJAX: Passed keyword validation');
// Limit to 20 keywords max
if (count($keyword_ids) > 20) {
wp_send_json_error(['message' => 'Maximum 20 keywords allowed for clustering']);
}
// Get keywords data and check if they already have clusters assigned
global $wpdb;
$placeholders = implode(',', array_fill(0, count($keyword_ids), '%d'));
$keywords = $wpdb->get_results($wpdb->prepare("
SELECT * FROM {$wpdb->prefix}igny8_keywords
WHERE id IN ({$placeholders})
", $keyword_ids));
if (empty($keywords)) {
wp_send_json_error(['message' => 'No valid keywords found']);
}
// Check if keywords already have clusters assigned
$keywords_with_clusters = array_filter($keywords, function($keyword) {
return !empty($keyword->cluster_id) && $keyword->cluster_id > 0;
});
if (!empty($keywords_with_clusters)) {
$keyword_names = array_column($keywords_with_clusters, 'keyword');
wp_send_json_error(['message' => 'Keywords already have clusters assigned: ' . implode(', ', array_slice($keyword_names, 0, 3)) . (count($keyword_names) > 3 ? '...' : '')]);
}
// Get clustering prompt
$prompt_template = wp_unslash(igny8_get_ai_setting('clustering_prompt', igny8_get_default_clustering_prompt()));
// Generate session ID for progress tracking
$session_id = 'clustering_' . time() . '_' . wp_generate_password(8, false);
// Log AI request initiation
igny8_log_ai_event('AI Request Initiated', 'planner', 'clustering', 'info', 'Starting AI clustering process', 'Keywords: ' . count($keyword_ids) . ', Session: ' . $session_id);
// Log data preparation
igny8_log_ai_event('Data Preparation', 'planner', 'clustering', 'info', 'Preparing keywords data for AI', 'Keywords count: ' . count($keywords) . ', Prompt length: ' . strlen($prompt_template));
// Process with AI
error_log('Igny8 AI: Starting AI processing with ' . count($keywords) . ' keywords');
error_log('Igny8 AJAX: About to call igny8_process_ai_request');
$ai_result = igny8_process_ai_request('clustering', $keywords, $prompt_template);
error_log('Igny8 AJAX: AI processing completed, result type: ' . gettype($ai_result));
// Log detailed AI processing result
if ($ai_result === false) {
igny8_log_ai_event('AI Processing Failed', 'planner', 'clustering', 'error', 'AI processing returned false', 'Check OpenAI API configuration');
} elseif (is_array($ai_result) && isset($ai_result['clusters'])) {
igny8_log_ai_event('AI Processing Complete', 'planner', 'clustering', 'success', 'AI returned ' . count($ai_result['clusters']) . ' clusters', 'Clusters: ' . json_encode(array_column($ai_result['clusters'], 'name')));
} elseif (is_array($ai_result)) {
igny8_log_ai_event('AI Processing Failed', 'planner', 'clustering', 'error', 'AI returned array but missing clusters key', 'Result keys: ' . json_encode(array_keys($ai_result)));
} else {
igny8_log_ai_event('AI Processing Failed', 'planner', 'clustering', 'error', 'AI returned invalid data type', 'Type: ' . gettype($ai_result) . ', Value: ' . json_encode($ai_result));
}
if (!$ai_result) {
error_log('Igny8 AI: AI processing returned false');
wp_send_json_error(['message' => 'AI processing failed - no result']);
}
if (!isset($ai_result['clusters'])) {
error_log('Igny8 AI: AI result missing clusters array. Result: ' . print_r($ai_result, true));
wp_send_json_error(['message' => 'AI processing failed - invalid result format']);
}
// Log database operations start
igny8_log_ai_event('Database Operations Started', 'planner', 'clustering', 'info', 'Starting to create clusters in database', 'Clusters to create: ' . count($ai_result['clusters']));
// Get sector options for assignment logic
$sector_options = igny8_get_sector_options();
$sector_count = count($sector_options);
// Create clusters in database
$created_clusters = [];
foreach ($ai_result['clusters'] as $cluster_data) {
// Determine sector_id based on sector count
$sector_id = 1; // Default fallback
if ($sector_count == 1) {
// Only 1 sector: assign all clusters to that sector
$sector_id = $sector_options[0]['value'];
} elseif ($sector_count > 1) {
// Multiple sectors: use AI response sector assignment
if (isset($cluster_data['sector']) && !empty($cluster_data['sector'])) {
// Find sector ID by matching sector name from AI response
foreach ($sector_options as $sector) {
if (strtolower(trim($sector['label'])) === strtolower(trim($cluster_data['sector']))) {
$sector_id = $sector['value'];
break;
}
}
}
// If no match found or no sector in AI response, use first sector as fallback
if ($sector_id == 1 && !isset($cluster_data['sector'])) {
$sector_id = $sector_options[0]['value'];
}
}
$result = $wpdb->insert(
$wpdb->prefix . 'igny8_clusters',
[
'cluster_name' => sanitize_text_field($cluster_data['name']),
'sector_id' => $sector_id,
'status' => 'active',
'keyword_count' => count($cluster_data['keywords']),
'total_volume' => 0,
'avg_difficulty' => 0,
'mapped_pages_count' => 0,
'created_at' => current_time('mysql')
],
['%s', '%d', '%s', '%d', '%d', '%f', '%d', '%s']
);
if ($result) {
$cluster_id = $wpdb->insert_id;
$created_clusters[] = $cluster_id;
// Trigger taxonomy term creation for AI-generated cluster
do_action('igny8_cluster_added', $cluster_id);
igny8_log_ai_event('Cluster Taxonomy Triggered', 'planner', 'clustering', 'info', 'Triggered igny8_cluster_added action', "Cluster: {$cluster_data['name']} (ID: {$cluster_id})");
// Log cluster creation
igny8_log_ai_event('Cluster Created', 'planner', 'clustering', 'success', 'Cluster created successfully', "Cluster: {$cluster_data['name']} (ID: {$cluster_id})");
// Update keywords with cluster_id
foreach ($cluster_data['keywords'] as $keyword_name) {
$update_result = $wpdb->update(
$wpdb->prefix . 'igny8_keywords',
['cluster_id' => $cluster_id],
['keyword' => $keyword_name],
['%d'],
['%s']
);
// Log if keyword update failed
if ($update_result === false) {
error_log("Igny8 AI: Failed to update keyword '{$keyword_name}' with cluster_id {$cluster_id}. Error: " . $wpdb->last_error);
}
}
// Log keyword updates
igny8_log_ai_event('Keywords Updated', 'planner', 'clustering', 'success', 'Keywords assigned to cluster', "Cluster: {$cluster_data['name']}, Keywords: " . count($cluster_data['keywords']));
// Update cluster metrics
try {
$metrics_result = igny8_update_cluster_metrics($cluster_id);
if ($metrics_result) {
igny8_log_ai_event('Metrics Updated', 'planner', 'clustering', 'success', 'Cluster metrics calculated', "Cluster: {$cluster_data['name']}");
} else {
igny8_log_ai_event('Metrics Update Failed', 'planner', 'clustering', 'warning', 'Failed to update cluster metrics', "Cluster: {$cluster_data['name']}");
}
} catch (Exception $e) {
igny8_log_ai_event('Metrics Update Error', 'planner', 'clustering', 'error', 'Exception during metrics update', "Cluster: {$cluster_data['name']}, Error: " . $e->getMessage());
error_log("Igny8 AI: Metrics update error for cluster {$cluster_id}: " . $e->getMessage());
}
} else {
// Log cluster creation failure
igny8_log_ai_event('Cluster Creation Failed', 'planner', 'clustering', 'error', 'Failed to create cluster in database', "Cluster: {$cluster_data['name']}, Error: " . $wpdb->last_error);
}
}
// Log completion
igny8_log_ai_event('AI Clustering Complete', 'planner', 'clustering', 'success', 'AI clustering process completed successfully', "Clusters created: " . count($created_clusters));
wp_send_json_success([
'message' => 'Successfully created ' . count($created_clusters) . ' clusters',
'clusters_created' => count($created_clusters),
'session_id' => $session_id
]);
} catch (Exception $e) {
// Log error
igny8_log_ai_event('AI Clustering Error', 'planner', 'clustering', 'error', 'Exception during AI clustering process', $e->getMessage());
error_log('Igny8 AI Clustering Error: ' . $e->getMessage());
wp_send_json_error(['message' => 'Error processing AI clustering: ' . $e->getMessage()]);
}
}
/**
* Save Prompt - AJAX handler for saving prompts
*/
add_action('wp_ajax_igny8_save_prompt', 'igny8_ajax_save_prompt');
function igny8_ajax_save_prompt() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_thinker_settings')) {
wp_send_json_error('Security check failed');
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
// Get parameters
$prompt_type = sanitize_text_field($_POST['prompt_type'] ?? '');
$prompt_value = sanitize_textarea_field($_POST['prompt_value'] ?? '');
if (empty($prompt_type) || empty($prompt_value)) {
wp_send_json_error('Prompt type and value are required');
}
// Validate prompt type
$valid_prompt_types = [
'clustering_prompt',
'ideas_prompt',
'content_generation_prompt',
'image_prompt_template',
'negative_prompt',
];
if (!in_array($prompt_type, $valid_prompt_types)) {
wp_send_json_error('Invalid prompt type');
}
// Save the prompt using appropriate method
if (in_array($prompt_type, ['image_prompt_template', 'negative_prompt'])) {
// Image prompts are stored as regular WordPress options
update_option('igny8_' . $prompt_type, $prompt_value);
} else {
// AI prompts use the AI settings system
igny8_update_ai_setting($prompt_type, $prompt_value);
}
wp_send_json_success('Prompt saved successfully');
}
/**
* Reset Individual Prompt - AJAX handler for resetting individual prompts
*/
add_action('wp_ajax_igny8_reset_prompt', 'igny8_ajax_reset_prompt');
function igny8_ajax_reset_prompt() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_thinker_settings')) {
wp_send_json_error('Security check failed');
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
// Get parameters
$prompt_type = sanitize_text_field($_POST['prompt_type'] ?? '');
if (empty($prompt_type)) {
wp_send_json_error('Prompt type is required');
}
// Validate prompt type
$valid_prompt_types = [
'clustering_prompt',
'ideas_prompt',
'content_generation_prompt',
];
if (!in_array($prompt_type, $valid_prompt_types)) {
wp_send_json_error('Invalid prompt type');
}
// Get default prompt value
$default_function = 'igny8_get_default_' . $prompt_type;
if (!function_exists($default_function)) {
wp_send_json_error('Default prompt function not found');
}
$default_value = $default_function();
// Reset the prompt using AI settings system
igny8_update_ai_setting($prompt_type, $default_value);
wp_send_json_success('Prompt reset to default successfully');
}
/**
* Reset Multiple Prompts - AJAX handler for resetting multiple prompts at once
*/
add_action('wp_ajax_igny8_reset_multiple_prompts', 'igny8_ajax_reset_multiple_prompts');
function igny8_ajax_reset_multiple_prompts() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_thinker_settings')) {
wp_send_json_error('Security check failed');
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
// Get parameters
$prompt_types = $_POST['prompt_types'] ?? [];
if (empty($prompt_types) || !is_array($prompt_types)) {
wp_send_json_error('Prompt types are required');
}
// Validate prompt types
$valid_prompt_types = [
'clustering_prompt',
'ideas_prompt',
'content_generation_prompt',
];
$reset_data = [];
foreach ($prompt_types as $prompt_type) {
$prompt_type = sanitize_text_field($prompt_type);
if (!in_array($prompt_type, $valid_prompt_types)) {
wp_send_json_error('Invalid prompt type: ' . $prompt_type);
}
// Get default prompt value
if ($prompt_type === 'content_generation_prompt') {
$default_value = igny8_content_generation_prompt();
} else {
$default_function = 'igny8_get_default_' . $prompt_type;
if (!function_exists($default_function)) {
wp_send_json_error('Default prompt function not found for: ' . $prompt_type);
}
$default_value = $default_function();
}
// Reset the prompt using AI settings system
igny8_update_ai_setting($prompt_type, $default_value);
// Store the reset value for response
$reset_data[$prompt_type] = $default_value;
}
wp_send_json_success([
'message' => 'All prompts reset to default successfully',
'data' => $reset_data
]);
}
/**
* AI Logs - Get AI event logs
*/
add_action('wp_ajax_igny8_get_ai_logs', 'igny8_ajax_get_ai_logs');
function igny8_ajax_get_ai_logs() {
// Verify nonce - try multiple nonce types
$nonce_valid = false;
if (isset($_POST['nonce'])) {
$nonce_valid = wp_verify_nonce($_POST['nonce'], 'igny8_debug_nonce') ||
wp_verify_nonce($_POST['nonce'], 'igny8_planner_settings') ||
wp_verify_nonce($_POST['nonce'], 'igny8_writer_settings') ||
wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce');
}
if (!$nonce_valid) {
wp_send_json_error(['message' => 'Security check failed']);
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
// Get AI logs from options (last 50 events)
$ai_logs = get_option('igny8_ai_logs', []);
// Sort by timestamp (newest first)
usort($ai_logs, function($a, $b) {
return strtotime($b['timestamp']) - strtotime($a['timestamp']);
});
// Limit to last 50 events
$ai_logs = array_slice($ai_logs, 0, 50);
wp_send_json_success($ai_logs);
}
/**
* AI Logs - Clear AI event logs
*/
add_action('wp_ajax_igny8_clear_ai_logs', 'igny8_ajax_clear_ai_logs');
function igny8_ajax_clear_ai_logs() {
// Verify nonce - try multiple nonce types
$nonce_valid = false;
if (isset($_POST['nonce'])) {
$nonce_valid = wp_verify_nonce($_POST['nonce'], 'igny8_debug_nonce') ||
wp_verify_nonce($_POST['nonce'], 'igny8_planner_settings') ||
wp_verify_nonce($_POST['nonce'], 'igny8_writer_settings') ||
wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce');
}
if (!$nonce_valid) {
wp_send_json_error(['message' => 'Security check failed']);
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
// Clear AI logs
delete_option('igny8_ai_logs');
wp_send_json_success(['message' => 'AI logs cleared successfully']);
}
/**
* AI Ideas Generation - Generate content ideas from clusters
*/
add_action('wp_ajax_igny8_ai_generate_ideas', 'igny8_ajax_ai_generate_ideas');
function igny8_ajax_ai_generate_ideas() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_planner_settings')) {
wp_send_json_error(['message' => 'Security check failed']);
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
// Check if AI mode is enabled
if (igny8_get_ai_setting('planner_mode', 'manual') !== 'ai') {
wp_send_json_error(['message' => 'AI mode is not enabled']);
}
// Check if ideas generation is enabled
if (igny8_get_ai_setting('ideas', 'enabled') !== 'enabled') {
wp_send_json_error(['message' => 'Ideas generation feature is disabled']);
}
// Handle cluster_ids - it comes as JSON string from JavaScript
$cluster_ids_raw = $_POST['cluster_ids'] ?? [];
if (is_string($cluster_ids_raw)) {
$cluster_ids = json_decode($cluster_ids_raw, true) ?: [];
} else {
$cluster_ids = $cluster_ids_raw;
}
$cluster_ids = array_map('intval', $cluster_ids);
if (empty($cluster_ids)) {
wp_send_json_error(['message' => 'No clusters selected']);
}
// Limit to 5 clusters max
if (count($cluster_ids) > 5) {
wp_send_json_error(['message' => 'Maximum 5 clusters allowed for idea generation']);
}
// Get clusters data with their keywords and check if they already have ideas
global $wpdb;
$placeholders = implode(',', array_fill(0, count($cluster_ids), '%d'));
$clusters = $wpdb->get_results($wpdb->prepare("
SELECT c.*,
GROUP_CONCAT(k.keyword SEPARATOR ', ') as keywords_list
FROM {$wpdb->prefix}igny8_clusters c
LEFT JOIN {$wpdb->prefix}igny8_keywords k ON c.id = k.cluster_id
WHERE c.id IN ({$placeholders})
GROUP BY c.id
", $cluster_ids));
if (empty($clusters)) {
wp_send_json_error(['message' => 'No valid clusters found']);
}
// Check if clusters already have associated ideas
$clusters_with_ideas = [];
foreach ($clusters as $cluster) {
$idea_count = $wpdb->get_var($wpdb->prepare("
SELECT COUNT(*) FROM {$wpdb->prefix}igny8_content_ideas
WHERE keyword_cluster_id = %d
", $cluster->id));
if ($idea_count > 0) {
$clusters_with_ideas[] = $cluster->name;
}
}
if (!empty($clusters_with_ideas)) {
wp_send_json_error(['message' => 'Clusters already have associated ideas: ' . implode(', ', array_slice($clusters_with_ideas, 0, 3)) . (count($clusters_with_ideas) > 3 ? '...' : '')]);
}
// Generate session ID for progress tracking
$session_id = 'ideas_' . time() . '_' . wp_generate_password(8, false);
// Log AI request initiation
igny8_log_ai_event('AI Request Initiated', 'planner', 'ideas', 'info', 'Starting AI ideas generation process', 'Clusters: ' . count($cluster_ids) . ', Session: ' . $session_id);
// Get ideas prompt
$prompt_template = wp_unslash(igny8_get_ai_setting('ideas_prompt', igny8_get_default_ideas_prompt()));
// Log data preparation
igny8_log_ai_event('Data Preparation', 'planner', 'ideas', 'info', 'Preparing clusters data for AI', 'Clusters count: ' . count($clusters) . ', Prompt length: ' . strlen($prompt_template));
// Process with AI
error_log('Igny8 AI: Starting AI processing with ' . count($clusters) . ' clusters');
$ai_result = igny8_process_ai_request('ideas', $clusters, $prompt_template);
// Log detailed AI processing result
if ($ai_result === false) {
igny8_log_ai_event('AI Processing Failed', 'planner', 'ideas', 'error', 'AI processing returned false', 'Check OpenAI API configuration');
} elseif (is_array($ai_result) && isset($ai_result['ideas'])) {
igny8_log_ai_event('AI Processing Complete', 'planner', 'ideas', 'success', 'AI returned ' . count($ai_result['ideas']) . ' ideas', 'Ideas: ' . json_encode(array_column($ai_result['ideas'], 'title')));
} elseif (is_array($ai_result)) {
igny8_log_ai_event('AI Processing Failed', 'planner', 'ideas', 'error', 'AI returned array but missing ideas key', 'Result keys: ' . json_encode(array_keys($ai_result)));
} else {
igny8_log_ai_event('AI Processing Failed', 'planner', 'ideas', 'error', 'AI returned invalid data type', 'Type: ' . gettype($ai_result) . ', Value: ' . json_encode($ai_result));
}
if (!$ai_result) {
error_log('Igny8 AI: AI processing returned false');
wp_send_json_error(['message' => 'AI processing failed - no result']);
}
// Handle different AI response formats
if (!$ai_result) {
wp_send_json_error(['message' => 'AI processing failed']);
}
// Check if response is wrapped in 'ideas' key or direct array
$ideas = null;
if (isset($ai_result['ideas'])) {
$ideas = $ai_result['ideas'];
} elseif (is_array($ai_result) && !isset($ai_result['ideas'])) {
// AI returned direct array of ideas
$ideas = $ai_result;
igny8_log_ai_event('Response Format Adjusted', 'planner', 'ideas', 'info', 'AI returned direct array, wrapped for processing', 'Ideas count: ' . count($ideas));
}
if (!$ideas || !is_array($ideas)) {
igny8_log_ai_event('AI Processing Failed', 'planner', 'ideas', 'error', 'No valid ideas found in AI response', 'Response type: ' . gettype($ai_result) . ', Keys: ' . json_encode(array_keys($ai_result)));
wp_send_json_error(['message' => 'AI processing failed - no valid ideas']);
}
// Log database operations start
igny8_log_ai_event('Database Operations Started', 'planner', 'ideas', 'info', 'Starting to create ideas in database', 'Ideas to create: ' . count($ideas));
// Create ideas in database
$created_ideas = [];
foreach ($ideas as $idea_data) {
// Validate required fields
if (empty($idea_data['title']) || empty($idea_data['description']) || empty($idea_data['cluster_id'])) {
igny8_log_ai_event('Idea Validation Failed', 'planner', 'ideas', 'error', 'Missing required fields in AI response', 'Title: ' . ($idea_data['title'] ?? 'missing') . ', Description: ' . (empty($idea_data['description']) ? 'missing' : 'present') . ', Cluster ID: ' . ($idea_data['cluster_id'] ?? 'missing'));
continue;
}
// Debug: Log the idea data being processed
error_log('Igny8 Debug: Processing idea - Title: ' . $idea_data['title'] . ', Cluster ID: ' . $idea_data['cluster_id'] . ', Content Structure: ' . ($idea_data['content_structure'] ?? 'missing') . ', Content Type: ' . ($idea_data['content_type'] ?? 'missing'));
// Validate content structure and type
if (empty($idea_data['content_structure'])) {
$idea_data['content_structure'] = 'cluster_hub'; // Default fallback
}
if (empty($idea_data['content_type'])) {
$idea_data['content_type'] = 'post'; // Default fallback
}
// Handle target_keywords field - store as comma-separated text
$target_keywords = null;
if (isset($idea_data['covered_keywords']) && !empty($idea_data['covered_keywords'])) {
// Handle both array and string formats
if (is_array($idea_data['covered_keywords'])) {
$keywords = array_map('trim', $idea_data['covered_keywords']);
} else {
$keywords = array_map('trim', explode(',', $idea_data['covered_keywords']));
}
$keywords = array_filter($keywords); // Remove empty values
$target_keywords = implode(', ', $keywords);
}
// Handle image_prompts field - store as JSON string
$image_prompts = null;
if (isset($idea_data['image_prompts']) && !empty($idea_data['image_prompts'])) {
// Ensure it's properly formatted JSON
if (is_array($idea_data['image_prompts'])) {
$image_prompts = json_encode($idea_data['image_prompts']);
} else {
$image_prompts = sanitize_text_field($idea_data['image_prompts']);
}
}
// Handle description field - store as JSON string for structured content
$description = null;
if (isset($idea_data['description']) && !empty($idea_data['description'])) {
if (is_array($idea_data['description'])) {
// If it's already structured JSON, encode it
$description = json_encode($idea_data['description']);
} else {
// If it's a string, store as is (for backward compatibility)
$description = sanitize_textarea_field($idea_data['description']);
}
}
// Debug: Log what we're trying to insert
error_log('Igny8 Debug: Inserting idea with target_keywords: ' . ($target_keywords ?: 'NULL') . ', image_prompts: ' . ($image_prompts ? 'Present' : 'NULL') . ', description length: ' . strlen($description ?: 'NULL'));
$result = $wpdb->insert(
$wpdb->prefix . 'igny8_content_ideas',
[
'idea_title' => sanitize_text_field($idea_data['title']),
'idea_description' => $description ?: sanitize_textarea_field($idea_data['description']),
'content_structure' => sanitize_text_field($idea_data['content_structure'] ?? 'cluster_hub'),
'content_type' => sanitize_text_field($idea_data['content_type'] ?? 'post'),
'keyword_cluster_id' => intval($idea_data['cluster_id']),
'status' => 'new',
'estimated_word_count' => intval($idea_data['estimated_word_count']),
'target_keywords' => $target_keywords ?: null,
'image_prompts' => $image_prompts ?: null,
'source' => 'AI',
'mapped_post_id' => null,
'tasks_count' => 0
],
['%s', '%s', '%s', '%s', '%d', '%s', '%d', '%s', '%s', '%s', '%d', '%d']
);
if ($result) {
$created_ideas[] = $wpdb->insert_id;
igny8_log_ai_event('Idea Created', 'planner', 'ideas', 'success', 'Content idea created successfully', 'Title: ' . $idea_data['title']);
} else {
error_log('Igny8 Debug: Database insert failed - Error: ' . $wpdb->last_error);
igny8_log_ai_event('Idea Creation Failed', 'planner', 'ideas', 'error', 'Failed to create content idea', 'Title: ' . $idea_data['title'] . ', Error: ' . $wpdb->last_error);
}
}
// Log completion
igny8_log_ai_event('AI Ideas Generation Complete', 'planner', 'ideas', 'success', 'AI ideas generation process completed successfully', 'Ideas created: ' . count($created_ideas));
wp_send_json_success([
'message' => 'Successfully created ' . count($created_ideas) . ' content ideas',
'ideas_created' => count($created_ideas),
'session_id' => $session_id
]);
}
/**
* AI Image Generation - Generate featured images for ideas
*/
/**
* Get image prompt counts for posts (preview before generation)
*/
add_action('wp_ajax_igny8_get_image_counts', 'igny8_ajax_get_image_counts');
function igny8_ajax_get_image_counts() {
if (!wp_verify_nonce($_POST['nonce'], 'igny8_writer_settings')) {
wp_send_json_error(['message' => 'Security check failed']);
}
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$task_ids_raw = $_POST['post_ids'] ?? [];
if (is_string($task_ids_raw)) {
$task_ids = json_decode($task_ids_raw, true) ?: [];
} else {
$task_ids = $task_ids_raw;
}
$task_ids = array_map('intval', $task_ids);
if (empty($task_ids)) {
wp_send_json_error(['message' => 'No tasks selected']);
}
// Get image generation settings from request
$desktop_enabled = sanitize_text_field($_POST['desktop_enabled'] ?? '0') === '1';
$mobile_enabled = sanitize_text_field($_POST['mobile_enabled'] ?? '0') === '1';
$max_in_article_images = intval($_POST['max_in_article_images'] ?? 1);
error_log('Igny8: Image counts settings - Desktop: ' . ($desktop_enabled ? 'enabled' : 'disabled') .
', Mobile: ' . ($mobile_enabled ? 'enabled' : 'disabled') .
', Max In-Article: ' . $max_in_article_images);
global $wpdb;
$placeholders = implode(',', array_fill(0, count($task_ids), '%d'));
$tasks = $wpdb->get_results($wpdb->prepare(
"SELECT id, title, assigned_post_id FROM {$wpdb->prefix}igny8_tasks WHERE id IN ($placeholders)",
...$task_ids
));
if (empty($tasks)) {
wp_send_json_error(['message' => 'No valid tasks found']);
}
$image_queue = [];
foreach ($tasks as $task) {
if (empty($task->assigned_post_id)) continue;
$post_title = get_the_title($task->assigned_post_id) ?: $task->title;
// Check for featured image prompt
$featured_prompt = get_post_meta($task->assigned_post_id, '_igny8_featured_image_prompt', true);
if (!empty($featured_prompt)) {
$image_queue[] = [
'post_id' => $task->assigned_post_id,
'task_id' => $task->id,
'post_title' => $post_title,
'type' => 'featured',
'label' => 'Featured Image',
'prompt' => $featured_prompt
];
}
// Check for in-article image prompts - handle both formats
// Only add in-article images if desktop or mobile is enabled
if ($desktop_enabled || $mobile_enabled) {
// Format 1: New format with array (from _igny8_article_images_data)
$article_images_data = get_post_meta($task->assigned_post_id, '_igny8_article_images_data', true);
if (!empty($article_images_data)) {
$article_images = json_decode($article_images_data, true);
if (json_last_error() !== JSON_ERROR_NONE) {
error_log("IGNY8 IMAGE QUEUE: JSON decode error for _igny8_article_images_data: " . json_last_error_msg());
error_log("IGNY8 IMAGE QUEUE: Raw data: " . substr($article_images_data, 0, 200) . "...");
// Try to clean the data by stripping HTML tags
$cleaned_data = wp_strip_all_tags($article_images_data);
$article_images = json_decode($cleaned_data, true);
if (json_last_error() !== JSON_ERROR_NONE) {
error_log("IGNY8 IMAGE QUEUE: Still invalid JSON after cleaning: " . json_last_error_msg());
$article_images = null; // Skip this format
} else {
error_log("IGNY8 IMAGE QUEUE: Successfully cleaned and parsed JSON");
}
}
if (is_array($article_images)) {
$image_count = 0;
foreach ($article_images as $index => $image_data) {
// Find the prompt key (prompt-img-1, prompt-img-2, etc.)
$prompt_key = null;
$prompt_value = null;
foreach ($image_data as $key => $value) {
if (strpos($key, 'prompt-img-') === 0) {
$prompt_key = $key;
$prompt_value = $value;
break;
}
}
if (!empty($prompt_value) && $image_count < $max_in_article_images) {
// Desktop version (if enabled)
if ($desktop_enabled) {
$image_queue[] = [
'post_id' => $task->assigned_post_id,
'task_id' => $task->id,
'post_title' => $post_title,
'type' => 'article',
'device' => 'desktop',
'section' => $image_data['section'] ?? "Section " . ($index + 1),
'label' => "Article " . ($index + 1) . " - Desktop",
'prompt' => $prompt_value,
'index' => $index
];
}
// Mobile version (if enabled)
if ($mobile_enabled) {
$image_queue[] = [
'post_id' => $task->assigned_post_id,
'task_id' => $task->id,
'post_title' => $post_title,
'type' => 'article',
'device' => 'mobile',
'section' => $image_data['section'] ?? "Section " . ($index + 1),
'label' => "Article " . ($index + 1) . " - Mobile",
'prompt' => $prompt_value,
'index' => $index
];
}
$image_count++;
}
}
}
}
}
// Format 2: Old format with in_article_image_1, in_article_image_2, etc (from _igny8_image_prompts)
if (($desktop_enabled || $mobile_enabled) && (empty($image_queue) || count($image_queue) == 1)) { // Only featured found, check old format
$image_prompts_json = get_post_meta($task->assigned_post_id, '_igny8_image_prompts', true);
if (!empty($image_prompts_json)) {
$image_prompts = json_decode($image_prompts_json, true);
if (is_array($image_prompts)) {
$article_index = 0;
foreach ($image_prompts as $key => $prompt) {
if (strpos($key, 'in_article_image_') === 0 && !empty($prompt) && $article_index < $max_in_article_images) {
// Extract section name from key or use generic name
$section = "Section " . ($article_index + 1);
// Desktop version (if enabled)
if ($desktop_enabled) {
$image_queue[] = [
'post_id' => $task->assigned_post_id,
'task_id' => $task->id,
'post_title' => $post_title,
'type' => 'article',
'device' => 'desktop',
'section' => $section,
'label' => "Article " . ($article_index + 1) . " - Desktop",
'prompt' => $prompt,
'index' => $article_index
];
}
// Mobile version (if enabled)
if ($mobile_enabled) {
$image_queue[] = [
'post_id' => $task->assigned_post_id,
'task_id' => $task->id,
'post_title' => $post_title,
'type' => 'article',
'device' => 'mobile',
'section' => $section,
'label' => "Article " . ($article_index + 1) . " - Mobile",
'prompt' => $prompt,
'index' => $article_index
];
}
$article_index++;
}
}
}
}
}
}
wp_send_json_success([
'total_images' => count($image_queue),
'queue' => $image_queue
]);
}
/**
* Generate single image from queue
*/
add_action('wp_ajax_igny8_generate_single_image_queue', 'igny8_ajax_generate_single_image_queue');
function igny8_ajax_generate_single_image_queue() {
if (!wp_verify_nonce($_POST['nonce'], 'igny8_writer_settings')) {
wp_send_json_error(['message' => 'Security check failed']);
}
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$post_id = intval($_POST['post_id'] ?? 0);
$type = sanitize_text_field($_POST['type'] ?? '');
$device = sanitize_text_field($_POST['device'] ?? '');
$prompt = sanitize_textarea_field($_POST['prompt'] ?? '');
$section = sanitize_text_field($_POST['section'] ?? '');
$index = intval($_POST['index'] ?? 0);
if (empty($post_id) || empty($type) || empty($prompt)) {
wp_send_json_error(['message' => 'Missing required parameters']);
}
// Get image generation settings
$image_type = get_option('igny8_image_type', 'realistic');
$image_provider = get_option('igny8_image_provider', 'runware');
$image_format = get_option('igny8_image_format', 'jpg');
$negative_prompt = get_option('igny8_negative_prompt', '');
try {
if ($type === 'featured') {
// Generate featured image
$result = igny8_generate_single_image(
$post_id,
$prompt,
'featured',
$image_provider,
$image_format,
$negative_prompt,
['type' => 'featured']
);
if ($result['success']) {
set_post_thumbnail($post_id, $result['attachment_id']);
wp_send_json_success([
'attachment_id' => $result['attachment_id'],
'image_url' => $result['image_url'],
'type' => 'featured'
]);
} else {
wp_send_json_error(['message' => $result['error']]);
}
} elseif ($type === 'article') {
// Generate article image (desktop or mobile)
// Use prompt as-is if it's already detailed, otherwise enhance it
$full_prompt = $prompt;
if (strlen($prompt) < 50 || strpos($prompt, 'Create') !== 0) {
// Only enhance if prompt is short or doesn't start with "Create"
$full_prompt = "Create a high-quality {$image_type} image for the section titled '{$section}'. {$prompt}";
}
$size_type = ($device === 'mobile') ? 'mobile' : 'desktop';
$result = igny8_generate_single_image(
$post_id,
$full_prompt,
$size_type,
$image_provider,
$image_format,
$negative_prompt,
[
'section' => $section,
'index' => $index,
'device' => $device
]
);
if ($result['success']) {
wp_send_json_success([
'attachment_id' => $result['attachment_id'],
'image_url' => $result['image_url'],
'type' => 'article',
'device' => $device,
'section' => $section,
'index' => $index
]);
} else {
wp_send_json_error(['message' => $result['error']]);
}
}
} catch (Exception $e) {
wp_send_json_error(['message' => 'Exception: ' . $e->getMessage()]);
}
}
/**
* AI Generate Images for Drafts - Generate images from post meta prompts
*/
add_action('wp_ajax_igny8_ai_generate_images_drafts', 'igny8_ajax_ai_generate_images_drafts');
function igny8_ajax_ai_generate_images_drafts() {
error_log('Igny8: AJAX HANDLER CALLED - igny8_ajax_ai_generate_images_drafts');
error_log('Igny8: POST data received: ' . print_r($_POST, true));
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_writer_settings')) {
error_log('Igny8: NONCE VERIFICATION FAILED');
wp_send_json_error(['message' => 'Security check failed']);
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
// Handle post_ids - these are actually task IDs from igny8_tasks table
$task_ids_raw = $_POST['post_ids'] ?? [];
if (is_string($task_ids_raw)) {
$task_ids = json_decode($task_ids_raw, true) ?: [];
} else {
$task_ids = $task_ids_raw;
}
$task_ids = array_map('intval', $task_ids);
if (empty($task_ids)) {
wp_send_json_error(['message' => 'No tasks selected']);
}
// Limit to 10 tasks max (image generation is expensive)
if (count($task_ids) > 10) {
wp_send_json_error(['message' => 'Maximum 10 tasks allowed for image generation']);
}
// Get image generation settings from request
$desktop_enabled = sanitize_text_field($_POST['desktop_enabled'] ?? '0') === '1';
$mobile_enabled = sanitize_text_field($_POST['mobile_enabled'] ?? '0') === '1';
$max_in_article_images = intval($_POST['max_in_article_images'] ?? 1);
error_log('Igny8: Image generation settings - Desktop: ' . ($desktop_enabled ? 'enabled' : 'disabled') .
', Mobile: ' . ($mobile_enabled ? 'enabled' : 'disabled') .
', Max In-Article: ' . $max_in_article_images);
global $wpdb;
// Event 3: Task IDs validated
error_log('Igny8: IMAGE_GEN_EVENT_3 - Task IDs validated: ' . implode(', ', $task_ids));
$debug_events[] = ['event' => 'Task IDs validated', 'level' => 'INFO', 'data' => ['taskIds' => $task_ids]];
// Get WordPress post IDs from tasks table
$placeholders = implode(',', array_fill(0, count($task_ids), '%d'));
$tasks = $wpdb->get_results($wpdb->prepare(
"SELECT id, title, assigned_post_id FROM {$wpdb->prefix}igny8_tasks WHERE id IN ($placeholders)",
...$task_ids
));
if (empty($tasks)) {
error_log('Igny8: IMAGE_GEN_EVENT_3_ERROR - No valid tasks found');
wp_send_json_error(['message' => 'No valid tasks found']);
}
// Event 4: WordPress post IDs retrieved
$post_ids_retrieved = array_map(function($task) { return $task->assigned_post_id; }, $tasks);
$task_to_post_map = [];
foreach ($tasks as $t) {
$task_to_post_map[$t->id] = $t->assigned_post_id;
}
error_log('Igny8: IMAGE_GEN_EVENT_4 - WordPress post IDs retrieved: ' . implode(', ', $post_ids_retrieved));
$debug_events[] = ['event' => 'WordPress post IDs retrieved', 'level' => 'SUCCESS', 'data' => ['mapping' => $task_to_post_map]];
// Process each task
$generated_images = [];
$failed_images = [];
// Generate session ID for progress tracking
$session_id = 'image_gen_drafts_' . time() . '_' . wp_generate_password(8, false);
// Log AI request initiation
igny8_log_ai_event('AI Image Generation Initiated', 'writer', 'image_generation', 'info', 'Starting AI image generation for drafts', 'Tasks: ' . count($tasks) . ', Session: ' . $session_id);
foreach ($tasks as $task) {
$task_id = $task->id;
$post_id = $task->assigned_post_id;
$task_title = $task->title;
error_log('Igny8: IMAGE_GEN - Processing Task ID: ' . $task_id . ' -> WordPress Post ID: ' . $post_id);
if (!$post_id) {
error_log('Igny8: IMAGE_GEN_ERROR - Task ' . $task_id . ' has no assigned WordPress post');
$failed_images[] = [
'task_id' => $task_id,
'task_title' => $task_title,
'error' => 'No WordPress post assigned to this task'
];
continue;
}
$post = get_post($post_id);
if (!$post) {
error_log('Igny8: IMAGE_GEN_ERROR - WordPress post ' . $post_id . ' not found for task ' . $task_id);
$failed_images[] = [
'task_id' => $task_id,
'task_title' => $task_title,
'post_id' => $post_id,
'error' => 'WordPress post not found'
];
continue;
}
error_log('Igny8: IMAGE_GEN - WordPress post found: ' . $post->post_title . ' (Post ID: ' . $post_id . ', Task ID: ' . $task_id . ')');
// Event 5: Image prompts loaded
$featured_prompt = get_post_meta($post_id, '_igny8_featured_image_prompt', true);
$article_images = get_post_meta($post_id, '_igny8_article_images_data', true);
error_log('Igny8: IMAGE_GEN_EVENT_5 - Image prompts loaded for post: ' . $post_id);
$debug_events[] = ['event' => 'Image prompts loaded', 'level' => 'INFO', 'data' => ['postId' => $post_id, 'postTitle' => $post->post_title]];
// Event 6: Featured image generation initiated
error_log('Igny8: IMAGE_GEN_EVENT_6 - Featured image generation initiated for post: ' . $post_id);
$debug_events[] = ['event' => 'Featured image generation started', 'level' => 'INFO', 'data' => ['postId' => $post_id]];
$featured_result = igny8_generate_featured_image_for_post($post_id);
error_log('Igny8: Step 4 - WordPress post retrieved: ' . ($post ? 'Post found: ' . $post->post_title . ' (ID: ' . $post->ID . ')' : 'Post not found'));
// Generate featured image (always)
if ($featured_result['success']) {
$generated_images[] = [
'task_id' => $task_id,
'post_id' => $post_id,
'post_title' => $post->post_title,
'type' => 'featured',
'attachment_id' => $featured_result['attachment_id'],
'image_url' => $featured_result['image_url'],
'provider' => $featured_result['provider']
];
// Event 9: Image saved successfully
$debug_events[] = ['event' => 'Featured image saved', 'level' => 'SUCCESS', 'data' => ['postId' => $post_id, 'attachmentId' => $featured_result['attachment_id'], 'provider' => $featured_result['provider']]];
// Log success
igny8_log_ai_event('Featured Image Generated', 'writer', 'image_generation', 'success', 'Featured image generated and set for post', 'Post: ' . $post->post_title . ', Post ID: ' . $post_id . ', Attachment ID: ' . $featured_result['attachment_id']);
} else {
$failed_images[] = [
'task_id' => $task_id,
'post_id' => $post_id,
'post_title' => $post->post_title,
'type' => 'featured',
'error' => $featured_result['error']
];
// Event: Image generation failed
$debug_events[] = ['event' => 'Featured image failed', 'level' => 'ERROR', 'data' => ['postId' => $post_id, 'error' => $featured_result['error']]];
}
// Safety check: Calculate safe image quantity based on post content
error_log('Igny8: Step 5 - Calculating safe image quantity for post_id: ' . $post_id . ', content length: ' . strlen($post->post_content));
$safe_max_images = igny8_calculate_safe_image_quantity($post->post_content, $max_in_article_images);
error_log('Igny8: Step 5 - Safe max images calculated: ' . $safe_max_images);
// Generate desktop in-article images if enabled
if ($desktop_enabled) {
error_log('Igny8: Step 6 - Generating desktop images for post_id: ' . $post_id . ', count: ' . $safe_max_images);
for ($i = 1; $i <= $safe_max_images; $i++) {
error_log('Igny8: Step 6 - Generating desktop image ' . $i . ' for post_id: ' . $post_id);
$desktop_result = igny8_generate_single_article_image($post_id, 'desktop', $i);
error_log('Igny8: Step 6 - Desktop image ' . $i . ' result: ' . print_r($desktop_result, true));
if ($desktop_result['success']) {
$generated_images[] = [
'task_id' => $task_id,
'post_id' => $post_id,
'post_title' => $post->post_title,
'type' => 'desktop',
'index' => $i,
'attachment_id' => $desktop_result['attachment_id'],
'image_url' => $desktop_result['image_url'],
'provider' => $desktop_result['provider']
];
} else {
$failed_images[] = [
'task_id' => $task_id,
'post_id' => $post_id,
'post_title' => $post->post_title,
'type' => 'desktop',
'index' => $i,
'error' => $desktop_result['error']
];
}
}
}
// Generate mobile in-article images if enabled
if ($mobile_enabled) {
error_log('Igny8: Step 7 - Generating mobile images for post_id: ' . $post_id . ', count: ' . $safe_max_images);
for ($i = 1; $i <= $safe_max_images; $i++) {
error_log('Igny8: Step 7 - Generating mobile image ' . $i . ' for post_id: ' . $post_id);
$mobile_result = igny8_generate_single_article_image($post_id, 'mobile', $i);
error_log('Igny8: Step 7 - Mobile image ' . $i . ' result: ' . print_r($mobile_result, true));
if ($mobile_result['success']) {
$generated_images[] = [
'task_id' => $task_id,
'post_id' => $post_id,
'post_title' => $post->post_title,
'type' => 'mobile',
'index' => $i,
'attachment_id' => $mobile_result['attachment_id'],
'image_url' => $mobile_result['image_url'],
'provider' => $mobile_result['provider']
];
} else {
$failed_images[] = [
'task_id' => $task_id,
'post_id' => $post_id,
'post_title' => $post->post_title,
'type' => 'mobile',
'index' => $i,
'error' => $mobile_result['error']
];
}
}
}
}
// Log completion
igny8_log_ai_event('AI Image Generation Complete', 'writer', 'image_generation', 'success', 'Image generation completed', 'Success: ' . count($generated_images) . ', Failed: ' . count($failed_images));
wp_send_json_success([
'message' => 'Generated ' . count($generated_images) . ' images' . (count($failed_images) > 0 ? ', ' . count($failed_images) . ' failed' : ''),
'images_generated' => count($generated_images),
'images_failed' => count($failed_images),
'generated_images' => $generated_images,
'failed_images' => $failed_images,
'session_id' => $session_id,
'debug_events' => $debug_events
]);
}
/**
* AI Generate Single Image - Generate one image at a time for queue processing
*/
add_action('wp_ajax_igny8_ai_generate_single_image', 'igny8_ajax_ai_generate_single_image');
function igny8_ajax_ai_generate_single_image() {
// Verify nonce
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'igny8_writer_settings')) {
wp_send_json_error(['message' => 'Security check failed']);
}
// Check user permissions
if (!current_user_can('edit_posts')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
// Get parameters - post_id is actually task_id from igny8_tasks table
$task_id = intval($_POST['post_id'] ?? 0);
$type = sanitize_text_field($_POST['type'] ?? 'featured');
$device = sanitize_text_field($_POST['device'] ?? '');
$index = intval($_POST['index'] ?? 1);
if (!$task_id) {
wp_send_json_error(['message' => 'Invalid task ID']);
}
// Get WordPress post ID from task
global $wpdb;
$task = $wpdb->get_row($wpdb->prepare(
"SELECT id, assigned_post_id, title FROM {$wpdb->prefix}igny8_tasks WHERE id = %d",
$task_id
));
if (!$task || !$task->assigned_post_id) {
wp_send_json_error(['message' => 'Task not found or no post assigned (Task ID: ' . $task_id . ')']);
}
$post_id = $task->assigned_post_id;
// Verify WordPress post exists
$post = get_post($post_id);
if (!$post) {
wp_send_json_error(['message' => 'WordPress post not found (Post ID: ' . $post_id . ', Task ID: ' . $task_id . ')']);
}
error_log('Igny8: IMAGE_GEN_SINGLE - Processing Task ID: ' . $task_id . ' -> WordPress Post ID: ' . $post_id . ' (' . $post->post_title . ')');
// Generate image based on type
if ($type === 'featured') {
$result = igny8_generate_featured_image_for_post($post_id);
} else {
$result = igny8_generate_single_article_image($post_id, $device, $index);
}
if ($result['success']) {
// For in-article images, add to meta box
if ($type !== 'featured') {
$image_label = sanitize_text_field($_POST['image_label'] ?? '');
$device = sanitize_text_field($_POST['device'] ?? 'desktop');
$section = isset($_POST['section']) ? intval($_POST['section']) : null;
if (!empty($image_label)) {
igny8_add_inarticle_image_meta($post_id, $result['attachment_id'], $image_label, $device, $section);
}
}
wp_send_json_success([
'message' => 'Image generated successfully',
'attachment_id' => $result['attachment_id'],
'image_url' => $result['image_url'],
'provider' => $result['provider']
]);
} else {
wp_send_json_error([
'message' => $result['error'] ?? 'Failed to generate image'
]);
}
}
/**
* Create sample sectors for testing (development only)
*/
add_action('wp_ajax_igny8_create_sample_sectors', 'igny8_ajax_create_sample_sectors');
function igny8_ajax_create_sample_sectors() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_planner_settings')) {
wp_send_json_error(['message' => 'Security check failed']);
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
// Only create if no sectors exist
$existing_sectors = get_terms([
'taxonomy' => 'sectors',
'hide_empty' => false
]);
if (!empty($existing_sectors) && !is_wp_error($existing_sectors)) {
wp_send_json_error(['message' => 'Sectors already exist']);
}
// Create parent sectors
$parent_sectors = [
'Technology' => [
'Software Development',
'Artificial Intelligence',
'Cybersecurity',
'Cloud Computing'
],
'Healthcare' => [
'Medical Devices',
'Pharmaceuticals',
'Telemedicine',
'Mental Health'
],
'Finance' => [
'Banking',
'Insurance',
'Investment',
'Fintech'
],
'Education' => [
'Online Learning',
'Educational Technology',
'Professional Development',
'Research'
]
];
$created_count = 0;
$errors = [];
foreach ($parent_sectors as $parent_name => $children) {
// Create parent sector
$parent_result = wp_insert_term($parent_name, 'sectors');
if (is_wp_error($parent_result)) {
$errors[] = "Failed to create parent sector: {$parent_name}";
continue;
}
$parent_id = $parent_result['term_id'];
$created_count++;
// Create child sectors
foreach ($children as $child_name) {
$child_result = wp_insert_term($child_name, 'sectors', [
'parent' => $parent_id
]);
if (is_wp_error($child_result)) {
$errors[] = "Failed to create child sector: {$child_name}";
} else {
$created_count++;
}
}
}
if ($created_count > 0) {
wp_send_json_success([
'message' => "Created {$created_count} sectors successfully",
'created_count' => $created_count,
'errors' => $errors
]);
} else {
wp_send_json_error([
'message' => 'Failed to create any sectors',
'errors' => $errors
]);
}
}
/**
* AJAX handler for saving AI prompts
*/
add_action('wp_ajax_igny8_save_ai_prompts', 'igny8_ajax_save_ai_prompts');
function igny8_ajax_save_ai_prompts() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_planner_settings')) {
wp_send_json_error(['message' => 'Security check failed']);
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
// Get and sanitize prompt data
$clustering_prompt = sanitize_textarea_field($_POST['igny8_clustering_prompt'] ?? '');
$ideas_prompt = sanitize_textarea_field($_POST['igny8_ideas_prompt'] ?? '');
// Save prompts using AI settings system
igny8_update_ai_setting('clustering_prompt', $clustering_prompt);
igny8_update_ai_setting('ideas_prompt', $ideas_prompt);
wp_send_json_success(['message' => 'AI prompts saved successfully']);
}
/**
* AJAX handler for resetting AI prompts to defaults
*/
add_action('wp_ajax_igny8_reset_ai_prompts', 'igny8_ajax_reset_ai_prompts');
function igny8_ajax_reset_ai_prompts() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_planner_settings')) {
wp_send_json_error(['message' => 'Security check failed']);
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
// Reset prompts to defaults
igny8_update_ai_setting('clustering_prompt', igny8_get_default_clustering_prompt());
igny8_update_ai_setting('ideas_prompt', igny8_get_default_ideas_prompt());
wp_send_json_success(['message' => 'AI prompts reset to defaults successfully']);
}
// ===================================================================
// IMPORT/EXPORT AJAX HANDLERS
// ===================================================================
/**
* Download CSV template
*/
add_action('wp_ajax_igny8_download_template', 'igny8_ajax_download_template');
function igny8_ajax_download_template() {
// Verify nonce - check both POST and GET
$nonce = $_POST['nonce'] ?? $_GET['nonce'] ?? '';
$nonce_valid = wp_verify_nonce($nonce, 'igny8_import_export_nonce') ||
wp_verify_nonce($nonce, 'igny8_ajax_nonce') ||
wp_verify_nonce($nonce, 'igny8_admin_nonce');
if (!$nonce_valid) {
wp_die('Security check failed - Invalid nonce');
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_die('Insufficient permissions');
}
$template_type = sanitize_text_field($_POST['template_type'] ?? $_GET['template_type'] ?? '');
if (empty($template_type)) {
wp_die('Template type is required');
}
$plugin_root = plugin_dir_path(dirname(dirname(__FILE__)));
$template_path = $plugin_root . "assets/templates/igny8_{$template_type}_template.csv";
if (!file_exists($template_path)) {
wp_die('Template file not found: ' . $template_path);
}
// Set headers for file download
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="igny8_' . $template_type . '_template.csv"');
header('Pragma: no-cache');
header('Expires: 0');
// Output file content
readfile($template_path);
exit;
}
/**
* Run CSV import
*/
add_action('wp_ajax_igny8_run_import', 'igny8_ajax_run_import');
function igny8_ajax_run_import() {
// Debug logging
error_log('Igny8 Import Debug - POST data: ' . print_r($_POST, true));
error_log('Igny8 Import Debug - Nonce received: ' . ($_POST['nonce'] ?? 'NOT SET'));
// Verify nonce - check both possible nonce actions
$nonce_valid = false;
if (isset($_POST['nonce'])) {
$nonce_import_export = wp_verify_nonce($_POST['nonce'], 'igny8_import_export_nonce');
$nonce_ajax = wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce');
$nonce_valid = $nonce_import_export || $nonce_ajax;
error_log('Igny8 Import Debug - Import/Export nonce valid: ' . ($nonce_import_export ? 'YES' : 'NO'));
error_log('Igny8 Import Debug - AJAX nonce valid: ' . ($nonce_ajax ? 'YES' : 'NO'));
error_log('Igny8 Import Debug - Overall valid: ' . ($nonce_valid ? 'YES' : 'NO'));
}
if (!$nonce_valid) {
wp_send_json_error(['message' => 'Security check failed - Invalid nonce. Received: ' . ($_POST['nonce'] ?? 'NOT SET')]);
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$import_type = sanitize_text_field($_POST['import_type'] ?? '');
$allowed_types = ['keywords', 'clusters', 'ideas', 'mapping', 'tasks', 'templates', 'audits', 'suggestions', 'backlinks', 'campaigns', 'rewrites', 'tones', 'personalization_data', 'variations'];
if (!in_array($import_type, $allowed_types)) {
wp_send_json_error(['message' => 'Invalid import type']);
}
// Handle file upload
if (!isset($_FILES['import_file']) || $_FILES['import_file']['error'] !== UPLOAD_ERR_OK) {
wp_send_json_error(['message' => 'No file uploaded or upload error']);
}
$uploaded_file = $_FILES['import_file'];
$file_extension = strtolower(pathinfo($uploaded_file['name'], PATHINFO_EXTENSION));
if ($file_extension !== 'csv') {
wp_send_json_error(['message' => 'Only CSV files are allowed']);
}
// Read and parse CSV
$csv_data = [];
$handle = fopen($uploaded_file['tmp_name'], 'r');
if ($handle === false) {
wp_send_json_error(['message' => 'Could not read uploaded file']);
}
// Read headers
$headers = fgetcsv($handle);
if ($headers === false) {
fclose($handle);
wp_send_json_error(['message' => 'Invalid CSV format - no headers found']);
}
// Read data rows
while (($row = fgetcsv($handle)) !== false) {
if (count($row) === count($headers)) {
$csv_data[] = array_combine($headers, $row);
}
}
fclose($handle);
if (empty($csv_data)) {
wp_send_json_error(['message' => 'No data rows found in CSV']);
}
// Import data based on type
$result = igny8_import_data($import_type, $csv_data, $headers);
// Log import activity
igny8_log_import_export('import', $import_type, $result['success'], $result['message'], $result['details']);
if ($result['success']) {
wp_send_json_success($result);
} else {
wp_send_json_error($result);
}
}
/**
* Run CSV export
*/
add_action('wp_ajax_igny8_run_export', 'igny8_ajax_run_export');
function igny8_ajax_run_export() {
// Verify nonce - check both possible nonce actions
$nonce_valid = false;
if (isset($_POST['nonce'])) {
$nonce_valid = wp_verify_nonce($_POST['nonce'], 'igny8_import_export_nonce') ||
wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce');
}
if (!$nonce_valid) {
wp_send_json_error(['message' => 'Security check failed - Invalid nonce']);
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$export_type = sanitize_text_field($_POST['export_type'] ?? '');
$include_metrics = isset($_POST['include_metrics']) && $_POST['include_metrics'] === 'on';
$include_relationships = isset($_POST['include_relationships']) && $_POST['include_relationships'] === 'on';
$include_timestamps = isset($_POST['include_timestamps']) && $_POST['include_timestamps'] === 'on';
$allowed_types = ['keywords', 'clusters', 'ideas', 'mapping', 'tasks', 'templates', 'audits', 'suggestions', 'backlinks', 'campaigns', 'rewrites', 'tones', 'personalization_data', 'variations'];
if (!in_array($export_type, $allowed_types)) {
wp_send_json_error(['message' => 'Invalid export type']);
}
// Generate CSV data
$csv_data = igny8_export_data($export_type, [
'include_metrics' => $include_metrics,
'include_relationships' => $include_relationships,
'include_timestamps' => $include_timestamps
]);
if (!$csv_data || !is_array($csv_data)) {
wp_send_json_error(['message' => 'Export failed - No data returned from export function']);
}
// Handle empty results
if ($csv_data['count'] == 0) {
wp_send_json_error(['message' => 'No records found to export']);
}
// Generate filename
$date = date('Y-m-d_H-i-s');
$filename = "igny8_export_{$export_type}_{$date}.csv";
// Log export activity
igny8_log_import_export('export', $export_type, true, 'Export completed successfully', "Records exported: {$csv_data['count']}");
// Set headers for file download
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Pragma: no-cache');
header('Expires: 0');
// Output CSV content directly
echo $csv_data['content'];
exit;
}
/**
* Save import/export settings
*/
add_action('wp_ajax_igny8_save_import_export_settings', 'igny8_ajax_save_import_export_settings');
function igny8_ajax_save_import_export_settings() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_import_export_nonce')) {
wp_send_json_error(['message' => 'Security check failed']);
}
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
$settings = [
'default_format' => sanitize_text_field($_POST['default_format'] ?? 'csv'),
'overwrite_existing' => isset($_POST['overwrite_existing']) && $_POST['overwrite_existing'] === 'on',
'include_metrics' => isset($_POST['include_metrics']) && $_POST['include_metrics'] === 'on',
'file_naming_pattern' => sanitize_text_field($_POST['file_naming_pattern'] ?? 'igny8_export_[date].csv')
];
update_option('igny8_import_export_settings', $settings);
wp_send_json_success(['message' => 'Import/Export settings saved successfully']);
}
/**
* Import data helper function
*/
function igny8_import_data($type, $data, $headers) {
global $wpdb;
$imported = 0;
$skipped = 0;
$errors = [];
switch ($type) {
case 'keywords':
$table = $wpdb->prefix . 'igny8_keywords';
$required_fields = ['keyword'];
break;
case 'clusters':
$table = $wpdb->prefix . 'igny8_clusters';
$required_fields = ['cluster_name'];
break;
case 'ideas':
$table = $wpdb->prefix . 'igny8_content_ideas';
$required_fields = ['idea_title'];
break;
case 'mapping':
$table = $wpdb->prefix . 'igny8_mapping';
$required_fields = ['source_id', 'target_id'];
break;
case 'tasks':
$table = $wpdb->prefix . 'igny8_tasks';
$required_fields = ['title'];
break;
case 'templates':
$table = $wpdb->prefix . 'igny8_templates';
$required_fields = ['template_name'];
break;
case 'audits':
$table = $wpdb->prefix . 'igny8_audits';
$required_fields = ['page_id'];
break;
case 'suggestions':
$table = $wpdb->prefix . 'igny8_suggestions';
$required_fields = ['audit_id'];
break;
case 'backlinks':
$table = $wpdb->prefix . 'igny8_backlinks';
$required_fields = ['source_url', 'target_url'];
break;
case 'campaigns':
$table = $wpdb->prefix . 'igny8_campaigns';
$required_fields = ['campaign_name'];
break;
case 'rewrites':
$table = $wpdb->prefix . 'igny8_rewrites';
$required_fields = ['post_id'];
break;
case 'tones':
$table = $wpdb->prefix . 'igny8_tones';
$required_fields = ['tone_name'];
break;
case 'personalization_data':
$table = $wpdb->prefix . 'igny8_personalization_data';
$required_fields = ['data_key'];
break;
case 'variations':
$table = $wpdb->prefix . 'igny8_variations';
$required_fields = ['post_id', 'field_name'];
break;
default:
return ['success' => false, 'message' => 'Invalid import type'];
}
foreach ($data as $row) {
// Validate required fields
$missing_fields = [];
foreach ($required_fields as $field) {
if (empty($row[$field])) {
$missing_fields[] = $field;
}
}
if (!empty($missing_fields)) {
$skipped++;
$errors[] = "Row skipped: Missing required fields: " . implode(', ', $missing_fields);
continue;
}
// Prepare data for insertion
$insert_data = [];
$format = [];
foreach ($headers as $header) {
if (isset($row[$header]) && $row[$header] !== '') {
$insert_data[$header] = $row[$header];
// Determine format based on field type
if (in_array($header, ['search_volume', 'difficulty', 'cpc', 'keyword_count', 'total_volume', 'avg_difficulty', 'mapped_pages_count', 'estimated_word_count', 'source_id', 'target_id', 'sector_id', 'cluster_id'])) {
$format[] = '%d';
} else {
$format[] = '%s';
}
}
}
// Add default fields
if (!isset($insert_data['status'])) {
$insert_data['status'] = 'active';
$format[] = '%s';
}
if (!isset($insert_data['created_at'])) {
$insert_data['created_at'] = current_time('mysql');
$format[] = '%s';
}
// Insert into database
$result = $wpdb->insert($table, $insert_data, $format);
if ($result === false) {
$skipped++;
$errors[] = "Failed to insert row: " . $wpdb->last_error;
} else {
$imported++;
// Trigger cluster_added action for imported clusters to create taxonomy terms
if ($type === 'clusters') {
$cluster_id = $wpdb->insert_id;
do_action('igny8_cluster_added', $cluster_id);
}
}
}
$message = "Import completed. Imported: {$imported}, Skipped: {$skipped}";
$details = !empty($errors) ? implode('; ', array_slice($errors, 0, 5)) : '';
return [
'success' => true,
'message' => $message,
'details' => $details,
'imported' => $imported,
'skipped' => $skipped,
'errors' => $errors
];
}
/**
* Export data helper function
*/
function igny8_export_data($type, $options = []) {
global $wpdb;
switch ($type) {
case 'keywords':
$table = $wpdb->prefix . 'igny8_keywords';
$columns = ['keyword', 'search_volume', 'difficulty', 'cpc', 'intent', 'status', 'sector_id', 'cluster_id'];
break;
case 'clusters':
$table = $wpdb->prefix . 'igny8_clusters';
$columns = ['cluster_name', 'sector_id', 'status', 'keyword_count', 'total_volume', 'avg_difficulty', 'mapped_pages_count'];
break;
case 'ideas':
$table = $wpdb->prefix . 'igny8_content_ideas';
$columns = ['idea_title', 'idea_description', 'content_structure', 'content_type', 'keyword_cluster_id', 'status', 'estimated_word_count'];
break;
case 'mapping':
$table = $wpdb->prefix . 'igny8_mapping';
$columns = ['source_type', 'source_id', 'target_type', 'target_id', 'relevance_score'];
break;
case 'tasks':
$table = $wpdb->prefix . 'igny8_tasks';
$columns = ['title', 'description', 'content_type', 'cluster_id', 'priority', 'status', 'keywords', 'schedule_at', 'assigned_post_id'];
break;
case 'templates':
$table = $wpdb->prefix . 'igny8_templates';
$columns = ['template_name', 'prompt_type', 'system_prompt', 'user_prompt', 'is_active'];
break;
case 'audits':
$table = $wpdb->prefix . 'igny8_audits';
$columns = ['page_id', 'audit_status', 'seo_score', 'issues_found', 'recommendations'];
break;
case 'suggestions':
$table = $wpdb->prefix . 'igny8_suggestions';
$columns = ['audit_id', 'suggestion_type', 'priority', 'status', 'impact_level'];
break;
case 'backlinks':
$table = $wpdb->prefix . 'igny8_backlinks';
$columns = ['source_url', 'target_url', 'anchor_text', 'domain_authority', 'link_type', 'status'];
break;
case 'campaigns':
$table = $wpdb->prefix . 'igny8_campaigns';
$columns = ['campaign_name', 'target_url', 'status', 'backlink_count', 'live_links_count'];
break;
case 'rewrites':
$table = $wpdb->prefix . 'igny8_rewrites';
$columns = ['post_id', 'tone_id', 'variation_content', 'created_at'];
break;
case 'tones':
$table = $wpdb->prefix . 'igny8_tones';
$columns = ['tone_name', 'tone_type', 'description', 'status', 'usage_count'];
break;
case 'personalization_data':
$table = $wpdb->prefix . 'igny8_personalization_data';
$columns = ['data_key', 'data_value', 'data_type', 'created_at'];
break;
case 'variations':
$table = $wpdb->prefix . 'igny8_variations';
$columns = ['post_id', 'field_name', 'variation_content', 'tone_id'];
break;
default:
return false;
}
// Add optional columns
if ($options['include_timestamps']) {
$columns[] = 'created_at';
$columns[] = 'updated_at';
}
// Build query with optional filters
$query = "SELECT " . implode(', ', $columns) . " FROM {$table}";
$query_params = [];
// Add WHERE clause for selected IDs if provided
if (!empty($options['selected_ids'])) {
$placeholders = implode(',', array_fill(0, count($options['selected_ids']), '%d'));
$query .= " WHERE id IN ({$placeholders})";
$query_params = $options['selected_ids'];
} else {
$query_params = [];
}
// Debug logging
error_log('Igny8 Export Debug - Table: ' . $table);
error_log('Igny8 Export Debug - Query: ' . $query);
error_log('Igny8 Export Debug - Query params: ' . print_r($query_params, true));
error_log('Igny8 Export Debug - Selected IDs: ' . print_r($options['selected_ids'] ?? 'NOT SET', true));
// Check if table exists
$table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$table}'");
error_log('Igny8 Export Debug - Table exists: ' . ($table_exists ? 'YES' : 'NO'));
if (!$table_exists) {
error_log('Igny8 Export Debug - Table does not exist: ' . $table);
return [
'content' => '',
'count' => 0,
'message' => 'Table does not exist: ' . $table
];
}
// Check total records in table
$total_records = $wpdb->get_var("SELECT COUNT(*) FROM {$table}");
error_log('Igny8 Export Debug - Total records in table: ' . $total_records);
// Check if columns exist in table
$existing_columns = $wpdb->get_col("SHOW COLUMNS FROM {$table}");
error_log('Igny8 Export Debug - Existing columns: ' . print_r($existing_columns, true));
// Filter out non-existent columns
$valid_columns = array_intersect($columns, $existing_columns);
$invalid_columns = array_diff($columns, $existing_columns);
if (!empty($invalid_columns)) {
error_log('Igny8 Export Debug - Invalid columns removed: ' . print_r($invalid_columns, true));
}
if (empty($valid_columns)) {
error_log('Igny8 Export Debug - No valid columns found for export');
return [
'content' => '',
'count' => 0,
'message' => 'No valid columns found for export'
];
}
// Rebuild query with valid columns only
$query = "SELECT " . implode(', ', $valid_columns) . " FROM {$table}";
// Re-add WHERE clause for selected IDs if provided (after column validation)
if (!empty($options['selected_ids'])) {
$placeholders = implode(',', array_fill(0, count($options['selected_ids']), '%d'));
$query .= " WHERE id IN ({$placeholders})";
$query_params = $options['selected_ids'];
} else {
$query_params = [];
}
if (!empty($query_params)) {
$results = $wpdb->get_results($wpdb->prepare($query, $query_params), ARRAY_A);
} else {
$results = $wpdb->get_results($query, ARRAY_A);
}
// Check for SQL errors
if ($wpdb->last_error) {
error_log('Igny8 Export Debug - SQL Error: ' . $wpdb->last_error);
return [
'content' => '',
'count' => 0,
'message' => 'SQL Error: ' . $wpdb->last_error
];
}
error_log('Igny8 Export Debug - Results count: ' . count($results));
error_log('Igny8 Export Debug - Results: ' . print_r($results, true));
if (empty($results)) {
return [
'content' => '',
'count' => 0
];
}
// Generate CSV content
$output = fopen('php://temp', 'r+');
// Write headers
fputcsv($output, $columns);
// Write data
foreach ($results as $row) {
fputcsv($output, $row);
}
rewind($output);
$csv_content = stream_get_contents($output);
fclose($output);
return [
'content' => $csv_content,
'count' => count($results)
];
}
/**
* Log import/export activity
*/
function igny8_log_import_export($operation, $type, $success, $message, $details = '') {
$logs = get_option('igny8_import_export_logs', []);
$log_entry = [
'timestamp' => current_time('mysql'),
'operation' => ucfirst($operation) . ' ' . ucfirst($type),
'status' => $success ? 'success' : 'error',
'message' => $message,
'details' => $details
];
// Add to beginning of array (newest first)
array_unshift($logs, $log_entry);
// Keep only last 50 entries
$logs = array_slice($logs, 0, 50);
update_option('igny8_import_export_logs', $logs);
}
/**
* AJAX handler for exporting selected records
*/
function igny8_ajax_export_selected() {
// Debug logging
error_log('Igny8 Export Selected Debug - POST data: ' . print_r($_POST, true));
// Verify nonce - check both possible nonce actions
$nonce_valid = false;
if (isset($_POST['nonce'])) {
$nonce_import_export = wp_verify_nonce($_POST['nonce'], 'igny8_import_export_nonce');
$nonce_ajax = wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce');
$nonce_valid = $nonce_import_export || $nonce_ajax;
error_log('Igny8 Export Selected Debug - Import/Export nonce valid: ' . ($nonce_import_export ? 'YES' : 'NO'));
error_log('Igny8 Export Selected Debug - AJAX nonce valid: ' . ($nonce_ajax ? 'YES' : 'NO'));
error_log('Igny8 Export Selected Debug - Overall valid: ' . ($nonce_valid ? 'YES' : 'NO'));
}
if (!$nonce_valid) {
wp_send_json_error('Security check failed - Invalid nonce. Received: ' . ($_POST['nonce'] ?? 'NOT SET'));
}
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
$export_type = sanitize_text_field($_POST['export_type'] ?? '');
$selected_ids = isset($_POST['selected_ids']) ? json_decode(stripslashes($_POST['selected_ids']), true) : [];
error_log('Igny8 Export Selected Debug - Raw selected_ids: ' . print_r($_POST['selected_ids'] ?? 'NOT SET', true));
error_log('Igny8 Export Selected Debug - Decoded selected_ids: ' . print_r($selected_ids, true));
if (empty($export_type)) {
wp_send_json_error('Export type is required');
}
// For export all, selected_ids can be empty
if (!empty($selected_ids) && !is_array($selected_ids)) {
wp_send_json_error('Invalid selected IDs format');
}
// Get export options
$options = [
'include_timestamps' => isset($_POST['include_timestamps']) && $_POST['include_timestamps'],
'include_relationships' => isset($_POST['include_relationships']) && $_POST['include_relationships'],
'include_metrics' => isset($_POST['include_metrics']) && $_POST['include_metrics']
];
// Add selected IDs filter
$options['selected_ids'] = array_map('intval', $selected_ids);
error_log('Igny8 Export Selected Debug - Export type: ' . $export_type);
error_log('Igny8 Export Selected Debug - Options: ' . print_r($options, true));
$result = igny8_export_data($export_type, $options);
error_log('Igny8 Export Selected Debug - Export result: ' . print_r($result, true));
if (!$result || !is_array($result)) {
wp_send_json_error('Export failed - No data returned from export function');
}
// Handle empty results
if ($result['count'] == 0) {
wp_send_json_error('No records found to export');
}
// Log the export
igny8_log_import_export('export_selected', $export_type, $result['success'], $result['message'], "Records exported: {$result['count']}");
wp_send_json_success([
'csv_content' => $result['content'],
'count' => $result['count'],
'filename' => $export_type . '_export_' . date('Y-m-d_H-i-s') . '.csv'
]);
}
add_action('wp_ajax_igny8_export_selected', 'igny8_ajax_export_selected');
/**
* AJAX handler for getting import modal HTML
*/
function igny8_ajax_get_import_modal() {
// Verify nonce - check both possible nonce actions
$nonce_valid = false;
if (isset($_POST['nonce'])) {
$nonce_valid = wp_verify_nonce($_POST['nonce'], 'igny8_import_export_nonce') ||
wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce');
}
if (!$nonce_valid) {
wp_send_json_error('Security check failed - Invalid nonce');
}
$table_id = sanitize_text_field($_POST['table_id'] ?? '');
if (empty($table_id)) {
wp_send_json_error('Table ID is required');
}
// Get configuration for this table
$config = igny8_get_import_export_config($table_id);
if (!$config) {
wp_send_json_error('Import/Export not available for this table');
}
// Generate modal HTML using PHP template
$modal_html = igny8_get_import_modal_html($table_id, $config);
wp_send_json_success($modal_html);
}
add_action('wp_ajax_igny8_get_import_modal', 'igny8_ajax_get_import_modal');
/**
* AJAX handler for getting export modal HTML
*/
function igny8_ajax_get_export_modal() {
// Verify nonce - check both possible nonce actions
$nonce_valid = false;
if (isset($_POST['nonce'])) {
$nonce_valid = wp_verify_nonce($_POST['nonce'], 'igny8_import_export_nonce') ||
wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce');
}
if (!$nonce_valid) {
wp_send_json_error('Security check failed - Invalid nonce');
}
$table_id = sanitize_text_field($_POST['table_id'] ?? '');
if (empty($table_id)) {
wp_send_json_error('Table ID is required');
}
$selected_ids = isset($_POST['selected_ids']) ? json_decode(stripslashes($_POST['selected_ids']), true) : [];
// Get configuration for this table
$config = igny8_get_import_export_config($table_id);
if (!$config) {
wp_send_json_error('Import/Export not available for this table');
}
error_log('Igny8 Export Modal Debug - Table ID: ' . $table_id);
error_log('Igny8 Export Modal Debug - Config: ' . print_r($config, true));
error_log('Igny8 Export Modal Debug - Selected IDs: ' . print_r($selected_ids, true));
// Generate modal HTML using PHP template
$modal_html = igny8_get_export_modal_html($table_id, $config, $selected_ids);
wp_send_json_success($modal_html);
}
add_action('wp_ajax_igny8_get_export_modal', 'igny8_ajax_get_export_modal');
/**
* Get Import Modal HTML using PHP template
*/
function igny8_get_import_modal_html($table_id, $config) {
// Start output buffering
ob_start();
// Include the template file
define('IGNY8_INCLUDE_TEMPLATE', true);
include plugin_dir_path(dirname(__FILE__)) . '../modules/components/import-modal-tpl.php';
// Get the output and clean the buffer
$html = ob_get_clean();
return $html;
}
/**
* Get Export Modal HTML using PHP template
*/
function igny8_get_export_modal_html($table_id, $config, $selected_ids = []) {
// Start output buffering
ob_start();
// Include the template file
define('IGNY8_INCLUDE_TEMPLATE', true);
include plugin_dir_path(dirname(__FILE__)) . '../modules/components/export-modal-tpl.php';
// Get the output and clean the buffer
$html = ob_get_clean();
return $html;
}
/**
* API Logs - Get API request logs
*/
add_action('wp_ajax_igny8_get_api_logs', 'igny8_ajax_get_api_logs');
function igny8_ajax_get_api_logs() {
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
global $wpdb;
$logs = $wpdb->get_results("
SELECT * FROM {$wpdb->prefix}igny8_logs
WHERE source = 'openai_api'
ORDER BY created_at DESC
LIMIT 20
");
$html = '';
$total = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_logs WHERE source = 'openai_api'");
if ($logs) {
foreach ($logs as $log) {
$context = json_decode($log->context, true);
$status_class = $log->status === 'success' ? 'success' : 'error';
$status_icon = $log->status === 'success' ? '✅' : '❌';
$html .= "<tr>
<td>" . esc_html($log->created_at) . "</td>
<td><span class='igny8-status {$status_class}'>{$status_icon} " . esc_html($log->status) . "</span></td>
<td>" . esc_html($context['model'] ?? 'Unknown') . "</td>
<td>" . intval($context['input_tokens'] ?? 0) . " / " . intval($context['output_tokens'] ?? 0) . "</td>
<td>" . igny8_format_cost($context['total_cost'] ?? 0) . "</td>
<td>" . esc_html($log->api_id ? substr($log->api_id, 0, 12) . '...' : 'N/A') . "</td>
</tr>";
}
} else {
$html = '<tr><td colspan="6">No API logs found.</td></tr>';
}
wp_send_json_success(['html' => $html, 'total' => $total]);
}
/**
* API Logs - Clear API request logs
*/
add_action('wp_ajax_igny8_clear_api_logs', 'igny8_ajax_clear_api_logs');
function igny8_ajax_clear_api_logs() {
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
global $wpdb;
$result = $wpdb->delete(
$wpdb->prefix . 'igny8_logs',
['source' => 'openai_api'],
['%s']
);
if ($result !== false) {
wp_send_json_success(['message' => 'API logs cleared successfully']);
} else {
wp_send_json_error(['message' => 'Failed to clear API logs']);
}
}
/**
* Image Logs - Get image request logs
*/
add_action('wp_ajax_igny8_get_image_logs', 'igny8_ajax_get_image_logs');
function igny8_ajax_get_image_logs() {
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
global $wpdb;
$logs = $wpdb->get_results("
SELECT * FROM {$wpdb->prefix}igny8_logs
WHERE source = 'openai_image'
ORDER BY created_at DESC
LIMIT 20
");
$html = '';
$total = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_logs WHERE source = 'openai_image'");
if ($logs) {
foreach ($logs as $log) {
$context = json_decode($log->context, true);
$status_class = $log->status === 'success' ? 'success' : 'error';
$status_icon = $log->status === 'success' ? '✅' : '❌';
$html .= "<tr>
<td>" . esc_html($log->created_at) . "</td>
<td><span class='igny8-status {$status_class}'>{$status_icon} " . esc_html($log->status) . "</span></td>
<td>" . esc_html($context['model'] ?? 'dall-e-3') . "</td>
<td>" . intval($context['prompt_length'] ?? 0) . " chars</td>
<td>" . igny8_format_cost($context['total_cost'] ?? 0) . "</td>
<td>" . esc_html($context['image_size'] ?? '1024x1024') . "</td>
<td>" . esc_html($log->api_id ? substr($log->api_id, 0, 12) . '...' : 'N/A') . "</td>
</tr>";
}
} else {
$html = '<tr><td colspan="7">No image request logs found.</td></tr>';
}
wp_send_json_success(['html' => $html, 'total' => $total]);
}
/**
* Image Logs - Clear image request logs
*/
add_action('wp_ajax_igny8_clear_image_logs', 'igny8_ajax_clear_image_logs');
function igny8_ajax_clear_image_logs() {
// Check user permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Insufficient permissions']);
}
global $wpdb;
$result = $wpdb->delete(
$wpdb->prefix . 'igny8_logs',
['source' => 'openai_image'],
['%s']
);
if ($result !== false) {
wp_send_json_success(['message' => 'Image logs cleared successfully']);
} else {
wp_send_json_error(['message' => 'Failed to clear image logs']);
}
}
// ===================================================================
// CRON HEALTH AJAX HANDLERS
// ===================================================================
/**
* Manual Cron Run AJAX handler
*/
add_action('wp_ajax_igny8_cron_manual_run', 'igny8_ajax_cron_manual_run');
function igny8_ajax_cron_manual_run() {
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
$hook = sanitize_text_field($_POST['hook'] ?? '');
// Load master dispatcher functions
if (!function_exists('igny8_get_defined_cron_jobs')) {
include_once plugin_dir_path(__FILE__) . '../cron/igny8-cron-master-dispatcher.php';
}
$defined_jobs = igny8_get_defined_cron_jobs();
if (!isset($defined_jobs[$hook])) {
wp_send_json_error('Invalid cron job');
}
// Run the cron job manually with timing
$start_time = microtime(true);
do_action($hook);
$execution_time = microtime(true) - $start_time;
// Update health status
$current_time = current_time('timestamp');
$job_health = [
'last_run' => $current_time,
'success' => true,
'execution_time' => round($execution_time, 2),
'error_message' => '',
'next_run' => igny8_calculate_next_run_time($current_time, 'daily', 0)
];
update_option('igny8_cron_health_' . $hook, $job_health);
// Update last run in settings
$cron_settings = get_option('igny8_cron_settings', []);
$cron_settings[$hook]['last_run'] = $current_time;
update_option('igny8_cron_settings', $cron_settings);
wp_send_json_success([
'message' => "Cron job $hook executed successfully in " . round($execution_time, 2) . "s",
'execution_time' => round($execution_time, 2)
]);
}
/**
* Clear Cron Locks AJAX handler
*/
add_action('wp_ajax_igny8_clear_cron_locks', 'igny8_ajax_clear_cron_locks');
function igny8_ajax_clear_cron_locks() {
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
if (!wp_verify_nonce($_POST['nonce'], 'igny8_clear_locks')) {
wp_send_json_error('Invalid nonce');
}
// Clear all execution locks
global $wpdb;
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_igny8_%_processing'");
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_igny8_%_processing'");
wp_send_json_success(['message' => 'All execution locks cleared successfully']);
}
/**
* Test Runware API Connection AJAX handler
*/
function igny8_ajax_test_runware_connection() {
// Security checks
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
if (!wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce')) {
wp_send_json_error('Security check failed');
}
// Get Runware API key
$api_key = get_option('igny8_runware_api_key', '');
if (empty($api_key)) {
wp_send_json_error(['message' => 'Missing API key.']);
}
// Prepare payload as specified
$payload = [
[
'taskType' => 'authentication',
'apiKey' => $api_key
],
[
'taskType' => 'imageInference',
'taskUUID' => wp_generate_uuid4(),
'positivePrompt' => 'test image connection',
'model' => 'runware:97@1',
'width' => 128,
'height' => 128,
'negativePrompt' => 'text, watermark, logo, overlay, title, caption, writing on walls, writing on objects, UI, infographic elements, post title',
'steps' => 2,
'CFGScale' => 5,
'numberResults' => 1
]
];
// Make API request
$response = wp_remote_post('https://api.runware.ai/v1', [
'headers' => ['Content-Type' => 'application/json'],
'body' => json_encode($payload),
'timeout' => 30
]);
if (is_wp_error($response)) {
wp_send_json_error(['message' => $response->get_error_message()]);
}
$body = json_decode(wp_remote_retrieve_body($response), true);
if (isset($body['data'][0]['imageURL'])) {
wp_send_json_success(['message' => '✅ Runware API connected successfully!']);
} elseif (isset($body['errors'][0]['message'])) {
wp_send_json_error(['message' => '❌ ' . $body['errors'][0]['message']]);
} else {
wp_send_json_error(['message' => '❌ Unknown response from Runware.']);
}
}
/**
* Save Image Generation Settings - AJAX handler for saving image generation settings
*/
add_action('wp_ajax_igny8_save_image_settings', 'igny8_ajax_save_image_settings');
function igny8_ajax_save_image_settings() {
// Debug: Log all received data
error_log('Image settings AJAX called with data: ' . print_r($_POST, true));
// Verify nonce
if (!isset($_POST['generate_image_nonce'])) {
error_log('Image settings: Missing generate_image_nonce');
wp_send_json_error('Missing security token');
return;
}
if (!wp_verify_nonce($_POST['generate_image_nonce'], 'generate_image')) {
error_log('Image settings: Nonce verification failed');
wp_send_json_error('Security check failed');
return;
}
// Check user capabilities
if (!current_user_can('manage_options')) {
error_log('Image settings: Insufficient permissions');
wp_send_json_error('Insufficient permissions');
return;
}
// Get and sanitize form data
$image_type = sanitize_text_field($_POST['image_type'] ?? 'realistic');
$image_provider = sanitize_text_field($_POST['image_provider'] ?? 'openai');
$desktop_enabled = sanitize_text_field($_POST['desktop_enabled'] ?? '0');
$mobile_enabled = sanitize_text_field($_POST['mobile_enabled'] ?? '0');
$max_in_article_images = intval($_POST['max_in_article_images'] ?? 1);
$image_format = sanitize_text_field($_POST['image_format'] ?? 'jpg');
$negative_prompt = sanitize_textarea_field($_POST['negative_prompt'] ?? '');
error_log('Image settings: Saving data - Type: ' . $image_type . ', Provider: ' . $image_provider . ', Desktop: ' . $desktop_enabled . ', Mobile: ' . $mobile_enabled . ', Max In-Article: ' . $max_in_article_images . ', Format: ' . $image_format);
// Save image generation settings
update_option('igny8_image_type', $image_type);
update_option('igny8_image_service', $image_provider);
update_option('igny8_desktop_enabled', $desktop_enabled);
update_option('igny8_mobile_enabled', $mobile_enabled);
update_option('igny8_max_in_article_images', $max_in_article_images);
update_option('igny8_image_format', $image_format);
update_option('igny8_negative_prompt', $negative_prompt);
error_log('Image settings: Successfully saved all options');
wp_send_json_success('Image settings saved successfully');
}
/**
* Save Image Prompt Template - AJAX handler for saving image prompt templates
*/
add_action('wp_ajax_igny8_save_image_prompt_template', 'igny8_ajax_save_image_prompt_template');
function igny8_ajax_save_image_prompt_template() {
// Debug: Log all received data
error_log('Prompt template AJAX called with data: ' . print_r($_POST, true));
// Verify nonce
if (!isset($_POST['save_prompt_nonce'])) {
error_log('Prompt template: Missing save_prompt_nonce');
wp_send_json_error('Missing security token');
return;
}
if (!wp_verify_nonce($_POST['save_prompt_nonce'], 'save_prompt')) {
error_log('Prompt template: Nonce verification failed');
wp_send_json_error('Security check failed');
return;
}
// Check user capabilities
if (!current_user_can('manage_options')) {
error_log('Prompt template: Insufficient permissions');
wp_send_json_error('Insufficient permissions');
return;
}
// Get and sanitize prompt template
$prompt_template = sanitize_textarea_field($_POST['prompt_template'] ?? '');
$negative_prompt = sanitize_textarea_field($_POST['negative_prompt'] ?? '');
if (empty($prompt_template)) {
error_log('Prompt template: Empty prompt template');
wp_send_json_error('Prompt template is required');
return;
}
error_log('Prompt template: Saving template: ' . $prompt_template);
error_log('Prompt template: Saving negative prompt: ' . $negative_prompt);
// Save image prompt template and negative prompt
update_option('igny8_image_prompt_template', $prompt_template);
update_option('igny8_negative_prompt', $negative_prompt);
error_log('Prompt template: Successfully saved');
wp_send_json_success('Image prompt template saved successfully');
}
add_action('wp_ajax_igny8_reset_image_prompt_template', 'igny8_ajax_reset_image_prompt_template');
function igny8_ajax_reset_image_prompt_template() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_thinker_settings')) {
wp_send_json_error('Security check failed');
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
// Default image prompt template
$default_value = 'Create a high-quality {image_type} image to use as a featured photo for a blog post titled "{post_title}". The image should visually represent the theme, mood, and subject implied by the image prompt: {image_prompt}. Focus on a realistic, well-composed scene that naturally communicates the topic without text or logos. Use balanced lighting, pleasing composition, and photographic detail suitable for lifestyle or editorial web content. Avoid adding any visible or readable text, brand names, or illustrative effects. **And make sure image is not blurry.**';
// Reset the prompt template
update_option('igny8_image_prompt_template', $default_value);
wp_send_json_success([
'message' => 'Image prompt template reset to default successfully',
'prompt_value' => $default_value
]);
}
// TEST AJAX handler to verify ajax.php is loaded
add_action('wp_ajax_igny8_test_ajax_connection', 'igny8_ajax_test_connection');
function igny8_ajax_test_connection() {
// Simple test to verify ajax.php is loaded and working
wp_send_json_success([
'message' => 'AJAX connection working! ajax.php is loaded.',
'timestamp' => current_time('mysql'),
'server_time' => date('Y-m-d H:i:s'),
'test_data' => 'This message came from core/admin/ajax.php'
]);
}
// Content parsing function (temporary testing code)
function igny8_convert_to_wp_blocks($content) {
// Sanitize: keep only block-level tags
$content = strip_tags($content, '<p><h1><h2><h3><h4><h5><h6><ul><ol><li><blockquote><table><thead><tbody><tr><td><th><strong><em><br>');
// Add newline after block-level tags to preserve structure for regex matching
$content = preg_replace('/(<\/(p|ul|ol|table|blockquote|h[1-6])>)/i', "$1\n", $content);
// Optional: trim inline whitespace only, preserve structural line breaks
$content = preg_replace('/>\s+</', '><', $content);
// Match all major blocks
preg_match_all('/(<h[1-6][^>]*>.*?<\/h[1-6]>|<p[^>]*>.*?<\/p>|<ul[^>]*>.*?<\/ul>|<ol[^>]*>.*?<\/ol>|<blockquote[^>]*>.*?<\/blockquote>|<table[^>]*>.*?<\/table>|\[.*?\])/is', $content, $matches);
$blocks = [];
$used_length = 0;
foreach ($matches[0] as $block) {
$used_length += strlen($block);
// Headings - FIXED: Always include level attribute
if (preg_match('/^<h([2])[^>]*>(.*?)<\/h\1>$/is', $block, $m)) {
$blocks[] = "<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">{$m[2]}</h2>\n<!-- /wp:heading -->";
} elseif (preg_match('/^<h([3-6])[^>]*>(.*?)<\/h\1>$/is', $block, $m)) {
$blocks[] = "<!-- wp:heading {\"level\":{$m[1]}} -->\n<h{$m[1]} class=\"wp-block-heading\">{$m[2]}</h{$m[1]}>\n<!-- /wp:heading -->";
}
// Paragraph
elseif (preg_match('/^<p[^>]*>(.*?)<\/p>$/is', $block, $m)) {
$blocks[] = "<!-- wp:paragraph -->\n<p>{$m[1]}</p>\n<!-- /wp:paragraph -->";
}
// Unordered list
elseif (preg_match('/^<ul[^>]*>(.*?)<\/ul>$/is', $block, $m)) {
preg_match_all('/<li[^>]*>(.*?)<\/li>/is', $m[1], $li_items);
$list_items = '';
foreach ($li_items[1] as $li) {
$list_items .= "<!-- wp:list-item -->\n<li>{$li}</li>\n<!-- /wp:list-item -->\n";
}
$blocks[] = "<!-- wp:list -->\n<ul class=\"wp-block-list\">\n{$list_items}</ul>\n<!-- /wp:list -->";
}
// Ordered list
elseif (preg_match('/^<ol[^>]*>(.*?)<\/ol>$/is', $block, $m)) {
preg_match_all('/<li[^>]*>(.*?)<\/li>/is', $m[1], $li_items);
$list_items = '';
foreach ($li_items[1] as $li) {
$list_items .= "<!-- wp:list-item -->\n<li>{$li}</li>\n<!-- /wp:list-item -->\n";
}
$blocks[] = "<!-- wp:list {\"ordered\":true} -->\n<ol class=\"wp-block-list\">\n{$list_items}</ol>\n<!-- /wp:list -->";
}
// Blockquote
elseif (preg_match('/^<blockquote[^>]*>(.*?)<\/blockquote>$/is', $block, $m)) {
$inner = trim(strip_tags($m[1], '<p><strong><em><br>'));
$blocks[] = "<!-- wp:quote -->\n<blockquote class=\"wp-block-quote\">\n<!-- wp:paragraph -->\n<p>{$inner}</p>\n<!-- /wp:paragraph -->\n</blockquote>\n<!-- /wp:quote -->";
}
// Table
elseif (preg_match('/^<table[^>]*>(.*?)<\/table>$/is', $block, $m)) {
$blocks[] = "<!-- wp:table -->\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\">{$m[1]}</table></figure>\n<!-- /wp:table -->";
}
// Shortcode
elseif (preg_match('/^\[(.*?)\]$/', $block, $m)) {
$blocks[] = "<!-- wp:shortcode -->\n[{$m[1]}]\n<!-- /wp:shortcode -->";
}
}
// Handle trailing leftover text
$remaining = trim(substr($content, $used_length));
if (!empty($remaining)) {
$remaining = strip_tags($remaining);
if ($remaining !== '') {
$blocks[] = "<!-- wp:paragraph -->\n<p>{$remaining}</p>\n<!-- /wp:paragraph -->";
}
}
return implode("\n\n", $blocks);
}