0) {
igny8_log_ai_event('AI Queue Processed', 'ai', 'queue_processing', 'success', 'AI queue tasks processed', "Tasks processed: $processed");
// Set global variables for detailed logging
$GLOBALS['igny8_cron_processed_count'] = $processed;
$GLOBALS['igny8_cron_result_details'] = "Processed {$processed} AI queue tasks";
echo "Igny8 AI QUEUE HANDLER: Set global variables - processed_count: $processed, result_details: Processed {$processed} AI queue tasks
";
} else {
igny8_log_ai_event('AI Queue Empty', 'ai', 'queue_processing', 'info', 'No tasks in queue', 'All tasks are already processed');
// Set global variables for detailed logging
$GLOBALS['igny8_cron_processed_count'] = 0;
$GLOBALS['igny8_cron_result_details'] = "No AI queue tasks to process";
}
} catch (Exception $e) {
igny8_log_ai_event('AI Queue Error', 'ai', 'queue_processing', 'error', 'AI queue processing failed', $e->getMessage());
// Set global variables for detailed logging (failure case)
$GLOBALS['igny8_cron_processed_count'] = 0;
$GLOBALS['igny8_cron_result_details'] = "FAILED: " . $e->getMessage();
} finally {
// Always release the lock
delete_transient($lock_key);
}
}
/**
* Auto Cluster Cron Handler
*
* Automatically clusters unmapped keywords with taxonomy safety.
*/
function igny8_auto_cluster_cron_handler() {
// Suppress PHP warnings for model rates in cron context
$old_error_reporting = error_reporting();
error_reporting($old_error_reporting & ~E_WARNING);
echo "
";
echo "Igny8 CRON HANDLER: auto_cluster started
";
error_log("Igny8 CRON HANDLER: auto_cluster started");
// Check if automation is enabled via cron settings
echo "Igny8 CRON HANDLER: Checking if automation is enabled
";
$cron_settings = get_option('igny8_cron_settings', []);
$job_settings = $cron_settings['igny8_auto_cluster_cron'] ?? [];
$auto_cluster_enabled = $job_settings['enabled'] ?? false;
echo "Igny8 CRON HANDLER: auto_cluster_enabled = " . ($auto_cluster_enabled ? 'enabled' : 'disabled') . "
";
if (!$auto_cluster_enabled) {
echo "Igny8 CRON HANDLER: Automation disabled, exiting
";
error_log("Igny8 CRON HANDLER: auto_cluster automation disabled");
echo "
";
return;
}
echo "Igny8 CRON HANDLER: Automation enabled, continuing
";
// Check if AI mode is enabled
echo "Igny8 CRON HANDLER: Checking AI mode
";
$planner_mode = igny8_get_ai_setting('planner_mode', 'manual');
echo "Igny8 CRON HANDLER: planner_mode = " . $planner_mode . "
";
if ($planner_mode !== 'ai') {
echo "Igny8 CRON HANDLER: AI mode disabled, exiting
";
error_log("Igny8 CRON HANDLER: AI mode disabled");
echo "";
return;
}
echo "Igny8 CRON HANDLER: AI mode enabled, continuing
";
// Check if sector is selected
echo "Igny8 CRON HANDLER: Checking sector options
";
// Check if function exists first
if (!function_exists('igny8_get_sector_options')) {
echo "Igny8 CRON HANDLER: ERROR - igny8_get_sector_options function not found
";
igny8_log_ai_event('Auto Cluster Failed', 'planner', 'auto_cluster', 'error', 'Sector options function not available', 'Function igny8_get_sector_options not found');
echo "Igny8 CRON HANDLER: Exiting due to missing function
";
echo "";
return;
}
try {
echo "Igny8 CRON HANDLER: Calling igny8_get_sector_options()
";
$sector_options = igny8_get_sector_options();
echo "Igny8 CRON HANDLER: sector_options count = " . count($sector_options) . "
";
echo "Igny8 CRON HANDLER: sector_options content: " . print_r($sector_options, true) . "
";
} catch (Exception $e) {
echo "Igny8 CRON HANDLER: ERROR - Exception in igny8_get_sector_options: " . $e->getMessage() . "
";
igny8_log_ai_event('Auto Cluster Failed', 'planner', 'auto_cluster', 'error', 'Exception in sector options function', $e->getMessage());
echo "Igny8 CRON HANDLER: Exiting due to sector options exception
";
echo "";
return;
}
if (empty($sector_options)) {
echo "Igny8 CRON HANDLER: No sector selected, checking alternatives
";
echo "Igny8 CRON HANDLER: Checking if igny8_get_sector_options function exists: " . (function_exists('igny8_get_sector_options') ? 'YES' : 'NO') . "
";
// Try to get sectors directly from database
global $wpdb;
$sectors = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}igny8_sectors WHERE status = 'active'");
echo "Igny8 CRON HANDLER: Direct DB query found " . count($sectors) . " active sectors
";
if (empty($sectors)) {
echo "Igny8 CRON HANDLER: ERROR - No active sectors found, cannot proceed
";
igny8_log_ai_event('Auto Cluster Failed', 'planner', 'auto_cluster', 'error', 'No active sectors configured', 'Sector configuration required for clustering');
echo "Igny8 CRON HANDLER: Exiting due to missing sectors
";
echo "";
return;
} else {
echo "Igny8 CRON HANDLER: Found sectors in DB, using them
";
$sector_options = $sectors;
}
}
echo "Igny8 CRON HANDLER: Sector selected, continuing
";
global $wpdb;
echo "Igny8 CRON HANDLER: Database connection established
";
// Get unmapped keywords (use dynamic limit from settings)
$limit = $GLOBALS['igny8_cron_limit'] ?? null;
if ($limit === null) {
// Try to get limit from Smart Automation Jobs table directly
$cron_limits = get_option('igny8_cron_limits', []);
$limit = $cron_limits['igny8_auto_cluster_cron'] ?? null;
if ($limit === null) {
error_log('Igny8 Auto Cluster Cron: No limit set in Smart Automation Jobs table');
$GLOBALS['igny8_cron_processed_count'] = 0;
$GLOBALS['igny8_cron_result_details'] = "FAILED: No limit set in Smart Automation Jobs table";
return;
}
echo "Igny8 CRON HANDLER: Using limit from Smart Automation Jobs table: $limit
";
}
echo "Igny8 CRON HANDLER: Querying unmapped keywords (limit: $limit)
";
$query = "SELECT id, keyword FROM {$wpdb->prefix}igny8_keywords WHERE cluster_id IS NULL OR cluster_id = 0 LIMIT $limit";
echo "Igny8 CRON HANDLER: SQL Query: " . $query . "
";
$unmapped_keywords = $wpdb->get_results($query);
echo "Igny8 CRON HANDLER: Found " . count($unmapped_keywords) . " unmapped keywords
";
if (!empty($unmapped_keywords)) {
echo "Igny8 CRON HANDLER: Sample keywords:
";
foreach (array_slice($unmapped_keywords, 0, 3) as $keyword) {
echo "- ID: " . $keyword->id . ", Keyword: " . $keyword->keyword . "
";
}
}
if (empty($unmapped_keywords)) {
echo "Igny8 CRON HANDLER: ERROR - No unmapped keywords found
";
igny8_log_ai_event('Auto Cluster Failed', 'planner', 'auto_cluster', 'error', 'No unmapped keywords available for clustering', 'All keywords are already clustered or no keywords exist');
echo "Igny8 CRON HANDLER: Exiting due to no keywords to process
";
echo "";
return;
}
$keyword_ids = array_column($unmapped_keywords, 'id');
echo "Igny8 CRON HANDLER: Processing " . count($keyword_ids) . " keywords
";
// Log automation start
echo "Igny8 CRON HANDLER: Logging automation start
";
igny8_log_ai_event('Auto Cluster Started', 'planner', 'auto_cluster', 'info', 'Starting automated clustering', 'Keywords: ' . count($keyword_ids));
// Direct clustering without AJAX simulation
echo "Igny8 CRON HANDLER: Starting direct clustering process
";
// Set up user context for permissions
if (!current_user_can('manage_options')) {
// Get admin user for cron context
$admin_users = get_users(['role' => 'administrator', 'number' => 1]);
if (!empty($admin_users)) {
wp_set_current_user($admin_users[0]->ID);
echo "Igny8 CRON HANDLER: Set admin user context for permissions
";
}
}
// Get keywords data
echo "Igny8 CRON HANDLER: Getting keywords data
";
$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));
echo "Igny8 CRON HANDLER: Found " . count($keywords) . " keywords in database
";
if (empty($keywords)) {
echo "Igny8 CRON HANDLER: ERROR - No valid keywords found
";
igny8_log_ai_event('Auto Cluster Failed', 'planner', 'auto_cluster', 'error', 'No valid keywords found in database', 'Keyword IDs: ' . implode(',', $keyword_ids));
echo "";
return;
}
// Check if keywords already have clusters
$keywords_with_clusters = array_filter($keywords, function($keyword) {
return !empty($keyword->cluster_id) && $keyword->cluster_id > 0;
});
if (!empty($keywords_with_clusters)) {
echo "Igny8 CRON HANDLER: WARNING - Some keywords already have clusters
";
$keyword_names = array_column($keywords_with_clusters, 'keyword');
echo "Igny8 CRON HANDLER: Already clustered: " . implode(', ', array_slice($keyword_names, 0, 3)) . "
";
}
// Get clustering prompt
echo "Igny8 CRON HANDLER: Getting clustering prompt
";
$prompt_template = wp_unslash(igny8_get_ai_setting('clustering_prompt', igny8_get_default_clustering_prompt()));
echo "Igny8 CRON HANDLER: Prompt length: " . strlen($prompt_template) . "
";
// Generate session ID for progress tracking
$session_id = 'cron_clustering_' . time() . '_' . wp_generate_password(8, false);
echo "Igny8 CRON HANDLER: Session ID: " . $session_id . "
";
// Log AI request initiation
igny8_log_ai_event('Cron AI Request Initiated', 'planner', 'clustering', 'info', 'Starting cron AI clustering process', 'Keywords: ' . count($keyword_ids) . ', Session: ' . $session_id);
// Process with AI
echo "Igny8 CRON HANDLER: Calling AI processing
";
try {
$ai_result = igny8_process_ai_request('clustering', $keywords, $prompt_template);
echo "Igny8 CRON HANDLER: AI processing completed
";
if ($ai_result === false) {
echo "Igny8 CRON HANDLER: ERROR - AI processing returned false
";
igny8_log_ai_event('Cron AI Processing Failed', 'planner', 'clustering', 'error', 'AI processing returned false', 'Check OpenAI API configuration');
echo "";
return;
}
if (!is_array($ai_result) || !isset($ai_result['clusters'])) {
echo "Igny8 CRON HANDLER: ERROR - AI returned invalid result
";
igny8_log_ai_event('Cron AI Processing Failed', 'planner', 'clustering', 'error', 'AI returned invalid result', 'Result type: ' . gettype($ai_result));
echo "";
return;
}
echo "Igny8 CRON HANDLER: AI returned " . count($ai_result['clusters']) . " clusters
";
// Debug: Show AI response structure
echo "Igny8 CRON HANDLER: AI Response Debug:
";
echo "Igny8 CRON HANDLER: Full AI result structure: " . json_encode($ai_result, JSON_PRETTY_PRINT) . "
";
igny8_log_ai_event('Cron AI Processing Complete', 'planner', 'clustering', 'success', 'AI returned ' . count($ai_result['clusters']) . ' clusters', 'Clusters: ' . json_encode(array_column($ai_result['clusters'], 'name')));
// Log database operations start
echo "Igny8 CRON HANDLER: Starting database operations
";
igny8_log_ai_event('Cron 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 (same as AJAX handler)
echo "Igny8 CRON HANDLER: Getting sector options for assignment
";
$sector_options = igny8_get_sector_options();
$sector_count = count($sector_options);
echo "Igny8 CRON HANDLER: Found " . $sector_count . " sectors
";
// Create clusters in database (following AJAX handler process)
$created_clusters = [];
$clusters_created = 0;
$keywords_processed = 0;
foreach ($ai_result['clusters'] as $cluster_data) {
echo "Igny8 CRON HANDLER: Processing cluster: " . $cluster_data['name'] . "
";
try {
// Determine sector_id based on sector count (same logic as AJAX handler)
$sector_id = 1; // Default fallback
echo "Igny8 CRON HANDLER: Determining sector assignment
";
if ($sector_count == 1) {
// Only 1 sector: assign all clusters to that sector
$sector_id = $sector_options[0]['value'];
echo "Igny8 CRON HANDLER: Single sector found, assigning to sector ID: " . $sector_id . "
";
} elseif ($sector_count > 1) {
// Multiple sectors: use AI response sector assignment
if (isset($cluster_data['sector']) && !empty($cluster_data['sector'])) {
echo "Igny8 CRON HANDLER: AI provided sector: " . $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'];
echo "Igny8 CRON HANDLER: Matched sector ID: " . $sector_id . "
";
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'];
echo "Igny8 CRON HANDLER: No sector match, using first sector ID: " . $sector_id . "
";
}
}
// Create cluster record in database (same as AJAX handler)
echo "Igny8 CRON HANDLER: Creating cluster record in database
";
$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;
$clusters_created++;
echo "Igny8 CRON HANDLER: SUCCESS - Created cluster record with ID: " . $cluster_id . "
";
// Trigger taxonomy term creation for AI-generated cluster (same as AJAX handler)
echo "Igny8 CRON HANDLER: Triggering taxonomy term creation
";
try {
do_action('igny8_cluster_added', $cluster_id);
echo "Igny8 CRON HANDLER: SUCCESS - Taxonomy term creation triggered
";
igny8_log_ai_event('Cron Cluster Taxonomy Triggered', 'planner', 'clustering', 'info', 'Triggered igny8_cluster_added action', "Cluster: {$cluster_data['name']} (ID: {$cluster_id})");
} catch (Exception $e) {
echo "Igny8 CRON HANDLER: WARNING - Taxonomy term creation failed: " . $e->getMessage() . "
";
igny8_log_ai_event('Cron Cluster Taxonomy Failed', 'planner', 'clustering', 'warning', 'Taxonomy term creation failed', "Cluster: {$cluster_data['name']} (ID: {$cluster_id}), Error: " . $e->getMessage());
} catch (Throwable $e) {
echo "Igny8 CRON HANDLER: ERROR - Fatal error in taxonomy term creation: " . $e->getMessage() . "
";
igny8_log_ai_event('Cron Cluster Taxonomy Fatal Error', 'planner', 'clustering', 'error', 'Fatal error in taxonomy term creation', "Cluster: {$cluster_data['name']} (ID: {$cluster_id}), Error: " . $e->getMessage());
}
// Log cluster creation
igny8_log_ai_event('Cron Cluster Created', 'planner', 'clustering', 'success', 'Cluster created successfully', "Cluster: {$cluster_data['name']} (ID: {$cluster_id})");
// Update keywords with cluster_id (same as AJAX handler)
if (isset($cluster_data['keywords']) && is_array($cluster_data['keywords'])) {
echo "Igny8 CRON HANDLER: Processing " . count($cluster_data['keywords']) . " keywords for this cluster
";
foreach ($cluster_data['keywords'] as $keyword_name) {
echo "Igny8 CRON HANDLER: Looking for keyword: " . $keyword_name . "
";
$update_result = $wpdb->update(
$wpdb->prefix . 'igny8_keywords',
['cluster_id' => $cluster_id],
['keyword' => $keyword_name],
['%d'],
['%s']
);
if ($update_result !== false) {
$keywords_processed++;
echo "Igny8 CRON HANDLER: SUCCESS - Assigned keyword '" . $keyword_name . "' to cluster ID " . $cluster_id . "
";
} else {
echo "Igny8 CRON HANDLER: ERROR - Failed to update keyword '" . $keyword_name . "': " . $wpdb->last_error . "
";
}
}
// Log keyword updates
igny8_log_ai_event('Cron Keywords Updated', 'planner', 'clustering', 'success', 'Keywords assigned to cluster', "Cluster: {$cluster_data['name']}, Keywords: " . count($cluster_data['keywords']));
} else {
echo "Igny8 CRON HANDLER: WARNING - No keywords found in cluster data
";
}
// Update cluster metrics (same as AJAX handler)
echo "Igny8 CRON HANDLER: Updating cluster metrics
";
try {
$metrics_result = igny8_update_cluster_metrics($cluster_id);
if ($metrics_result) {
echo "Igny8 CRON HANDLER: SUCCESS - Cluster metrics updated
";
igny8_log_ai_event('Cron Metrics Updated', 'planner', 'clustering', 'success', 'Cluster metrics calculated', "Cluster: {$cluster_data['name']}");
} else {
echo "Igny8 CRON HANDLER: WARNING - Failed to update cluster metrics
";
igny8_log_ai_event('Cron Metrics Update Failed', 'planner', 'clustering', 'warning', 'Failed to update cluster metrics', "Cluster: {$cluster_data['name']}");
}
} catch (Exception $e) {
echo "Igny8 CRON HANDLER: ERROR - Exception during metrics update: " . $e->getMessage() . "
";
igny8_log_ai_event('Cron Metrics Update Error', 'planner', 'clustering', 'error', 'Exception during metrics update', "Cluster: {$cluster_data['name']}, Error: " . $e->getMessage());
}
} else {
// Log cluster creation failure
echo "Igny8 CRON HANDLER: ERROR - Failed to create cluster record: " . $wpdb->last_error . "
";
igny8_log_ai_event('Cron Cluster Creation Failed', 'planner', 'clustering', 'error', 'Failed to create cluster in database', "Cluster: {$cluster_data['name']}, Error: " . $wpdb->last_error);
}
} catch (Exception $e) {
echo "Igny8 CRON HANDLER: ERROR - Exception processing cluster '{$cluster_data['name']}': " . $e->getMessage() . "
";
igny8_log_ai_event('Cron Cluster Processing Error', 'planner', 'clustering', 'error', 'Exception processing cluster', "Cluster: {$cluster_data['name']}, Error: " . $e->getMessage());
echo "Igny8 CRON HANDLER: Continuing with next cluster
";
continue;
} catch (Throwable $e) {
echo "Igny8 CRON HANDLER: FATAL ERROR - Fatal error processing cluster '{$cluster_data['name']}': " . $e->getMessage() . "
";
igny8_log_ai_event('Cron Cluster Processing Fatal Error', 'planner', 'clustering', 'error', 'Fatal error processing cluster', "Cluster: {$cluster_data['name']}, Error: " . $e->getMessage());
echo "Igny8 CRON HANDLER: Continuing with next cluster
";
continue;
}
}
echo "Igny8 CRON HANDLER: Clustering completed successfully
";
echo "Igny8 CRON HANDLER: Database clusters created: " . $clusters_created . "
";
echo "Igny8 CRON HANDLER: Keywords processed: " . $keywords_processed . "
";
// Verify the results by checking database
echo "Igny8 CRON HANDLER: Verifying results in database
";
$verify_clusters = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_clusters WHERE status = 'active'");
$verify_keywords = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_keywords WHERE cluster_id IS NOT NULL AND cluster_id > 0");
$verify_taxonomy_terms = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id WHERE tt.taxonomy = 'clusters'");
echo "Igny8 CRON HANDLER: Total active clusters in database: " . $verify_clusters . "
";
echo "Igny8 CRON HANDLER: Total clustered keywords in database: " . $verify_keywords . "
";
echo "Igny8 CRON HANDLER: Total taxonomy terms for clusters: " . $verify_taxonomy_terms . "
";
// Log completion with verification
igny8_log_ai_event('Cron Auto Cluster Complete', 'planner', 'auto_cluster', 'success', 'Cron automated clustering completed', 'Database clusters: ' . $clusters_created . ', Keywords processed: ' . $keywords_processed . ', Verified clusters: ' . $verify_clusters . ', Verified keywords: ' . $verify_keywords . ', Verified taxonomy terms: ' . $verify_taxonomy_terms);
// Set global variables for detailed logging
$GLOBALS['igny8_cron_processed_count'] = $keywords_processed;
$GLOBALS['igny8_cron_result_details'] = "Processed {$keywords_processed} keywords, created {$clusters_created} clusters";
} catch (Exception $e) {
echo "Igny8 CRON HANDLER: ERROR - Exception during AI processing: " . $e->getMessage() . "
";
igny8_log_ai_event('Cron Auto Cluster Failed', 'planner', 'auto_cluster', 'error', 'Exception during AI processing', $e->getMessage());
// Set global variables for detailed logging (failure case)
$GLOBALS['igny8_cron_processed_count'] = 0;
$GLOBALS['igny8_cron_result_details'] = "FAILED: " . $e->getMessage();
echo "";
return;
}
echo "Igny8 CRON HANDLER: auto_cluster completed
";
echo ""; // Close the handler div
// Restore error reporting
error_reporting($old_error_reporting);
}
/**
* Auto Generate Ideas Cron Handler
*
* Automatically generates ideas from clusters without ideas.
*/
function igny8_auto_generate_ideas_cron_handler() {
// Suppress PHP warnings for model rates in cron context
$old_error_reporting = error_reporting();
error_reporting($old_error_reporting & ~E_WARNING);
echo "";
echo "Igny8 CRON HANDLER: auto_generate_ideas started
";
error_log("Igny8 CRON HANDLER: auto_generate_ideas started");
// Check if automation is enabled via cron settings
echo "Igny8 CRON HANDLER: Checking if automation is enabled
";
$cron_settings = get_option('igny8_cron_settings', []);
$job_settings = $cron_settings['igny8_auto_generate_ideas_cron'] ?? [];
$auto_ideas_enabled = $job_settings['enabled'] ?? false;
echo "Igny8 CRON HANDLER: auto_ideas_enabled = " . ($auto_ideas_enabled ? 'enabled' : 'disabled') . "
";
if (!$auto_ideas_enabled) {
echo "Igny8 CRON HANDLER: Automation disabled, exiting
";
error_log("Igny8 CRON HANDLER: auto_generate_ideas automation disabled");
echo "
";
return;
}
echo "Igny8 CRON HANDLER: Automation enabled, continuing
";
// Check if AI mode is enabled
echo "Igny8 CRON HANDLER: Checking AI mode
";
$planner_mode = igny8_get_ai_setting('planner_mode', 'manual');
echo "Igny8 CRON HANDLER: planner_mode = " . $planner_mode . "
";
if ($planner_mode !== 'ai') {
echo "Igny8 CRON HANDLER: AI mode disabled, exiting
";
error_log("Igny8 CRON HANDLER: AI mode disabled");
echo "";
return;
}
echo "Igny8 CRON HANDLER: AI mode enabled, continuing
";
global $wpdb;
echo "Igny8 CRON HANDLER: Database connection established
";
// Get clusters without ideas (use dynamic limit from settings)
$limit = $GLOBALS['igny8_cron_limit'] ?? null;
if ($limit === null) {
error_log('Igny8 Auto Cluster Cron: No limit set in Smart Automation Jobs table');
$GLOBALS['igny8_cron_processed_count'] = 0;
$GLOBALS['igny8_cron_result_details'] = "FAILED: No limit set in Smart Automation Jobs table";
return;
}
echo "Igny8 CRON HANDLER: Querying clusters without ideas (limit: $limit)
";
$clusters_without_ideas = $wpdb->get_results("
SELECT c.id FROM {$wpdb->prefix}igny8_clusters c
LEFT JOIN {$wpdb->prefix}igny8_content_ideas i ON c.id = i.keyword_cluster_id
WHERE i.id IS NULL
LIMIT $limit
");
echo "Igny8 CRON HANDLER: Found " . count($clusters_without_ideas) . " clusters without ideas
";
if (empty($clusters_without_ideas)) {
echo "Igny8 CRON HANDLER: No clusters without ideas found, exiting
";
igny8_log_ai_event('Auto Generate Ideas Skipped', 'planner', 'auto_generate_ideas', 'info', 'No clusters without ideas found', 'All clusters already have ideas');
echo "";
return;
}
$cluster_ids = array_column($clusters_without_ideas, 'id');
echo "Igny8 CRON HANDLER: Processing " . count($cluster_ids) . " clusters
";
// Log automation start
echo "Igny8 CRON HANDLER: Logging automation start
";
igny8_log_ai_event('Auto Generate Ideas Started', 'planner', 'auto_generate_ideas', 'info', 'Starting automated idea generation', 'Clusters: ' . count($cluster_ids));
// Set up user context for permissions
echo "Igny8 CRON HANDLER: Setting up user context
";
if (!current_user_can('manage_options')) {
// Get admin user for cron context
$admin_users = get_users(['role' => 'administrator', 'number' => 1]);
if (!empty($admin_users)) {
wp_set_current_user($admin_users[0]->ID);
echo "Igny8 CRON HANDLER: Set admin user context for permissions
";
}
}
// Simulate AJAX request to existing AI function
echo "Igny8 CRON HANDLER: Starting AJAX simulation
";
$_POST['cluster_ids'] = json_encode($cluster_ids);
$_POST['nonce'] = wp_create_nonce('igny8_planner_settings');
// Capture output to prevent JSON response in cron
ob_start();
igny8_ajax_ai_generate_ideas();
$output = ob_get_clean();
echo "Igny8 CRON HANDLER: AJAX execution completed
";
// Parse JSON response
$response = json_decode($output, true);
echo "Igny8 CRON HANDLER: Response parsed, success = " . ($response['success'] ?? 'false') . "
";
if ($response && $response['success']) {
$ideas_created = $response['data']['ideas_created'] ?? count($cluster_ids);
echo "Igny8 CRON HANDLER: SUCCESS - Ideas generated: " . $ideas_created . "
";
igny8_log_ai_event('Auto Generate Ideas Complete', 'planner', 'auto_generate_ideas', 'success', 'Automated idea generation completed', 'Ideas created: ' . $ideas_created);
// Set global variables for detailed logging
$GLOBALS['igny8_cron_processed_count'] = $ideas_created;
$GLOBALS['igny8_cron_result_details'] = "Processed {$ideas_created} clusters, created {$ideas_created} ideas";
} else {
$error_msg = $response['data']['message'] ?? 'Unknown error';
echo "Igny8 CRON HANDLER: ERROR - " . $error_msg . "
";
igny8_log_ai_event('Auto Generate Ideas Failed', 'planner', 'auto_generate_ideas', 'error', 'Automated idea generation failed', $error_msg);
// Set global variables for detailed logging (failure case)
$GLOBALS['igny8_cron_processed_count'] = 0;
$GLOBALS['igny8_cron_result_details'] = "FAILED: " . $error_msg;
}
echo "Igny8 CRON HANDLER: auto_generate_ideas completed
";
echo ""; // Close the handler div
// Restore error reporting
error_reporting($old_error_reporting);
}
/**
* Auto Queue Cron Handler
*
* Automatically queues new ideas for content generation.
*/
function igny8_auto_queue_cron_handler() {
// Suppress PHP warnings for model rates in cron context
$old_error_reporting = error_reporting();
error_reporting($old_error_reporting & ~E_WARNING);
echo "";
echo "Igny8 CRON HANDLER: auto_queue started
";
error_log("Igny8 CRON HANDLER: auto_queue started");
// Check if automation is enabled via cron settings
echo "Igny8 CRON HANDLER: Checking if automation is enabled
";
$cron_settings = get_option('igny8_cron_settings', []);
$job_settings = $cron_settings['igny8_auto_queue_cron'] ?? [];
$auto_queue_enabled = $job_settings['enabled'] ?? false;
echo "Igny8 CRON HANDLER: auto_queue_enabled = " . ($auto_queue_enabled ? 'enabled' : 'disabled') . "
";
if (!$auto_queue_enabled) {
echo "Igny8 CRON HANDLER: Automation disabled, exiting
";
error_log("Igny8 CRON HANDLER: auto_queue automation disabled");
echo "
";
return;
}
echo "Igny8 CRON HANDLER: Automation enabled, continuing
";
global $wpdb;
echo "Igny8 CRON HANDLER: Database connection established
";
// Get new ideas (use dynamic limit from settings)
$limit = $GLOBALS['igny8_cron_limit'] ?? null;
if ($limit === null) {
// Try to get limit from Smart Automation Jobs table directly
$cron_limits = get_option('igny8_cron_limits', []);
$limit = $cron_limits['igny8_auto_queue_cron'] ?? null;
if ($limit === null) {
error_log('Igny8 Auto Queue Cron: No limit set in Smart Automation Jobs table');
$GLOBALS['igny8_cron_processed_count'] = 0;
$GLOBALS['igny8_cron_result_details'] = "FAILED: No limit set in Smart Automation Jobs table";
return;
}
echo "Igny8 CRON HANDLER: Using limit from Smart Automation Jobs table: $limit
";
}
echo "Igny8 CRON HANDLER: Querying new ideas (limit: $limit)
";
$new_ideas = $wpdb->get_results("
SELECT id FROM {$wpdb->prefix}igny8_content_ideas
WHERE status = 'new'
LIMIT $limit
");
echo "Igny8 CRON HANDLER: Found " . count($new_ideas) . " new ideas
";
if (empty($new_ideas)) {
echo "Igny8 CRON HANDLER: No new ideas found, exiting
";
igny8_log_ai_event('Auto Queue Skipped', 'planner', 'auto_queue', 'info', 'No new ideas found', 'All ideas are already queued');
echo "";
return;
}
$idea_ids = array_column($new_ideas, 'id');
echo "Igny8 CRON HANDLER: Processing " . count($idea_ids) . " ideas
";
// Log automation start
echo "Igny8 CRON HANDLER: Logging automation start
";
igny8_log_ai_event('Auto Queue Started', 'planner', 'auto_queue', 'info', 'Starting automated queueing', 'Ideas: ' . count($idea_ids));
// Set up user context for permissions
echo "Igny8 CRON HANDLER: Setting up user context
";
if (!current_user_can('edit_posts')) {
// Get admin user for cron context
$admin_users = get_users(['role' => 'administrator', 'number' => 1]);
if (!empty($admin_users)) {
wp_set_current_user($admin_users[0]->ID);
echo "Igny8 CRON HANDLER: Set admin user context for permissions
";
}
}
// Skip AJAX simulation and go directly to function calls
// (AJAX has issues in cron context with nonce verification)
echo "Igny8 CRON HANDLER: Skipping AJAX simulation (cron context issues)
";
echo "Igny8 CRON HANDLER: Using direct function calls
";
$created = [];
$skipped = [];
$failed = [];
foreach ($idea_ids as $idea_id) {
echo "Igny8 CRON HANDLER: Processing idea ID: " . $idea_id . "
";
$result = igny8_create_task_from_idea($idea_id);
if ($result['success']) {
if (!empty($result['task_id'])) {
$created[] = $result['task_id'];
echo "Igny8 CRON HANDLER: SUCCESS - Created task ID: " . $result['task_id'] . "
";
} else {
$skipped[] = $idea_id;
echo "Igny8 CRON HANDLER: SKIPPED - " . $result['message'] . "
";
}
} else {
$failed[] = $idea_id;
echo "Igny8 CRON HANDLER: FAILED - " . $result['message'] . "
";
}
}
// Update metrics for processed ideas
foreach ($idea_ids as $idea_id) {
igny8_update_idea_metrics($idea_id);
}
$created_count = count($created);
$skipped_count = count($skipped);
$failed_count = count($failed);
echo "Igny8 CRON HANDLER: DIRECT SUCCESS - Tasks created: " . $created_count . ", Skipped: " . $skipped_count . ", Failed: " . $failed_count . "
";
igny8_log_ai_event('Auto Queue Complete', 'planner', 'auto_queue', 'success', 'Automated queueing completed (direct)', 'Tasks created: ' . $created_count . ', Skipped: ' . $skipped_count . ', Failed: ' . $failed_count);
// Set global variables for detailed logging
$GLOBALS['igny8_cron_processed_count'] = $created_count;
$GLOBALS['igny8_cron_result_details'] = "Processed {$created_count} ideas, created {$created_count} tasks";
echo "Igny8 CRON HANDLER: auto_queue completed
";
echo ""; // Close the handler div
// Restore error reporting
error_reporting($old_error_reporting);
}
/**
* Auto Generate Content Cron Handler
*
* Automatically generates content from queued tasks.
*/
function igny8_auto_generate_content_cron_handler() {
// Suppress PHP warnings for model rates in cron context
$old_error_reporting = error_reporting();
error_reporting($old_error_reporting & ~E_WARNING);
echo "";
echo "Igny8 CRON HANDLER: auto_generate_content started
";
error_log("Igny8 CRON HANDLER: auto_generate_content started");
// Check if automation is enabled via cron settings
echo "Igny8 CRON HANDLER: Checking if automation is enabled
";
$cron_settings = get_option('igny8_cron_settings', []);
$job_settings = $cron_settings['igny8_auto_generate_content_cron'] ?? [];
$auto_content_enabled = $job_settings['enabled'] ?? false;
echo "Igny8 CRON HANDLER: auto_content_enabled = " . ($auto_content_enabled ? 'enabled' : 'disabled') . "
";
if (!$auto_content_enabled) {
echo "Igny8 CRON HANDLER: Automation disabled, exiting
";
error_log("Igny8 CRON HANDLER: auto_generate_content automation disabled");
echo "
";
return;
}
echo "Igny8 CRON HANDLER: Automation enabled, continuing
";
// Check if AI mode is enabled
echo "Igny8 CRON HANDLER: Checking AI mode
";
$writer_mode = igny8_get_ai_setting('writer_mode', 'manual');
echo "Igny8 CRON HANDLER: writer_mode = " . $writer_mode . "
";
if ($writer_mode !== 'ai') {
echo "Igny8 CRON HANDLER: AI mode disabled, exiting
";
error_log("Igny8 CRON HANDLER: AI mode disabled");
echo "";
return;
}
echo "Igny8 CRON HANDLER: AI mode enabled, continuing
";
global $wpdb;
echo "Igny8 CRON HANDLER: Database connection established
";
// Get queued tasks (use dynamic limit from settings)
$limit = $GLOBALS['igny8_cron_limit'] ?? null;
if ($limit === null) {
// Try to get limit from Smart Automation Jobs table directly
$cron_limits = get_option('igny8_cron_limits', []);
$limit = $cron_limits['igny8_auto_generate_content_cron'] ?? null;
if ($limit === null) {
error_log('Igny8 Auto Generate Content Cron: No limit set in Smart Automation Jobs table');
$GLOBALS['igny8_cron_processed_count'] = 0;
$GLOBALS['igny8_cron_result_details'] = "FAILED: No limit set in Smart Automation Jobs table";
return;
}
echo "Igny8 CRON HANDLER: Using limit from Smart Automation Jobs table: $limit
";
}
echo "Igny8 CRON HANDLER: Querying queued tasks (limit: $limit)
";
$queued_tasks = $wpdb->get_results("
SELECT id FROM {$wpdb->prefix}igny8_tasks
WHERE status = 'queued'
LIMIT $limit
");
echo "Igny8 CRON HANDLER: Found " . count($queued_tasks) . " queued tasks
";
if (empty($queued_tasks)) {
echo "Igny8 CRON HANDLER: No queued tasks found, exiting
";
igny8_log_ai_event('Auto Generate Content Skipped', 'writer', 'auto_generate_content', 'info', 'No queued tasks found', 'All tasks are already processed');
echo "";
return;
}
$task_ids = array_column($queued_tasks, 'id');
echo "Igny8 CRON HANDLER: Processing " . count($task_ids) . " tasks
";
// Log automation start
echo "Igny8 CRON HANDLER: Logging automation start
";
igny8_log_ai_event('Auto Generate Content Started', 'writer', 'auto_generate_content', 'info', 'Starting automated content generation', 'Tasks: ' . count($task_ids));
// Set up user context for permissions
echo "Igny8 CRON HANDLER: Setting up user context
";
if (!current_user_can('manage_options')) {
// Get admin user for cron context
$admin_users = get_users(['role' => 'administrator', 'number' => 1]);
if (!empty($admin_users)) {
wp_set_current_user($admin_users[0]->ID);
echo "Igny8 CRON HANDLER: Set admin user context for permissions
";
}
}
$completed = 0;
$failed = 0;
foreach ($task_ids as $task_id) {
echo "📋 SECTION 1: PROCESSING TASK " . $task_id . "
";
echo "Igny8 CRON HANDLER: Processing task ID: " . $task_id . "
";
echo "Igny8 CRON HANDLER: Sending to AI for content generation...
";
// Log AI request initiation
igny8_log_ai_event('AI Request Initiated', 'writer', 'auto_content_generation', 'info', 'Starting AI content generation', 'Task ID: ' . $task_id);
// Get AI configuration for logging
$api_key = get_option('igny8_api_key');
$model = get_option('igny8_model', 'gpt-4.1');
// Log AI request details
echo "Igny8 CRON HANDLER: AI Request Details - Model: " . $model . "
";
echo "Igny8 CRON HANDLER: API Key Status: " . (empty($api_key) ? 'Missing' : 'Configured') . "
";
igny8_log_ai_event('AI Request Details', 'writer', 'auto_content_generation', 'info', 'AI request configuration', 'Model: ' . $model . ', API Key: ' . (empty($api_key) ? 'Missing' : 'Configured'));
// Log the actual AI request being made
echo "Igny8 CRON HANDLER: Making AI API call to OpenAI...
";
igny8_log_ai_event('AI API Call', 'writer', 'auto_content_generation', 'info', 'Calling OpenAI API', 'Model: ' . $model . ', Task: ' . $task_id);
// Simulate AJAX request to content generation function
$_POST['task_id'] = $task_id;
$_POST['nonce'] = wp_create_nonce('igny8_writer_settings');
// Capture the response from AI call
ob_start();
igny8_ajax_ai_generate_content();
$ai_response = ob_get_clean();
// Log AI response
echo "Igny8 CRON HANDLER: AI Response received
";
echo "Igny8 CRON HANDLER: Response Length: " . strlen($ai_response) . " characters
";
igny8_log_ai_event('AI Response Received', 'writer', 'auto_content_generation', 'info', 'AI processing completed', 'Response captured for task: ' . $task_id);
// Parse the AI response to extract success/error status
$json_response = null;
$response_status = 'unknown';
$error_message = '';
// Try multiple methods to extract JSON from response
if (preg_match('/\{.*\}/s', $ai_response, $matches)) {
$json_response = json_decode($matches[0], true);
}
// If no JSON found, try to detect response type from content
if (!$json_response) {
if (strpos($ai_response, 'success') !== false && strpos($ai_response, 'true') !== false) {
$response_status = 'success';
echo "Igny8 CRON HANDLER: ✅ AI Response - SUCCESS (detected from content)
";
igny8_log_ai_event('AI Response Success', 'writer', 'auto_content_generation', 'success', 'AI content generation successful', 'Task ID: ' . $task_id);
} elseif (strpos($ai_response, 'error') !== false || strpos($ai_response, 'failed') !== false) {
$response_status = 'error';
$error_message = 'Error detected in response content';
echo "Igny8 CRON HANDLER: ❌ AI Response - FAILED (detected from content)
";
echo "Igny8 CRON HANDLER: Error: " . $error_message . "
";
igny8_log_ai_event('AI Response Failed', 'writer', 'auto_content_generation', 'error', 'AI content generation failed', 'Task ID: ' . $task_id . ', Error: ' . $error_message);
} else {
$response_status = 'unknown';
echo "Igny8 CRON HANDLER: ⚠️ AI Response - UNKNOWN FORMAT
";
igny8_log_ai_event('AI Response Unknown', 'writer', 'auto_content_generation', 'warning', 'AI response format not recognized', 'Task ID: ' . $task_id . ', Raw response: ' . substr($ai_response, 0, 200));
}
} else {
// JSON response found, parse it
if (isset($json_response['success']) && $json_response['success']) {
$response_status = 'success';
echo "Igny8 CRON HANDLER: ✅ AI Response - SUCCESS (JSON parsed)
";
igny8_log_ai_event('AI Response Success', 'writer', 'auto_content_generation', 'success', 'AI content generation successful', 'Task ID: ' . $task_id);
} else {
$response_status = 'error';
$error_message = $json_response['data']['message'] ?? $json_response['message'] ?? 'Unknown error';
echo "Igny8 CRON HANDLER: ❌ AI Response - FAILED (JSON parsed)
";
echo "Igny8 CRON HANDLER: Error: " . $error_message . "
";
igny8_log_ai_event('AI Response Failed', 'writer', 'auto_content_generation', 'error', 'AI content generation failed', 'Task ID: ' . $task_id . ', Error: ' . $error_message);
}
}
// Log response details for debugging
echo "Igny8 CRON HANDLER: Response Status: " . $response_status . "
";
echo "Igny8 CRON HANDLER: Response Length: " . strlen($ai_response) . " characters
";
igny8_log_ai_event('AI Response Analysis', 'writer', 'auto_content_generation', 'info', 'Response analysis completed', 'Status: ' . $response_status . ', Length: ' . strlen($ai_response) . ', Task: ' . $task_id);
// Get the actual post ID from the most recently created post by this user
$recent_posts = get_posts([
'post_type' => 'post',
'post_status' => 'draft',
'author' => get_current_user_id(),
'numberposts' => 1,
'orderby' => 'date',
'order' => 'DESC'
]);
$actual_post_id = !empty($recent_posts) ? $recent_posts[0]->ID : null;
$response = ['success' => true, 'data' => ['post_id' => $actual_post_id]];
if ($response && $response['success']) {
echo "Igny8 CRON HANDLER: ✅ AI Response received successfully
";
echo "🔧 SECTION 2: PROCESSING AI RESPONSE & SAVING DATA
";
echo "Igny8 CRON HANDLER: Processing AI response data...
";
// The detailed field processing logs will come from igny8_create_post_from_ai_response function
// which already has all the debugging output we added
echo "✅ SECTION 3: FINAL VERIFICATION & SUCCESS SUMMARY
";
if (!empty($response['data']['post_id'])) {
$post_id = $response['data']['post_id'];
echo "Igny8 CRON HANDLER: ✅ Post Created Successfully - ID: " . $post_id . "
";
echo "Igny8 CRON HANDLER: Checking fields for post ID: " . $post_id . "
";
// Verify all saved fields
$meta_title = get_post_meta($post_id, '_igny8_meta_title', true);
$meta_description = get_post_meta($post_id, '_igny8_meta_description', true);
$primary_keywords = get_post_meta($post_id, '_igny8_primary_keywords', true);
$secondary_keywords = get_post_meta($post_id, '_igny8_secondary_keywords', true);
$word_count = get_post_meta($post_id, '_igny8_word_count', true);
echo "Igny8 CRON HANDLER: ✅ Meta Title: " . (!empty($meta_title) ? 'Saved' : 'Missing') . "
";
echo "Igny8 CRON HANDLER: ✅ Meta Description: " . (!empty($meta_description) ? 'Saved' : 'Missing') . "
";
echo "Igny8 CRON HANDLER: ✅ Primary Keywords: " . (!empty($primary_keywords) ? 'Saved' : 'Missing') . "
";
echo "Igny8 CRON HANDLER: ✅ Secondary Keywords: " . (!empty($secondary_keywords) ? 'Saved' : 'Missing') . "
";
echo "Igny8 CRON HANDLER: ✅ Word Count: " . (!empty($word_count) ? 'Saved' : 'Missing') . "
";
// Verify taxonomies
$cluster_terms = wp_get_object_terms($post_id, 'clusters');
$sector_terms = wp_get_object_terms($post_id, 'sectors');
echo "Igny8 CRON HANDLER: ✅ Cluster Taxonomy: " . (!empty($cluster_terms) && !is_wp_error($cluster_terms) ? 'Associated' : 'Not Associated') . "
";
echo "Igny8 CRON HANDLER: ✅ Sector Taxonomy: " . (!empty($sector_terms) && !is_wp_error($sector_terms) ? 'Associated' : 'Not Associated') . "
";
// Verify Yoast meta
$yoast_meta = get_post_meta($post_id, '_yoast_wpseo_metadesc', true);
echo "Igny8 CRON HANDLER: ✅ Yoast Meta Description: " . (!empty($yoast_meta) ? 'Saved' : 'Missing') . "
";
$completed++;
echo "🎉 TASK " . $task_id . " COMPLETED SUCCESSFULLY!
";
} else {
echo "❌ Post creation failed - No post ID returned
";
$failed++;
}
} else {
$failed++;
$error_msg = $response['data']['message'] ?? 'Unknown error';
echo "❌ TASK " . $task_id . " FAILED: " . $error_msg . "
";
}
echo "
";
}
echo "Igny8 CRON HANDLER: Content generation completed - Success: " . $completed . ", Failed: " . $failed . "
";
if ($completed > 0) {
igny8_log_ai_event('Auto Generate Content Complete', 'writer', 'auto_generate_content', 'success', 'Automated content generation completed', 'Content generated: ' . $completed . ', Failed: ' . $failed);
// Set global variables for detailed logging
$GLOBALS['igny8_cron_processed_count'] = $completed;
$GLOBALS['igny8_cron_result_details'] = "Processed {$completed} tasks, generated {$completed} content pieces";
echo "Igny8 AUTO GENERATE CONTENT HANDLER: Set global variables - processed_count: $completed, result_details: Processed {$completed} tasks, generated {$completed} content pieces
";
} else {
igny8_log_ai_event('Auto Generate Content Failed', 'writer', 'auto_generate_content', 'error', 'Automated content generation failed', 'No content was generated');
// Set global variables for detailed logging (failure case)
$GLOBALS['igny8_cron_processed_count'] = 0;
$GLOBALS['igny8_cron_result_details'] = "FAILED: No content was generated";
}
echo "Igny8 CRON HANDLER: auto_generate_content completed
";
echo ""; // Close the handler div
// Restore error reporting
error_reporting($old_error_reporting);
}
/**
* Auto Publish Drafts Cron Handler
*
* Automatically publishes completed draft posts.
*/
function igny8_auto_publish_drafts_cron_handler() {
echo "";
echo "Igny8 CRON HANDLER: auto_publish_drafts started
";
error_log("Igny8 CRON HANDLER: auto_publish_drafts started");
// Check if automation is enabled via cron settings
echo "Igny8 CRON HANDLER: Checking if automation is enabled
";
$cron_settings = get_option('igny8_cron_settings', []);
$job_settings = $cron_settings['igny8_auto_publish_drafts_cron'] ?? [];
$auto_publish_enabled = $job_settings['enabled'] ?? false;
echo "Igny8 CRON HANDLER: auto_publish_enabled = " . ($auto_publish_enabled ? 'enabled' : 'disabled') . "
";
if (!$auto_publish_enabled) {
echo "Igny8 CRON HANDLER: Automation disabled, exiting
";
error_log("Igny8 CRON HANDLER: auto_publish_drafts automation disabled");
echo "
";
return;
}
echo "Igny8 CRON HANDLER: Automation enabled, continuing
";
global $wpdb;
// Get completed tasks with published content (use admin limit)
$limit = $GLOBALS['igny8_cron_limit'] ?? null;
if ($limit === null) {
// Try to get limit from Smart Automation Jobs table directly
$cron_limits = get_option('igny8_cron_limits', []);
$limit = $cron_limits['igny8_auto_publish_drafts_cron'] ?? null;
if ($limit === null) {
error_log('Igny8 Auto Publish Drafts Cron: No limit set in Smart Automation Jobs table');
$GLOBALS['igny8_cron_processed_count'] = 0;
$GLOBALS['igny8_cron_result_details'] = "FAILED: No limit set in Smart Automation Jobs table";
return;
}
echo "Igny8 CRON HANDLER: Using limit from Smart Automation Jobs table: $limit
";
}
echo "Igny8 CRON HANDLER: Querying completed tasks with draft posts (limit: $limit)
";
$completed_tasks = $wpdb->get_results("
SELECT t.id, t.assigned_post_id FROM {$wpdb->prefix}igny8_tasks t
INNER JOIN {$wpdb->posts} p ON t.assigned_post_id = p.ID
WHERE t.status = 'completed'
AND p.post_status = 'draft'
LIMIT $limit
");
echo "Igny8 CRON HANDLER: Found " . count($completed_tasks) . " completed tasks with draft posts
";
if (empty($completed_tasks)) {
echo "Igny8 CRON HANDLER: No draft posts found, exiting
";
igny8_log_ai_event('Auto Publish Drafts Skipped', 'writer', 'auto_publish_drafts', 'info', 'No draft posts found', 'All content is already published');
echo "";
return;
}
$post_ids = array_column($completed_tasks, 'assigned_post_id');
// Log automation start
igny8_log_ai_event('Auto Publish Drafts Started', 'writer', 'auto_publish_drafts', 'info', 'Starting automated publishing', 'Posts: ' . count($post_ids));
$published = 0;
echo "Igny8 CRON HANDLER: Processing " . count($post_ids) . " draft posts
";
foreach ($post_ids as $post_id) {
echo "Igny8 CRON HANDLER: Publishing post ID: " . $post_id . "
";
$result = wp_update_post([
'ID' => $post_id,
'post_status' => 'publish'
]);
if (!is_wp_error($result) && $result) {
$published++;
echo "Igny8 CRON HANDLER: ✅ SUCCESS - Published post ID: " . $post_id . "
";
} else {
echo "Igny8 CRON HANDLER: ❌ FAILED - Could not publish post ID: " . $post_id . "
";
}
}
echo "Igny8 CRON HANDLER: Publishing completed - Published: " . $published . " posts
";
if ($published > 0) {
igny8_log_ai_event('Auto Publish Drafts Complete', 'writer', 'auto_publish_drafts', 'success', 'Automated publishing completed', 'Posts published: ' . $published);
// Set global variables for detailed logging
$GLOBALS['igny8_cron_processed_count'] = $published;
$GLOBALS['igny8_cron_result_details'] = "Processed {$published} drafts, published {$published} posts";
} else {
igny8_log_ai_event('Auto Publish Drafts Failed', 'writer', 'auto_publish_drafts', 'error', 'Automated publishing failed', 'No posts were published');
// Set global variables for detailed logging (failure case)
$GLOBALS['igny8_cron_processed_count'] = 0;
$GLOBALS['igny8_cron_result_details'] = "FAILED: No posts were published";
}
echo "Igny8 CRON HANDLER: auto_publish_drafts completed
";
echo ""; // Close the handler div
}
/**
* Auto Optimizer Cron Handler (NEW)
*
* Automatically optimizes content and keywords.
*/
function igny8_auto_optimizer_cron_handler() {
// Check if automation is enabled
if (igny8_get_ai_setting('auto_optimizer_enabled', 'disabled') !== 'enabled') {
return;
}
global $wpdb;
// Get posts that need optimization (use admin limit)
$limit = $GLOBALS['igny8_cron_limit'] ?? null;
if ($limit === null) {
// Try to get limit from Smart Automation Jobs table directly
$cron_limits = get_option('igny8_cron_limits', []);
$limit = $cron_limits['igny8_auto_optimizer_cron'] ?? null;
if ($limit === null) {
error_log('Igny8 Auto Optimizer Cron: No limit set in Smart Automation Jobs table');
$GLOBALS['igny8_cron_processed_count'] = 0;
$GLOBALS['igny8_cron_result_details'] = "FAILED: No limit set in Smart Automation Jobs table";
return;
}
echo "Igny8 CRON HANDLER: Using limit from Smart Automation Jobs table: $limit
";
}
$posts_to_optimize = $wpdb->get_results("
SELECT p.ID, p.post_title
FROM {$wpdb->posts} p
LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = 'igny8_optimized'
WHERE p.post_status = 'publish'
AND p.post_type IN ('post', 'page')
AND pm.meta_value IS NULL
LIMIT $limit
");
if (empty($posts_to_optimize)) {
igny8_log_ai_event('Auto Optimizer Skipped', 'optimizer', 'auto_optimizer', 'info', 'No posts need optimization', 'All posts are already optimized');
return;
}
$post_ids = array_column($posts_to_optimize, 'ID');
// Log automation start
igny8_log_ai_event('Auto Optimizer Started', 'optimizer', 'auto_optimizer', 'info', 'Starting automated optimization', 'Posts: ' . count($post_ids));
$optimized = 0;
foreach ($post_ids as $post_id) {
// Run optimization process
$result = igny8_optimize_post_content($post_id);
if ($result['success']) {
$optimized++;
// Mark as optimized
update_post_meta($post_id, 'igny8_optimized', current_time('mysql'));
}
}
if ($optimized > 0) {
igny8_log_ai_event('Auto Optimizer Complete', 'optimizer', 'auto_optimizer', 'success', 'Automated optimization completed', 'Posts optimized: ' . $optimized);
} else {
igny8_log_ai_event('Auto Optimizer Failed', 'optimizer', 'auto_optimizer', 'error', 'Automated optimization failed', 'No posts were optimized');
}
}
/**
* Auto Recalc Cron Handler (NEW)
*
* Automatically recalculates metrics for all entities.
*/
function igny8_auto_recalc_cron_handler() {
global $wpdb;
// Get entities that need recalculation
$limit = $GLOBALS['igny8_cron_limit'] ?? null;
if ($limit === null) {
error_log('Igny8 Auto Cluster Cron: No limit set in Smart Automation Jobs table');
$GLOBALS['igny8_cron_processed_count'] = 0;
$GLOBALS['igny8_cron_result_details'] = "FAILED: No limit set in Smart Automation Jobs table";
return;
}
$clusters_to_recalc = $wpdb->get_results("
SELECT id FROM {$wpdb->prefix}igny8_clusters
WHERE status = 'active'
LIMIT $limit
");
$tasks_to_recalc = $wpdb->get_results("
SELECT id FROM {$wpdb->prefix}igny8_tasks
WHERE status IN ('completed', 'in_progress')
LIMIT $limit
");
$total_recalc = count($clusters_to_recalc) + count($tasks_to_recalc);
if ($total_recalc === 0) {
igny8_log_ai_event('Auto Recalc Skipped', 'analytics', 'auto_recalc', 'info', 'No entities need recalculation', 'All metrics are up to date');
return;
}
// Log automation start
igny8_log_ai_event('Auto Recalc Started', 'analytics', 'auto_recalc', 'info', 'Starting automated recalculation', 'Entities: ' . $total_recalc);
$recalculated = 0;
// Recalculate cluster metrics
foreach ($clusters_to_recalc as $cluster) {
igny8_update_cluster_metrics($cluster->id);
$recalculated++;
}
// Recalculate task metrics
foreach ($tasks_to_recalc as $task) {
igny8_update_task_metrics($task->id);
$recalculated++;
}
if ($recalculated > 0) {
igny8_log_ai_event('Auto Recalc Complete', 'analytics', 'auto_recalc', 'success', 'Automated recalculation completed', 'Entities recalculated: ' . $recalculated);
} else {
igny8_log_ai_event('Auto Recalc Failed', 'analytics', 'auto_recalc', 'error', 'Automated recalculation failed', 'No entities were recalculated');
}
}
/**
* Health Check Cron Handler (NEW)
*
* Performs system health checks, cleanup, and dependency validation.
*/
function igny8_health_check_cron_handler() {
global $wpdb;
$health_issues = [];
$cleanup_performed = [];
$dependency_issues = [];
// Check for orphaned records
$orphaned_keywords = $wpdb->get_var("
SELECT COUNT(*) FROM {$wpdb->prefix}igny8_keywords k
LEFT JOIN {$wpdb->prefix}igny8_clusters c ON k.cluster_id = c.id
WHERE k.cluster_id IS NOT NULL AND k.cluster_id > 0 AND c.id IS NULL
");
if ($orphaned_keywords > 0) {
$health_issues[] = "Found $orphaned_keywords orphaned keywords";
// Clean up orphaned keywords
$wpdb->query("
UPDATE {$wpdb->prefix}igny8_keywords
SET cluster_id = NULL
WHERE cluster_id IS NOT NULL
AND cluster_id > 0
AND cluster_id NOT IN (SELECT id FROM {$wpdb->prefix}igny8_clusters)
");
$cleanup_performed[] = "Cleaned up $orphaned_keywords orphaned keywords";
}
// Check for old failed AI tasks
$old_failed_tasks = $wpdb->get_var("
SELECT COUNT(*) FROM {$wpdb->prefix}igny8_ai_queue
WHERE status = 'failed'
AND created_at < DATE_SUB(NOW(), INTERVAL 7 DAY)
");
if ($old_failed_tasks > 0) {
$wpdb->query("
DELETE FROM {$wpdb->prefix}igny8_ai_queue
WHERE status = 'failed'
AND created_at < DATE_SUB(NOW(), INTERVAL 7 DAY)
");
$cleanup_performed[] = "Cleaned up $old_failed_tasks old failed AI tasks";
}
// Dependency validation - check execution order
$dependency_issues = igny8_validate_automation_dependencies();
// Check database table sizes
$table_sizes = [];
$tables = ['igny8_keywords', 'igny8_clusters', 'igny8_content_ideas', 'igny8_tasks', 'igny8_ai_queue'];
foreach ($tables as $table) {
$count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}$table");
$table_sizes[$table] = $count;
}
// Log health check results
if (empty($health_issues) && empty($dependency_issues)) {
igny8_log_ai_event('Health Check Passed', 'system', 'health_check', 'success', 'System health check completed', 'No issues found');
} else {
$all_issues = array_merge($health_issues, $dependency_issues);
igny8_log_ai_event('Health Check Issues', 'system', 'health_check', 'warning', 'Health check found issues', implode('; ', $all_issues));
}
if (!empty($cleanup_performed)) {
igny8_log_ai_event('Health Check Cleanup', 'system', 'health_check', 'info', 'Cleanup performed', implode('; ', $cleanup_performed));
}
// Store health metrics
update_option('igny8_health_metrics', [
'last_check' => current_time('mysql'),
'table_sizes' => $table_sizes,
'issues_found' => count($health_issues),
'dependency_issues' => count($dependency_issues),
'cleanup_performed' => count($cleanup_performed)
]);
}
/**
* Validate automation dependencies and execution order
*/
function igny8_validate_automation_dependencies() {
$issues = [];
global $wpdb;
// Check if clusters exist but no ideas generated
$clusters_without_ideas = $wpdb->get_var("
SELECT COUNT(*) FROM {$wpdb->prefix}igny8_clusters c
LEFT JOIN {$wpdb->prefix}igny8_content_ideas i ON c.id = i.keyword_cluster_id
WHERE c.status = 'active' AND i.id IS NULL
");
if ($clusters_without_ideas > 0) {
$issues[] = "Found $clusters_without_ideas clusters without content ideas (auto_generate_ideas may be disabled)";
}
// Check if ideas exist but not queued
$ideas_not_queued = $wpdb->get_var("
SELECT COUNT(*) FROM {$wpdb->prefix}igny8_content_ideas i
LEFT JOIN {$wpdb->prefix}igny8_tasks t ON i.id = t.idea_id
WHERE i.status = 'new' AND t.id IS NULL
");
if ($ideas_not_queued > 0) {
$issues[] = "Found $ideas_not_queued ideas not queued for content generation (auto_queue may be disabled)";
}
// Check if tasks exist but no content generated
$tasks_without_content = $wpdb->get_var("
SELECT COUNT(*) FROM {$wpdb->prefix}igny8_tasks t
LEFT JOIN {$wpdb->posts} p ON t.assigned_post_id = p.ID
WHERE t.status = 'queued' AND (p.ID IS NULL OR p.post_content = '')
");
if ($tasks_without_content > 0) {
$issues[] = "Found $tasks_without_content tasks without content (auto_generate_content may be disabled)";
}
// Check if content exists but no images
$content_without_images = $wpdb->get_var("
SELECT COUNT(*) FROM {$wpdb->posts} p
LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = 'igny8_has_ai_image'
WHERE p.post_status = 'publish'
AND p.post_type IN ('post', 'page')
AND p.post_content LIKE '%[image%'
AND pm.meta_value IS NULL
");
if ($content_without_images > 0) {
$issues[] = "Found $content_without_images posts with image placeholders but no AI images (auto_generate_images may be disabled)";
}
// Check if content exists but not published
$drafts_not_published = $wpdb->get_var("
SELECT COUNT(*) FROM {$wpdb->posts} p
INNER JOIN {$wpdb->prefix}igny8_tasks t ON p.ID = t.assigned_post_id
WHERE p.post_status = 'draft'
AND t.status = 'completed'
");
if ($drafts_not_published > 0) {
$issues[] = "Found $drafts_not_published completed tasks with unpublished content (auto_publish_drafts may be disabled)";
}
return $issues;
}
/**
* Auto Generate Images Cron Handler (NEW)
*
* Automatically generates images for content that needs them.
*/
function igny8_auto_generate_images_cron_handler() {
// Check if automation is enabled
if (igny8_get_ai_setting('auto_generate_images_enabled', 'disabled') !== 'enabled') {
return;
}
// Check if AI mode is enabled
if (igny8_get_ai_setting('writer_mode', 'manual') !== 'ai') {
return;
}
global $wpdb;
// Get posts that need images (use admin limit)
$limit = $GLOBALS['igny8_cron_limit'] ?? null;
if ($limit === null) {
// Try to get limit from Smart Automation Jobs table directly
$cron_limits = get_option('igny8_cron_limits', []);
$limit = $cron_limits['igny8_auto_generate_images_cron'] ?? null;
if ($limit === null) {
error_log('Igny8 Auto Generate Images Cron: No limit set in Smart Automation Jobs table');
$GLOBALS['igny8_cron_processed_count'] = 0;
$GLOBALS['igny8_cron_result_details'] = "FAILED: No limit set in Smart Automation Jobs table";
return;
}
echo "Igny8 CRON HANDLER: Using limit from Smart Automation Jobs table: $limit
";
}
$posts_needing_images = $wpdb->get_results("
SELECT p.ID, p.post_title, p.post_content
FROM {$wpdb->posts} p
LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = 'igny8_has_ai_image'
WHERE p.post_status = 'publish'
AND p.post_type IN ('post', 'page')
AND pm.meta_value IS NULL
AND p.post_content LIKE '%[image%'
LIMIT $limit
");
if (empty($posts_needing_images)) {
igny8_log_ai_event('Auto Generate Images Skipped', 'writer', 'auto_generate_images', 'info', 'No posts need images', 'All posts already have images or no image placeholders found');
return;
}
$post_ids = array_column($posts_needing_images, 'ID');
// Log automation start
igny8_log_ai_event('Auto Generate Images Started', 'writer', 'auto_generate_images', 'info', 'Starting automated image generation', 'Posts: ' . count($post_ids));
$generated = 0;
foreach ($post_ids as $post_id) {
// Run image generation process
$result = igny8_generate_post_images($post_id);
if ($result['success']) {
$generated++;
// Mark as having AI image
update_post_meta($post_id, 'igny8_has_ai_image', current_time('mysql'));
}
}
if ($generated > 0) {
igny8_log_ai_event('Auto Generate Images Complete', 'writer', 'auto_generate_images', 'success', 'Automated image generation completed', 'Images generated: ' . $generated);
// Set global variables for detailed logging
$GLOBALS['igny8_cron_processed_count'] = $generated;
$GLOBALS['igny8_cron_result_details'] = "Processed {$generated} posts, generated {$generated} images";
} else {
igny8_log_ai_event('Auto Generate Images Failed', 'writer', 'auto_generate_images', 'error', 'Automated image generation failed', 'No images were generated');
// Set global variables for detailed logging (failure case)
$GLOBALS['igny8_cron_processed_count'] = 0;
$GLOBALS['igny8_cron_result_details'] = "FAILED: No images were generated";
}
}
/**
* Test Cron Endpoint Handler (NEW)
*
* Simple test handler for cron endpoint validation.
*/
function igny8_test_cron_endpoint() {
igny8_log_ai_event('Cron Test', 'system', 'test_endpoint', 'info', 'Cron endpoint test executed', 'Test successful at ' . current_time('mysql'));
// Return success response for external cron
if (defined('DOING_CRON') && DOING_CRON) {
return [
'success' => true,
'message' => 'Igny8 CRON endpoint is working',
'timestamp' => current_time('mysql'),
'server' => $_SERVER['SERVER_NAME'] ?? 'unknown'
];
}
}
/**
* Safe taxonomy term creation with duplicate prevention
*
* @param string $term_name Term name
* @param string $taxonomy Taxonomy name
* @param array $args Additional arguments
* @return int|WP_Error Term ID or error
*/
function igny8_safe_create_term($term_name, $taxonomy, $args = []) {
// Check if term already exists
$existing_term = get_term_by('name', $term_name, $taxonomy);
if ($existing_term) {
return $existing_term->term_id;
}
// Create term with duplicate slug prevention
$slug = sanitize_title($term_name);
$counter = 1;
$original_slug = $slug;
while (term_exists($slug, $taxonomy)) {
$slug = $original_slug . '-' . $counter;
$counter++;
}
$args['slug'] = $slug;
$result = wp_insert_term($term_name, $taxonomy, $args);
if (is_wp_error($result)) {
// If still error, try to get existing term by slug
$existing = get_term_by('slug', $slug, $taxonomy);
if ($existing) {
return $existing->term_id;
}
return $result;
}
return $result['term_id'];
}
/**
* Enhanced cluster term creation with safety checks
*
* @param int $cluster_id Cluster ID
* @return int|false Term ID or false on failure
*/
function igny8_safe_create_cluster_term($cluster_id) {
global $wpdb;
// Get cluster data
$cluster = $wpdb->get_row($wpdb->prepare(
"SELECT cluster_name, cluster_term_id FROM {$wpdb->prefix}igny8_clusters WHERE id = %d",
$cluster_id
));
if (!$cluster) {
return false;
}
// Skip if already mapped
if (!empty($cluster->cluster_term_id)) {
return $cluster->cluster_term_id;
}
// Ensure taxonomy exists
if (!taxonomy_exists('clusters') && function_exists('igny8_register_taxonomies')) {
igny8_register_taxonomies();
}
// Create term safely
$term_id = igny8_safe_create_term($cluster->cluster_name, 'clusters');
if ($term_id && !is_wp_error($term_id)) {
// Update cluster with term ID
$wpdb->update(
"{$wpdb->prefix}igny8_clusters",
['cluster_term_id' => $term_id],
['id' => $cluster_id],
['%d'],
['%d']
);
return $term_id;
}
return false;
}
// ===================================================================
// REGISTER CRON HOOKS
// ===================================================================
// Register all cron job hooks
add_action('igny8_auto_cluster_cron', 'igny8_auto_cluster_cron_handler');
add_action('igny8_auto_generate_ideas_cron', 'igny8_auto_generate_ideas_cron_handler');
add_action('igny8_auto_queue_cron', 'igny8_auto_queue_cron_handler');
add_action('igny8_auto_generate_content_cron', 'igny8_auto_generate_content_cron_handler');
add_action('igny8_auto_generate_images_cron', 'igny8_auto_generate_images_cron_handler');
add_action('igny8_auto_publish_drafts_cron', 'igny8_auto_publish_drafts_cron_handler');
add_action('igny8_process_ai_queue_cron', 'igny8_process_ai_queue_cron_handler');
add_action('igny8_auto_recalc_cron', 'igny8_auto_recalc_cron_handler');
add_action('igny8_auto_optimizer_cron', 'igny8_auto_optimizer_cron_handler');
add_action('igny8_health_check_cron', 'igny8_health_check_cron_handler');