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 "Igny8 CRON: Content generation completed for task " . $task_id . "
";
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 .= "
'); // 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]>| ]*>.*?<\/p>|
]*>.*?<\/ul>|
]*>.*?<\/ol>|
]*>.*?<\/blockquote>|]*>.*?<\/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\1>$/is', $block, $m)) { $blocks[] = "\n {$m[2]}
\n"; } elseif (preg_match('/^]*>(.*?)<\/h\1>$/is', $block, $m)) { $blocks[] = "\n {$m[2]} \n"; } // Paragraph elseif (preg_match('/^]*>(.*?)<\/p>$/is', $block, $m)) { $blocks[] = "\n
{$m[1]}
\n"; } // Unordered list elseif (preg_match('/^]*>(.*?)<\/ul>$/is', $block, $m)) { preg_match_all('/
- ]*>(.*?)<\/li>/is', $m[1], $li_items); $list_items = ''; foreach ($li_items[1] as $li) { $list_items .= "\n
- {$li}
\n\n"; } $blocks[] = "\n\n{$list_items}
\n"; } // Ordered list elseif (preg_match('/^]*>(.*?)<\/ol>$/is', $block, $m)) { preg_match_all('/
- ]*>(.*?)<\/li>/is', $m[1], $li_items); $list_items = ''; foreach ($li_items[1] as $li) { $list_items .= "\n
- {$li}
\n\n"; } $blocks[] = "\n\n{$list_items}
\n"; } // Blockquote elseif (preg_match('/^]*>(.*?)<\/blockquote>$/is', $block, $m)) { $inner = trim(strip_tags($m[1], '
')); $blocks[] = "\n\n\n\n"; } // Table elseif (preg_match('/^{$inner}
\n\n]*>(.*?)<\/table>$/is', $block, $m)) { $blocks[] = "\n
\n"; } // Shortcode elseif (preg_match('/^\[(.*?)\]$/', $block, $m)) { $blocks[] = "\n[{$m[1]}]\n"; } } // Handle trailing leftover text $remaining = trim(substr($content, $used_length)); if (!empty($remaining)) { $remaining = strip_tags($remaining); if ($remaining !== '') { $blocks[] = "\n{$m[1]}
{$remaining}
\n"; } } return implode("\n\n", $blocks); }