]*>(.*?)<\/p>$/i', $line, $matches)) { - $block_content[] = '' . $line . ''; - } elseif (preg_match('/^
]*>(.*?)<\/blockquote>$/i', $line, $matches)) { - $block_content[] = '' . $line . ''; - } elseif (preg_match('/^]*>(.*?)<\/table>$/i', $line, $matches)) { - $block_content[] = '' . $line . ''; - } elseif (preg_match('/^\[igny8-image[^\]]*\]/', $line)) { - // Handle shortcodes - wrap in shortcode block - $block_content[] = '' . $line . ''; - } else { - // For any other content, wrap as paragraph - $block_content[] = '
' . $line . '
'; - } - } - - return implode("\n", $block_content); -} - - diff --git a/igny8-wp-plugin-for-reference-olny/ai/openai-api.php b/igny8-wp-plugin-for-reference-olny/ai/openai-api.php deleted file mode 100644 index b215d326..00000000 --- a/igny8-wp-plugin-for-reference-olny/ai/openai-api.php +++ /dev/null @@ -1,1729 +0,0 @@ -prefix . 'igny8_logs'; - $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'"); - - if ($table_exists) { - // Get table structure to determine correct column names - $columns = $wpdb->get_results("SHOW COLUMNS FROM $table_name"); - $column_names = array_column($columns, 'Field'); - - // Prepare data based on actual table structure - $log_data = [ - 'timestamp' => $timestamp, - 'post_id' => get_queried_object_id(), - 'user_id' => get_current_user_id(), - 'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown' - ]; - - // Insert based on actual table structure - if (in_array('log_type', $column_names) && in_array('message', $column_names) && in_array('data', $column_names)) { - // New structure from install.php - $wpdb->insert( - $table_name, - [ - 'log_type' => 'field_detection', - 'message' => "[{$level}] {$message}", - 'data' => json_encode($log_data), - 'user_id' => get_current_user_id() - ], - ['%s', '%s', '%s', '%d'] - ); - } else { - // Fallback: just log to error log if table structure doesn't match - error_log("IGNY8: Logs table structure mismatch, skipping database log"); - } - } else { - error_log("IGNY8: Logs table does not exist, skipping database log"); - } -} - -/** - * Build combined content for personalization - */ -function igny8_build_combined_content($for_field_detection = false, $post_id = null) { - // Check if Content Engine is enabled and use Content Engine-specific settings - $content_engine_status = get_option('igny8_content_engine_global_status', 'enabled'); - - // Use provided post_id or fall back to queried object - if ($post_id === null) { - $post_id = get_queried_object_id(); - } - - $post_type = get_post_type($post_id); - $enabled_post_types = get_option('igny8_content_engine_enabled_post_types', []); - - if ($content_engine_status === 'enabled' && in_array($post_type, $enabled_post_types)) { - // Use Content Engine-specific settings - $include_context = get_option('igny8_content_engine_include_page_context', '0') === '1'; - $input_scope = get_option('igny8_content_engine_input_scope', '300'); - } else { - // Use global settings - $include_context = get_option('igny8_include_page_context', '0') === '1'; - $input_scope = get_option('igny8_input_scope', '300'); - } - - $final_content = ''; - - // β Use PageContent from form if available - if (!empty($_POST['PageContent'])) { - $final_content .= "[SOURCE:PageContent from form]\n\n"; - $final_content .= trim(sanitize_text_field($_POST['PageContent'])); - } else { - // β Fallback to raw post content or term description - $queried = get_post($post_id); - - if ($queried instanceof WP_Post) { - // π― Post/page/product β use post content with proper scope - $raw_content = get_post_field('post_content', $queried->ID); - if (!empty($raw_content)) { - $final_content .= "[SOURCE:Post Content]\n\n"; - - // Apply scope logic - only add dynamic messages for field detection - if ($for_field_detection) { - // Add dynamic messages for field detection - if ($input_scope === 'title') { - $final_content .= "Use this blog/page title to define the fields:\n\n"; - $final_content .= get_the_title($queried->ID); - } elseif ($input_scope === '300') { - $final_content .= "Use these 300 words to define the fields:\n\n"; - $final_content .= wp_trim_words(strip_tags($raw_content), 300, '...'); - } elseif ($input_scope === '600') { - $final_content .= "Use these 600 words to define the fields:\n\n"; - $final_content .= wp_trim_words(strip_tags($raw_content), 600, '...'); - } else { - $final_content .= "Use this whole content to define the fields:\n\n"; - $final_content .= strip_tags($raw_content); - } - } else { - // For content generation, just add content without dynamic messages - if ($input_scope === 'title') { - $final_content .= get_the_title($queried->ID); - } elseif ($input_scope === '300') { - $final_content .= wp_trim_words(strip_tags($raw_content), 300, '...'); - } elseif ($input_scope === '600') { - $final_content .= wp_trim_words(strip_tags($raw_content), 600, '...'); - } else { - $final_content .= strip_tags($raw_content); - } - } - } - - } elseif (isset($queried->description) && !empty($queried->description)) { - // π·οΈ Archive (term) β use term description - $final_content .= "[SOURCE:Term Description]\n\n"; - $final_content .= wp_trim_words(strip_tags($queried->description), 300, '...'); - } - } - - return trim($final_content) ?: 'No content available.'; -} - -/** - * Check content for moderation violations using OpenAI's moderation API - */ -function igny8_check_moderation($text, $api_key) { - $res = wp_remote_post('https://api.openai.com/v1/moderations', [ - 'headers' => [ - 'Authorization' => 'Bearer ' . $api_key, - 'Content-Type' => 'application/json', - ], - 'body' => json_encode(['input' => $text]), - 'timeout' => 20, - ]); - - if (is_wp_error($res)) { - return ['flagged' => false, 'error' => $res->get_error_message()]; - } - - $body = json_decode(wp_remote_retrieve_body($res), true); - return [ - 'flagged' => $body['results'][0]['flagged'] ?? false, - 'categories' => $body['results'][0]['categories'] ?? [], - ]; -} - -/** - * Test OpenAI API connection - */ -function igny8_test_connection($api_key, $with_response = false) { - // Get the current model setting - $model = get_option('igny8_model', 'gpt-4.1'); - - if ($with_response) { - // Test with actual API call - // Prepare request body with model-specific parameters - $request_body = [ - 'model' => $model, - 'messages' => [ - [ - 'role' => 'user', - 'content' => 'test ping, reply with: OK! Ping Received. Also tell me: what is your maximum token limit that I can use in 1 request?' - ] - ] - ]; - - // Model-specific parameters - $request_body['temperature'] = 0.7; - - // Log the complete request to file - $log_data = [ - 'timestamp' => current_time('mysql'), - 'model' => $model, - 'request_body' => $request_body, - 'headers' => [ - 'Authorization' => 'Bearer ' . substr($api_key, 0, 10) . '...', - 'Content-Type' => 'application/json' - ] - ]; - - $log_file = ABSPATH . 'igny8_api_request_log.json'; - file_put_contents($log_file, json_encode($log_data, JSON_PRETTY_PRINT)); - error_log("Igny8 Debug: Complete API request logged to: " . $log_file); - - $res = wp_remote_post('https://api.openai.com/v1/chat/completions', [ - 'headers' => [ - 'Authorization' => 'Bearer ' . $api_key, - 'Content-Type' => 'application/json', - ], - 'body' => json_encode($request_body), - 'timeout' => 15, - ]); - } else { - // Simple connection test without API call - $res = wp_remote_get('https://api.openai.com/v1/models', [ - 'headers' => [ - 'Authorization' => 'Bearer ' . $api_key, - ], - 'timeout' => 10, - ]); - } - - if (is_wp_error($res)) { - return $res->get_error_message(); - } - - $code = wp_remote_retrieve_response_code($res); - $body = wp_remote_retrieve_body($res); - - // Log the complete response to file - if ($with_response) { - $response_log_data = [ - 'timestamp' => current_time('mysql'), - 'response_code' => $code, - 'response_body' => json_decode($body, true), - 'raw_response' => $body - ]; - - $response_log_file = ABSPATH . 'igny8_api_response_log.json'; - file_put_contents($response_log_file, json_encode($response_log_data, JSON_PRETTY_PRINT)); - error_log("Igny8 Debug: Complete API response logged to: " . $response_log_file); - } - - if ($code >= 200 && $code < 300) { - if ($with_response) { - // Handle API response test - $response_data = json_decode($body, true); - - if (isset($response_data['choices'][0]['message']['content'])) { - $response_text = trim($response_data['choices'][0]['message']['content']); - - // Extract token usage information - $input_tokens = $response_data['usage']['prompt_tokens'] ?? 0; - $output_tokens = $response_data['usage']['completion_tokens'] ?? 0; - $total_tokens = $response_data['usage']['total_tokens'] ?? 0; - - // Calculate cost using model rates - $rates = igny8_get_model_rates($model); - $cost = ($input_tokens * $rates['in'] + $output_tokens * $rates['out']) / 1000000; - - return [ - 'success' => true, - 'message' => 'API connection and response test successful!', - 'model_used' => $model, - 'response' => $response_text, - 'tokens_used' => $input_tokens . ' / ' . $output_tokens, - 'total_tokens' => $total_tokens, - 'cost' => '$' . number_format($cost, 4), - 'full_response' => $response_data - ]; - } else { - return [ - 'success' => false, - 'message' => 'API responded but no content received', - 'response' => $body - ]; - } - } else { - // Handle simple connection test - return [ - 'success' => true, - 'message' => 'API connection successful!', - 'model_used' => $model, - 'response' => 'Connection verified without API call' - ]; - } - } else { - return [ - 'success' => false, - 'message' => 'HTTP ' . $code . ' β ' . $body - ]; - } -} - -/** - * Log API call with cost calculation and error handling - */ -function igny8_log_api_call($model, $input_tokens, $output_tokens, $api_id = null, $status = 'success', $error_message = '') { - global $wpdb; - - try { - // Calculate cost using model rates - $cost_data = igny8_calculate_api_cost($model, $input_tokens, $output_tokens); - - // Debug logging for cost calculation - error_log("Igny8 Cost Debug: Model=$model, Input=$input_tokens, Output=$output_tokens"); - error_log("Igny8 Cost Debug: Calculated total_cost=" . $cost_data['total_cost']); - - - // Prepare log data with sanitization - $log_data = [ - 'event_type' => 'api_call', - 'api_id' => $api_id ? esc_sql($api_id) : null, - 'status' => esc_sql($status), - 'level' => $status === 'success' ? 'info' : 'error', - 'message' => sprintf( - 'Model: %s | Input: %d tokens | Output: %d tokens | Cost: %s', - esc_sql($model), - intval($input_tokens), - intval($output_tokens), - igny8_format_cost($cost_data['total_cost']) - ), - 'context' => wp_json_encode([ - 'model' => $model, - 'input_tokens' => intval($input_tokens), - 'output_tokens' => intval($output_tokens), - 'total_cost' => $cost_data['total_cost'], - 'input_cost' => $cost_data['input_cost'], - 'output_cost' => $cost_data['output_cost'], - 'error_message' => $error_message - ]), - 'source' => 'openai_api', - 'user_id' => get_current_user_id(), - 'created_at' => current_time('mysql') - ]; - - // Insert with error handling - $result = $wpdb->insert( - $wpdb->prefix . 'igny8_logs', - $log_data, - ['%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s'] - ); - - if ($result === false) { - error_log('Igny8 API Logging Error: ' . $wpdb->last_error); - } else { - error_log("Igny8 Cost Debug: Successfully stored cost=" . $cost_data['total_cost'] . " in database"); - } - - // Maintain 50 row limit for performance - igny8_maintain_logs_limit(); - - } catch (Exception $e) { - error_log('Igny8 API Logging Exception: ' . $e->getMessage()); - } -} - -/** - * Maintain logs table limit for performance - */ -function igny8_maintain_logs_limit() { - global $wpdb; - - // Keep only the 50 most recent logs - $wpdb->query(" - DELETE FROM {$wpdb->prefix}igny8_logs - WHERE id NOT IN ( - SELECT id FROM ( - SELECT id FROM {$wpdb->prefix}igny8_logs - ORDER BY created_at DESC - LIMIT 50 - ) AS latest_logs - ) - "); -} - -/** - * Call OpenAI API for content generation - */ -function igny8_call_openai($prompt, $api_key, $model) { - // Debug logging for CRON context - if (defined('DOING_CRON') && DOING_CRON) { - error_log("Igny8 OpenAI Call: Starting API call - Model: " . $model . ", Prompt length: " . strlen($prompt)); - } - - $body_data = [ - 'model' => $model, - 'messages' => [['role' => 'user', 'content' => $prompt]], - ]; - - // Model-specific parameters - $body_data['temperature'] = 0.7; - - if (defined('DOING_CRON') && DOING_CRON) { - error_log("Igny8 OpenAI Call: Request body prepared - Temperature: " . $body_data['temperature']); - } - - // Log the complete request to file - $log_data = [ - 'timestamp' => current_time('mysql'), - 'model' => $model, - 'request_body' => $body_data, - 'headers' => [ - 'Authorization' => 'Bearer ' . substr($api_key, 0, 10) . '...', - 'Content-Type' => 'application/json' - ], - 'prompt_length' => strlen($prompt), - 'prompt_preview' => substr($prompt, 0, 200) . '...' - ]; - - $log_file = ABSPATH . 'igny8_all_api_requests.json'; - - // Read existing logs and append new one - $existing_logs = []; - if (file_exists($log_file)) { - $existing_content = file_get_contents($log_file); - if (!empty($existing_content)) { - $existing_logs = json_decode($existing_content, true) ?: []; - } - } - - // Add new request to logs - $existing_logs[] = $log_data; - - // Keep only last 50 requests to prevent file from growing too large - if (count($existing_logs) > 50) { - $existing_logs = array_slice($existing_logs, -50); - } - - file_put_contents($log_file, json_encode($existing_logs, JSON_PRETTY_PRINT)); - error_log("Igny8 Debug: API request logged to: " . $log_file); - - $args = [ - 'body' => json_encode($body_data), - 'headers' => [ - 'Content-Type' => 'application/json', - 'Authorization' => 'Bearer ' . $api_key, - ], - 'timeout' => 60, - ]; - - if (defined('DOING_CRON') && DOING_CRON) { - error_log("Igny8 OpenAI Call: Making HTTP request to OpenAI API..."); - } - - $response = wp_remote_post('https://api.openai.com/v1/chat/completions', $args); - - if (is_wp_error($response)) { - // Log API error with detailed information - $error_message = $response->get_error_message(); - $error_code = $response->get_error_code(); - - if (defined('DOING_CRON') && DOING_CRON) { - error_log("Igny8 OpenAI Call: HTTP request failed - " . $error_message); - } - - // Enhanced error logging - igny8_log_api_call($model, 0, 0, null, 'error', $error_message); - - // Log detailed error information - $error_details = [ - 'error_code' => $error_code, - 'error_message' => $error_message, - 'model' => $model, - 'api_key_configured' => !empty($api_key), - 'prompt_length' => strlen($prompt), - 'timestamp' => current_time('mysql'), - 'request_url' => 'https://api.openai.com/v1/chat/completions' - ]; - - // Log to AI events for debug module - igny8_log_ai_event('OpenAI HTTP Error', 'ai', 'api_call', 'error', 'HTTP request failed', 'Error: ' . $error_message . ' | Details: ' . json_encode($error_details)); - - return 'Error: ' . $error_message; - } - - if (defined('DOING_CRON') && DOING_CRON) { - error_log("Igny8 OpenAI Call: HTTP request successful, processing response..."); - } - - $response_code = wp_remote_retrieve_response_code($response); - $response_body = wp_remote_retrieve_body($response); - $response_data = json_decode($response_body, true); - - // Check for HTTP errors (non-200 status codes) - if ($response_code !== 200) { - $error_message = "HTTP {$response_code} error"; - if (isset($response_data['error']['message'])) { - $error_message .= ": " . $response_data['error']['message']; - } - - if (defined('DOING_CRON') && DOING_CRON) { - error_log("Igny8 OpenAI Call: HTTP error - " . $error_message); - } - - // Log detailed HTTP error - $error_details = [ - 'http_code' => $response_code, - 'error_message' => $error_message, - 'model' => $model, - 'api_key_configured' => !empty($api_key), - 'response_body' => $response_body, - 'timestamp' => current_time('mysql') - ]; - - igny8_log_ai_event('OpenAI HTTP Error', 'ai', 'api_call', 'error', 'HTTP ' . $response_code . ' error', 'Error: ' . $error_message . ' | Details: ' . json_encode($error_details)); - - return 'Error: ' . $error_message; - } - - if (defined('DOING_CRON') && DOING_CRON) { - error_log("Igny8 OpenAI Call: Response decoded - Has data: " . ($response_data ? 'Yes' : 'No')); - if ($response_data && isset($response_data['choices'])) { - error_log("Igny8 OpenAI Call: Response has " . count($response_data['choices']) . " choices"); - } - } - - // Log the complete response to file - $response_log_data = [ - 'timestamp' => current_time('mysql'), - 'model' => $model, - 'response_code' => wp_remote_retrieve_response_code($response), - 'response_body' => $response_data, - 'raw_response' => $response_body - ]; - - $response_log_file = ABSPATH . 'igny8_all_api_responses.json'; - - // Read existing response logs and append new one - $existing_response_logs = []; - if (file_exists($response_log_file)) { - $existing_content = file_get_contents($response_log_file); - if (!empty($existing_content)) { - $existing_response_logs = json_decode($existing_content, true) ?: []; - } - } - - // Add new response to logs - $existing_response_logs[] = $response_log_data; - - // Keep only last 50 responses to prevent file from growing too large - if (count($existing_response_logs) > 50) { - $existing_response_logs = array_slice($existing_response_logs, -50); - } - - file_put_contents($response_log_file, json_encode($existing_response_logs, JSON_PRETTY_PRINT)); - error_log("Igny8 Debug: API response logged to: " . $response_log_file); - - // Extract API response data for logging - $api_id = $response_data['id'] ?? null; - $usage = $response_data['usage'] ?? []; - $input_tokens = $usage['prompt_tokens'] ?? 0; - $output_tokens = $usage['completion_tokens'] ?? 0; - - // Log successful API call - igny8_log_api_call($model, $input_tokens, $output_tokens, $api_id, 'success'); - - return $response_data['choices'][0]['message']['content'] ?? 'No response.'; -} - -/** - * Get content scope based on settings - */ -function igny8_get_content_scope($post_id, $scope) { - $content = igny8_build_combined_content(true); - return $content; -} - -/** - * Generate personalized content - */ -function igny8_generate_content($post_id, $field_inputs, $options = []) { - global $wpdb; - - $api_key = get_option('igny8_api_key'); - $model = get_option('igny8_model', 'gpt-4.1'); - - if (empty($api_key)) { - return ['success' => false, 'message' => 'OpenAI API key not configured']; - } - - // Build inputs string - $inputs_string = ''; - foreach ($field_inputs as $key => $value) { - if ($key !== 'PageContent' && !empty($value)) { - $inputs_string .= ucfirst($key) . ': ' . $value . "\n"; - } - } - - // Get content - $content = igny8_build_combined_content(false, $post_id); - - // Get rewrite prompt - $rewrite_prompt = get_option('igny8_content_engine_rewrite_prompt', 'Rewrite the following content to be personalized for a reader with these characteristics: - -[INPUTS] - -Original content: -[CONTENT] - -Make the content feel like it was written specifically for this person while maintaining the original message and tone.'); - - $prompt = str_replace(['[INPUTS]', '[CONTENT]'], [$inputs_string, $content], $rewrite_prompt); - - // Log the final prompt being sent to OpenAI - igny8_log_field_detection_process('INFO', 'Final prompt being sent to OpenAI:'); - igny8_log_field_detection_process('INFO', 'INPUTS: ' . $inputs_string); - igny8_log_field_detection_process('INFO', 'CONTENT: ' . substr($content, 0, 200) . '...'); - igny8_log_field_detection_process('INFO', 'PROMPT: ' . substr($prompt, 0, 500) . '...'); - - // Call OpenAI - $generated_content = igny8_call_openai($prompt, $api_key, $model); - - if (strpos($generated_content, 'Error:') === 0) { - return ['success' => false, 'message' => $generated_content]; - } - - // Save variation if requested - $variation_id = null; - if ($options['save_variation'] ?? false) { - $variation_id = igny8_save_variation($post_id, $field_inputs, $generated_content); - } - - return [ - 'success' => true, - 'content' => $generated_content, - 'variation_id' => $variation_id, - 'message' => 'Content generated successfully' - ]; -} - -/** - * Save content variation - */ -function igny8_save_variation($post_id, $field_inputs, $content) { - global $wpdb; - - $fields_hash = md5(wp_json_encode($field_inputs)); - $fields_json = wp_json_encode($field_inputs); - - // Check if variation already exists - $existing = $wpdb->get_var($wpdb->prepare( - "SELECT id FROM {$wpdb->prefix}igny8_variations WHERE post_id = %d AND fields_hash = %s", - $post_id, $fields_hash - )); - - if ($existing) { - // Update existing variation - $wpdb->update( - $wpdb->prefix . 'igny8_variations', - [ - 'content' => $content, - 'created_at' => current_time('mysql') - ], - ['id' => $existing], - ['%s', '%s'], - ['%d'] - ); - return $existing; - } else { - // Insert new variation - $wpdb->insert( - $wpdb->prefix . 'igny8_variations', - [ - 'post_id' => $post_id, - 'fields_hash' => $fields_hash, - 'fields_json' => $fields_json, - 'content' => $content, - 'created_at' => current_time('mysql') - ], - ['%d', '%s', '%s', '%s', '%s'] - ); - return $wpdb->insert_id; - } -} - -/** - * Get cached variation - */ -function igny8_get_cached_variation($post_id, $field_inputs) { - global $wpdb; - - $fields_hash = md5(wp_json_encode($field_inputs)); - - $variation = $wpdb->get_row($wpdb->prepare( - "SELECT * FROM {$wpdb->prefix}igny8_variations WHERE post_id = %d AND fields_hash = %s", - $post_id, $fields_hash - )); - - return $variation ? (array) $variation : null; -} - -/** - * Register shortcode [igny8] - */ - - -/** - * Automatically inject Igny8 shortcode into content - */ -function igny8_inject_shortcode_into_content($content) { - // Only run on frontend - if (is_admin()) { - return $content; - } - - // Check if Content Engine is enabled globally - $global_status = get_option('igny8_content_engine_global_status', 'enabled'); - if ($global_status !== 'enabled') { - return $content; - } - - // Get current post type - $post_type = get_post_type(); - if (!$post_type) { - return $content; - } - - // Check if this post type is enabled for personalization - $enabled_post_types = get_option('igny8_content_engine_enabled_post_types', []); - if (!in_array($post_type, $enabled_post_types)) { - return $content; - } - - // Get insertion position - $insertion_position = get_option('igny8_content_engine_insertion_position', 'before'); - - // Get display mode - $display_mode = get_option('igny8_content_engine_display_mode', 'always'); - - // Check if we should show personalization based on display mode - if ($display_mode === 'logged_in' && !is_user_logged_in()) { - return $content; - } - - if ($display_mode === 'logged_out' && is_user_logged_in()) { - return $content; - } - - // Inject shortcode based on position - $shortcode = '[igny8]'; - - switch ($insertion_position) { - case 'before': - return $shortcode . $content; - case 'after': - return $content . $shortcode; - case 'replace': - return $shortcode; - default: - return $shortcode . $content; - } -} - -// Hook into the_content filter -add_filter('the_content', 'igny8_inject_shortcode_into_content'); - -// Register AJAX actions -add_action('wp_ajax_igny8_get_fields', 'igny8_ajax_get_fields'); -add_action('wp_ajax_nopriv_igny8_get_fields', 'igny8_ajax_get_fields'); -add_action('wp_ajax_igny8_generate_custom', 'igny8_ajax_generate_custom'); -add_action('wp_ajax_nopriv_igny8_generate_custom', 'igny8_ajax_generate_custom'); -add_action('wp_ajax_igny8_save_content_manual', 'igny8_ajax_save_content_manual'); -add_action('wp_ajax_nopriv_igny8_save_content_manual', 'igny8_ajax_save_content_manual'); -add_action('wp_ajax_igny8_test_personalize', 'igny8_ajax_test_personalize'); -add_action('wp_ajax_nopriv_igny8_test_personalize', 'igny8_ajax_test_personalize'); - -/** - * AJAX Handler for getting personalization fields - */ -function igny8_ajax_get_fields() { - try { - // Log the start of field detection process - igny8_log_field_detection_process('START', 'Field detection process initiated'); - - // Debug logging - error_log('IGNY8 AJAX Debug - Request method: ' . $_SERVER['REQUEST_METHOD']); - error_log('IGNY8 AJAX Debug - GET params: ' . print_r($_GET, true)); - error_log('IGNY8 AJAX Debug - POST params: ' . print_r($_POST, true)); - - // Check nonce for security - handle both GET and POST - $nonce_param = isset($_GET['nonce']) ? $_GET['nonce'] : (isset($_POST['nonce']) ? $_POST['nonce'] : ''); - if (!wp_verify_nonce($nonce_param, 'igny8_ajax_nonce')) { - igny8_log_field_detection_process('ERROR', 'Security check failed - invalid nonce'); - wp_send_json_error('Security check failed - invalid nonce'); - } - - $post_id = isset($_GET['post_id']) ? absint($_GET['post_id']) : 0; - $form_fields = isset($_GET['form_fields']) ? sanitize_text_field($_GET['form_fields']) : ''; - - if (!$post_id) { - igny8_log_field_detection_process('ERROR', 'Invalid post ID: ' . $post_id); - wp_send_json_error('Invalid post ID'); - } - - igny8_log_field_detection_process('INFO', 'Processing post ID: ' . $post_id); - - // Get content for field detection - $content = igny8_build_combined_content(true, $post_id); - - if (empty($content)) { - igny8_log_field_detection_process('ERROR', 'No content found for field detection'); - wp_send_json_error('No content found for field detection'); - } - - igny8_log_field_detection_process('INFO', 'Content retrieved successfully. Length: ' . strlen($content)); - - // Check if field detection is enabled - $field_mode = get_option('igny8_content_engine_field_mode', 'auto'); - igny8_log_field_detection_process('INFO', 'Field detection mode: ' . $field_mode); - - if ($field_mode === 'auto') { - // Use AI to detect fields - igny8_log_field_detection_process('INFO', 'Starting AI field detection'); - - $api_key = get_option('igny8_api_key'); - $model = get_option('igny8_model', 'gpt-4.1'); - $detection_prompt = get_option('igny8_content_engine_detection_prompt', ''); - - if (empty($api_key)) { - igny8_log_field_detection_process('ERROR', 'OpenAI API key not configured'); - wp_send_json_error('OpenAI API key not configured. Please set it in Personalize > Settings.'); - } - - if (empty($detection_prompt)) { - igny8_log_field_detection_process('ERROR', 'Detection prompt not configured'); - wp_send_json_error('Detection prompt not configured. Please set it in Personalize > Settings.'); - } - - // Replace [CONTENT] placeholder in prompt - $prompt = str_replace('[CONTENT]', $content, $detection_prompt); - igny8_log_field_detection_process('INFO', 'Prompt prepared for OpenAI API'); - - // Call OpenAI for field detection - $response = igny8_call_openai($prompt, $api_key, $model, 1000); - - if (strpos($response, 'Error:') === 0) { - igny8_log_field_detection_process('ERROR', 'OpenAI API error: ' . $response); - $fields = []; // Fallback to empty fields on API error - } else { - igny8_log_field_detection_process('INFO', 'OpenAI API response received successfully'); - - // Try to parse the JSON response - $fields_data = json_decode($response, true); - if ($fields_data) { - // Handle both formats: array of fields or object with fields property - if (is_array($fields_data) && isset($fields_data[0]) && is_array($fields_data[0])) { - // Direct array format: [field1, field2, ...] - $fields = $fields_data; - igny8_log_field_detection_process('SUCCESS', 'Fields detected successfully (array format): ' . count($fields) . ' fields'); - } elseif (isset($fields_data['fields']) && is_array($fields_data['fields'])) { - // Object format: {fields: [field1, field2, ...]} - $fields = $fields_data['fields']; - igny8_log_field_detection_process('SUCCESS', 'Fields detected successfully (object format): ' . count($fields) . ' fields'); - } else { - igny8_log_field_detection_process('ERROR', 'Invalid JSON structure - neither array nor object with fields property'); - igny8_log_field_detection_process('ERROR', 'Raw OpenAI response: ' . $response); - $fields = []; // Fallback to empty fields - } - - // Log each detected field - if (!empty($fields)) { - foreach ($fields as $field) { - igny8_log_field_detection_process('FIELD', 'Detected field: ' . $field['label'] . ' (type: ' . $field['type'] . ')'); - } - } - } else { - igny8_log_field_detection_process('ERROR', 'Failed to parse OpenAI response as valid JSON'); - igny8_log_field_detection_process('ERROR', 'Raw OpenAI response: ' . $response); - $fields = []; // Fallback to empty fields - } - } - } else { - // Use fixed fields from configuration - igny8_log_field_detection_process('INFO', 'Using manual field configuration'); - - $fields = []; - $fixed_fields_config = get_option('igny8_content_engine_fixed_fields_config', []); - - if (!empty($fixed_fields_config)) { - // Use the configured fields directly - $fields = $fixed_fields_config; - igny8_log_field_detection_process('SUCCESS', 'Using configured fields: ' . count($fields) . ' fields'); - } elseif (!empty($form_fields)) { - // Fallback to form_fields parameter if no config - $field_names = explode(',', $form_fields); - foreach ($field_names as $field_name) { - $fields[] = [ - 'label' => trim($field_name), - 'type' => 'text', - 'options' => 'Example 1, Example 2' - ]; - } - igny8_log_field_detection_process('INFO', 'Using fallback fields from parameter: ' . count($fields) . ' fields'); - } - } - - // If no fields were generated, provide a fallback - if (empty($fields)) { - igny8_log_field_detection_process('ERROR', 'No fields could be generated'); - - // Provide more detailed error information - $error_details = [ - 'field_mode' => $field_mode, - 'api_key_set' => !empty(get_option('igny8_api_key')), - 'detection_prompt_set' => !empty(get_option('igny8_content_engine_detection_prompt')), - 'content_length' => strlen($content), - 'post_id' => $post_id - ]; - - igny8_log_field_detection_process('ERROR', 'Field generation failed - Details: ' . json_encode($error_details)); - - wp_send_json_error([ - 'message' => 'No fields could be generated. Please check your settings and try again.', - 'details' => $error_details - ]); - } - - igny8_log_field_detection_process('INFO', 'Generating form HTML for ' . count($fields) . ' fields'); - - // Generate form HTML with progress messages - ob_start(); - ?> --- - - getMessage()); - error_log('Igny8 AJAX Error: ' . $e->getMessage()); - error_log('Igny8 AJAX Trace: ' . $e->getTraceAsString()); - wp_send_json_error('Error: ' . $e->getMessage()); - } -} - -/** - * AJAX Handler for generating personalized content - */ -function igny8_ajax_generate_custom() { - try { - // Log the start of content generation process - igny8_log_field_detection_process('START', 'Content generation process initiated'); - - // Check nonce for security - if (!check_ajax_referer('igny8_ajax_nonce', 'nonce')) { - igny8_log_field_detection_process('ERROR', 'Security check failed for content generation'); - wp_send_json_error('Security check failed'); - } - - $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0; - - if (!$post_id) { - igny8_log_field_detection_process('ERROR', 'Invalid post ID for content generation: ' . $post_id); - wp_send_json_error('Invalid post ID'); - } - - igny8_log_field_detection_process('INFO', 'Generating content for post ID: ' . $post_id); - - // Get field inputs from form - $field_inputs = []; - foreach ($_POST as $key => $value) { - if ($key !== 'action' && $key !== 'nonce' && $key !== 'post_id') { - $field_inputs[sanitize_text_field($key)] = sanitize_text_field($value); - } - } - - igny8_log_field_detection_process('INFO', 'Field inputs collected: ' . count($field_inputs) . ' fields'); - - // Log each field input - foreach ($field_inputs as $key => $value) { - igny8_log_field_detection_process('FIELD', 'Field input: ' . $key . ' = ' . $value); - } - - // Generate personalized content - igny8_log_field_detection_process('INFO', 'Starting OpenAI content generation'); - $result = igny8_generate_content($post_id, $field_inputs, []); - - if ($result['success']) { - igny8_log_field_detection_process('SUCCESS', 'Content generated successfully. Length: ' . strlen($result['content'])); - - // Return enhanced content with generation details - $enhanced_content = ' -- -- - -- - - Fields were automatically detected using AI analysis of your content. - - Fields were loaded from your manual configuration. - - ---- - '; - - wp_send_json_success($enhanced_content); - } else { - igny8_log_field_detection_process('ERROR', 'Content generation failed: ' . $result['message']); - wp_send_json_error($result['message']); - } - - } catch (Exception $e) { - igny8_log_field_detection_process('ERROR', 'Exception in content generation: ' . $e->getMessage()); - error_log('Igny8 Content Generation Error: ' . $e->getMessage()); - error_log('Igny8 Content Generation Trace: ' . $e->getTraceAsString()); - wp_send_json_error('Error: ' . $e->getMessage()); - } -} - -/** - * AJAX Handler for saving content manually - */ -function igny8_ajax_save_content_manual() { - try { - // Check nonce for security - if (!check_ajax_referer('igny8_ajax_nonce', 'nonce')) { - wp_send_json_error('Security check failed'); - } - - $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0; - $content = isset($_POST['content']) ? wp_kses_post($_POST['content']) : ''; - $field_inputs = isset($_POST['field_inputs']) ? json_decode(stripslashes($_POST['field_inputs']), true) : []; - - if (!$post_id || empty($content)) { - wp_send_json_error('Invalid post ID or content'); - } - - // Save content variation - $result = igny8_save_variation($post_id, $field_inputs, $content); - - if ($result) { - wp_send_json_success('Content saved successfully'); - } else { - wp_send_json_error('Failed to save content'); - } - - } catch (Exception $e) { - wp_send_json_error('Error: ' . $e->getMessage()); - } -} - -/** - * Simple test AJAX handler - */ -function igny8_ajax_test_personalize() { - try { - // Check nonce for security - $nonce_param = isset($_GET['nonce']) ? $_GET['nonce'] : (isset($_POST['nonce']) ? $_POST['nonce'] : ''); - if (!wp_verify_nonce($nonce_param, 'igny8_ajax_nonce')) { - wp_send_json_error('Security check failed - invalid nonce'); - } - - wp_send_json_success([ - 'message' => 'Personalization AJAX is working!', - 'post_id' => get_queried_object_id(), - 'timestamp' => current_time('mysql') - ]); - - } catch (Exception $e) { - wp_send_json_error('Error: ' . $e->getMessage()); - } -} - -/** - * AJAX Handler for testing API connection - */ -function igny8_ajax_test_api() { - // Check nonce for security - if (!wp_verify_nonce($_POST['nonce'] ?? '', 'igny8_ajax_nonce')) { - wp_send_json_error('Security check failed'); - } - - // Check permissions - if (!current_user_can('edit_posts')) { - wp_send_json_error('Insufficient permissions'); - } - - // Get API key - $api_key = get_option('igny8_api_key', ''); - - if (empty($api_key)) { - wp_send_json_error(['message' => 'API key not configured']); - } - - // Check if response test is requested - $with_response = isset($_POST['with_response']) && $_POST['with_response'] === '1'; - - // Test the connection - $result = igny8_test_connection($api_key, $with_response); - - // Handle the new response format - if (is_array($result)) { - if ($result['success']) { - $response_data = [ - 'message' => $result['message'], - 'model_used' => $result['model_used'], - 'response' => $result['response'] - ]; - - // Add cost and token information if available - if (isset($result['full_response']['usage'])) { - $usage = $result['full_response']['usage']; - $model = $result['model_used']; - $cost_data = igny8_calculate_api_cost($model, $usage['prompt_tokens'], $usage['completion_tokens']); - - $response_data['tokens'] = $usage['prompt_tokens'] . ' / ' . $usage['completion_tokens']; - $response_data['cost'] = igny8_format_cost($cost_data['total_cost']); - } - - wp_send_json_success($response_data); - } else { - wp_send_json_error([ - 'message' => $result['message'], - 'details' => $result['response'] ?? '' - ]); - } - } else { - // Handle legacy string responses - if ($result === true) { - wp_send_json_success(['message' => 'Connection successful']); - } else { - $error_message = is_string($result) ? $result : 'Connection failed'; - wp_send_json_error(['message' => $error_message]); - } - } -} - - - -// Register the test AJAX handler -add_action('wp_ajax_igny8_test_personalize', 'igny8_ajax_test_personalize'); -add_action('wp_ajax_nopriv_igny8_test_personalize', 'igny8_ajax_test_personalize'); - -// Register the API test AJAX handler -add_action('wp_ajax_igny8_test_api', 'igny8_ajax_test_api'); - - -// Register the test field detection AJAX handler -add_action('wp_ajax_igny8_test_field_detection', 'igny8_ajax_test_field_detection'); - -// Register debug AJAX handler -add_action('wp_ajax_igny8_debug_personalization', 'igny8_ajax_debug_personalization'); -add_action('wp_ajax_nopriv_igny8_debug_personalization', 'igny8_ajax_debug_personalization'); - -// Register test field detection with sample content -add_action('wp_ajax_igny8_test_sample_field_detection', 'igny8_ajax_test_sample_field_detection'); - -// Register save variation AJAX handler -add_action('wp_ajax_igny8_save_variation', 'igny8_ajax_save_variation'); - -/** - * AJAX Handler for testing field detection - */ -function igny8_ajax_test_field_detection() { - try { - // Check nonce for security - if (!check_ajax_referer('igny8_ajax_nonce', 'nonce')) { - wp_send_json_error('Security check failed'); - } - - // Check permissions - if (!current_user_can('edit_posts')) { - wp_send_json_error('Insufficient permissions'); - } - - $content = isset($_POST['content']) ? sanitize_textarea_field($_POST['content']) : ''; - - if (empty($content)) { - wp_send_json_error('No content provided for testing'); - } - - // Log the test - igny8_log_field_detection_process('INFO', 'Field detection test initiated by admin'); - - // Check if field detection is enabled - $field_mode = get_option('igny8_content_engine_field_mode', 'auto'); - - if ($field_mode === 'auto') { - // Use AI to detect fields - $api_key = get_option('igny8_api_key'); - $model = get_option('igny8_model', 'gpt-4.1'); - $detection_prompt = get_option('igny8_content_engine_detection_prompt', ''); - - if (empty($api_key)) { - wp_send_json_error('OpenAI API key not configured'); - } - - if (empty($detection_prompt)) { - wp_send_json_error('Detection prompt not configured'); - } - - // Replace [CONTENT] placeholder in prompt - $prompt = str_replace('[CONTENT]', $content, $detection_prompt); - - // Call OpenAI for field detection - $response = igny8_call_openai($prompt, $api_key, $model, 1000); - - if (strpos($response, 'Error:') === 0) { - wp_send_json_error('OpenAI API error: ' . $response); - } else { - // Try to parse the JSON response - $fields_data = json_decode($response, true); - if ($fields_data) { - // Handle both formats: array of fields or object with fields property - if (is_array($fields_data) && isset($fields_data[0]) && is_array($fields_data[0])) { - // Direct array format: [field1, field2, ...] - convert to expected format - $fields_data = ['fields' => $fields_data]; - igny8_log_field_detection_process('SUCCESS', 'Test field detection successful (array format): ' . count($fields_data['fields']) . ' fields detected'); - } elseif (isset($fields_data['fields']) && is_array($fields_data['fields'])) { - // Object format: {fields: [field1, field2, ...]} - igny8_log_field_detection_process('SUCCESS', 'Test field detection successful (object format): ' . count($fields_data['fields']) . ' fields detected'); - } else { - igny8_log_field_detection_process('ERROR', 'Test field detection failed: Invalid JSON structure'); - wp_send_json_error('Invalid JSON structure - neither array nor object with fields property'); - } - wp_send_json_success($fields_data); - } else { - igny8_log_field_detection_process('ERROR', 'Test field detection failed: Invalid JSON response'); - wp_send_json_error('Failed to parse OpenAI response as valid JSON'); - } - } - } else { - // Use fixed fields from configuration - $fixed_fields_config = get_option('igny8_content_engine_fixed_fields_config', []); - - if (!empty($fixed_fields_config)) { - $fields_data = ['fields' => $fixed_fields_config]; - igny8_log_field_detection_process('SUCCESS', 'Test field detection successful: ' . count($fixed_fields_config) . ' configured fields'); - wp_send_json_success($fields_data); - } else { - igny8_log_field_detection_process('ERROR', 'Test field detection failed: No configured fields'); - wp_send_json_error('No fields configured for manual mode'); - } - } - - } catch (Exception $e) { - igny8_log_field_detection_process('ERROR', 'Exception in test field detection: ' . $e->getMessage()); - wp_send_json_error('Error: ' . $e->getMessage()); - } -} - -/** - * Debug AJAX handler to check personalization setup - */ -function igny8_ajax_debug_personalization() { - try { - // Check nonce for security - if (!check_ajax_referer('igny8_ajax_nonce', 'nonce')) { - wp_send_json_error('Security check failed'); - } - - $debug_info = [ - 'timestamp' => current_time('mysql'), - 'wordpress_version' => get_bloginfo('version'), - 'plugin_version' => get_option('igny8_version', 'unknown'), - 'current_user' => wp_get_current_user()->user_login, - 'is_admin' => current_user_can('manage_options'), - 'ajax_url' => admin_url('admin-ajax.php'), - 'post_id' => get_queried_object_id(), - 'post_type' => get_post_type(), - ]; - - // Check Content Engine settings - $debug_info['content_engine'] = [ - 'global_status' => get_option('igny8_content_engine_global_status', 'not_set'), - 'enabled_post_types' => get_option('igny8_content_engine_enabled_post_types', []), - 'display_mode' => get_option('igny8_content_engine_display_mode', 'not_set'), - 'insertion_position' => get_option('igny8_content_engine_insertion_position', 'not_set'), - 'teaser_text' => get_option('igny8_content_engine_teaser_text', 'not_set'), - 'field_mode' => get_option('igny8_content_engine_field_mode', 'not_set'), - 'detection_prompt' => get_option('igny8_content_engine_detection_prompt', 'not_set'), - 'api_key_set' => !empty(get_option('igny8_api_key', '')), - 'model' => get_option('igny8_model', 'not_set'), - ]; - - // Check if shortcode would be injected - $debug_info['shortcode_injection'] = [ - 'content_filter_active' => has_filter('the_content', 'igny8_inject_shortcode_into_content'), - 'would_show_personalization' => igny8_should_show_personalization(), - 'post_type_enabled' => igny8_is_post_type_enabled_for_personalization(), - ]; - - // Check database tables - global $wpdb; - $debug_info['database'] = [ - 'logs_table_exists' => $wpdb->get_var("SHOW TABLES LIKE '{$wpdb->prefix}igny8_logs'") ? true : false, - 'variations_table_exists' => $wpdb->get_var("SHOW TABLES LIKE '{$wpdb->prefix}igny8_variations'") ? true : false, - 'logs_count' => $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_logs") ?: 0, - ]; - - wp_send_json_success($debug_info); - - } catch (Exception $e) { - wp_send_json_error('Debug error: ' . $e->getMessage()); - } -} - -/** - * Check if personalization should be shown for current context - */ -function igny8_should_show_personalization() { - // Only run on frontend - if (is_admin()) { - return false; - } - - // Check if Content Engine is enabled globally - $global_status = get_option('igny8_content_engine_global_status', 'enabled'); - if ($global_status !== 'enabled') { - return false; - } - - // Get current post type - $post_type = get_post_type(); - if (!$post_type) { - return false; - } - - // Check if this post type is enabled for personalization - $enabled_post_types = get_option('igny8_content_engine_enabled_post_types', []); - if (!in_array($post_type, $enabled_post_types)) { - return false; - } - - // Get display mode - $display_mode = get_option('igny8_content_engine_display_mode', 'always'); - - // Check if we should show personalization based on display mode - if ($display_mode === 'logged_in' && !is_user_logged_in()) { - return false; - } - - if ($display_mode === 'logged_out' && is_user_logged_in()) { - return false; - } - - return true; -} - -/** - * Check if current post type is enabled for personalization - */ -function igny8_is_post_type_enabled_for_personalization() { - $post_type = get_post_type(); - if (!$post_type) { - return false; - } - - $enabled_post_types = get_option('igny8_content_engine_enabled_post_types', []); - return in_array($post_type, $enabled_post_types); -} - -/** - * Test field detection with sample cabinet content - */ -function igny8_ajax_test_sample_field_detection() { - try { - // Check nonce for security - if (!check_ajax_referer('igny8_ajax_nonce', 'nonce')) { - wp_send_json_error('Security check failed'); - } - - // Check permissions - if (!current_user_can('edit_posts')) { - wp_send_json_error('Insufficient permissions'); - } - - // Sample cabinet content - $sample_content = "Revamp Your Space with Style and Utility Introducing our exquisite Chic Wood Countertop Cabinet, a seamless fusion of sophistication and functionality tailored for your kitchen or bathroom. Crafted with precision, this cabinet is meticulously designed to elevate your home's aesthetic while offering a streamlined solution for organizing your essentials. Highlight Features Dual Window Doors: Transparent panels provide a clear view of neatly arranged items, safeguarding them from dust and moisture. Multi-Purpose Storage: Perfect for stowing spice jars, vanity beauty essentials, and other petite necessities. Premium Wood Construction: Crafted with high-quality wood for enduring durability and a touch of natural warmth that enhances any decor. Compact Design: Tailored to fit seamlessly on countertops, delivering exceptional space-saving prowess. Where and When to Utilize? Versatile in its application, this cabinet thrives in diverse environments. Whether adorning your kitchen to keep spices and condiments accessible or gracing the bathroom to house beauty and skincare essentials, its compact stature renders it a perfect fit for small apartments, dormitories, or any space craving efficient organization."; - - // Get detection prompt - $detection_prompt = get_option('igny8_content_engine_detection_prompt', ''); - if (empty($detection_prompt)) { - wp_send_json_error('Detection prompt not configured'); - } - - // Replace [CONTENT] placeholder in prompt - $prompt = str_replace('[CONTENT]', $sample_content, $detection_prompt); - - // Get API settings - $api_key = get_option('igny8_api_key'); - $model = get_option('igny8_model', 'gpt-4.1'); - - if (empty($api_key)) { - wp_send_json_error('OpenAI API key not configured'); - } - - // Call OpenAI for field detection - $response = igny8_call_openai($prompt, $api_key, $model, 1000); - - if (strpos($response, 'Error:') === 0) { - wp_send_json_error('OpenAI API error: ' . $response); - } else { - // Try to parse the JSON response - $fields_data = json_decode($response, true); - if ($fields_data && isset($fields_data['fields'])) { - wp_send_json_success([ - 'message' => 'Field detection test successful', - 'sample_content' => $sample_content, - 'detected_fields' => $fields_data, - 'raw_response' => $response - ]); - } else { - wp_send_json_error([ - 'message' => 'Failed to parse OpenAI response as valid JSON', - 'raw_response' => $response - ]); - } - } - - } catch (Exception $e) { - wp_send_json_error('Error: ' . $e->getMessage()); - } -} - -/** - * AJAX Handler for saving content variations - */ -function igny8_ajax_save_variation() { - try { - // Check nonce for security - if (!check_ajax_referer('igny8_ajax_nonce', 'nonce')) { - wp_send_json_error('Security check failed'); - } - - // Check if user has permission to save content - if (!current_user_can('manage_options')) { - wp_send_json_error('Insufficient permissions'); - } - - $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0; - $content = isset($_POST['content']) ? wp_kses_post($_POST['content']) : ''; - $field_inputs = isset($_POST['field_inputs']) ? json_decode(stripslashes($_POST['field_inputs']), true) : []; - - if (!$post_id || empty($content)) { - wp_send_json_error('Missing required data'); - } - - // Save to variations table - global $wpdb; - $table_name = $wpdb->prefix . 'igny8_variations'; - - $result = $wpdb->insert( - $table_name, - [ - 'post_id' => $post_id, - 'field_inputs' => json_encode($field_inputs), - 'personalized_content' => $content, - 'created_at' => current_time('mysql'), - 'created_by' => get_current_user_id() - ], - ['%d', '%s', '%s', '%s', '%d'] - ); - - if ($result === false) { - wp_send_json_error('Failed to save variation to database'); - } - - wp_send_json_success([ - 'variation_id' => $wpdb->insert_id, - 'message' => 'Content variation saved successfully' - ]); - - } catch (Exception $e) { - wp_send_json_error('Error: ' . $e->getMessage()); - } -} diff --git a/igny8-wp-plugin-for-reference-olny/ai/prompts-library.php b/igny8-wp-plugin-for-reference-olny/ai/prompts-library.php deleted file mode 100644 index a26ad494..00000000 --- a/igny8-wp-plugin-for-reference-olny/ai/prompts-library.php +++ /dev/null @@ -1,310 +0,0 @@ - Child\", \"Optional 2nd category if needed\"], - [IMAGE_PROMPTS] -} - -=========================== -CONTENT FORMAT & STRUCTURE -=========================== - -- Use only valid WP-supported HTML blocks:- -- -- - Content was personalized using AI based on your inputs. - Generated on ' . current_time('F j, Y \a\t g:i A') . ' - ----- ' . wp_kses_post($result['content']) . ' -- - ' . (current_user_can('manage_options') ? ' -- - -' : '') . ' -,
,
,
/
, and
-- Do not add extra line breaks, empty tags, or inconsistent spacing -- Use proper table structure when using tables: -
- -
- -=========================== -CONTENT FLOW RULES -=========================== - -**INTRODUCTION:** -- Start with 1 italicized hook (30β40 words) -- Follow with 2 narrative paragraphs (each 50β60 words; 2β3 sentences max) -- No headings allowed in intro - -**H2 SECTIONS (5β8 total):** -Each section should be 250β300 words and follow this format: -1. Two narrative paragraphs (80β120 words each, 2β3 sentences) -2. One list or table (must come *after* a paragraph) -3. Optional closing paragraph (40β60 words) -4. Insert 2β3- - - col heading1 col heading2 - - -cell1 cell2 -subsections naturally after main paragraphs - -**Formatting Rules:** -- Vary use of unordered lists, ordered lists, and tables across sections -- Never begin any section or sub-section with a list or table - -=========================== -KEYWORD & SEO RULES -=========================== - -- **Primary keyword** must appear in: - - The title - - First paragraph of the introduction - - At least 2 H2 headings - -- **Secondary keywords** must be used naturally, not forced - -- **Tone & style guidelines:** - - No robotic or passive voice - - Avoid generic intros like \"In today's worldβ¦\" - - Don't repeat heading in opening sentence - - Vary sentence structure and length - -=========================== -IMAGE PROMPT RULES -=========================== - -- Provide detailed, specific, and relevant image prompts -- Each prompt should reflect the topic/subtopic in that section -- Avoid vague or generic prompts - -=========================== -INPUT VARIABLES -=========================== - -CONTENT IDEA DETAILS: -[IGNY8_IDEA] - -KEYWORD CLUSTER: -[IGNY8_CLUSTER] - -ASSOCIATED KEYWORDS: -[IGNY8_KEYWORDS] - -=========================== -OUTPUT FORMAT -=========================== - -Return ONLY the final JSON object. -Do NOT include any comments, formatting, or explanations."; -} diff --git a/igny8-wp-plugin-for-reference-olny/ai/runware-api.php b/igny8-wp-plugin-for-reference-olny/ai/runware-api.php deleted file mode 100644 index 77241e39..00000000 --- a/igny8-wp-plugin-for-reference-olny/ai/runware-api.php +++ /dev/null @@ -1,191 +0,0 @@ - 'Bearer ' . $api_key, - 'Content-Type' => 'application/json', - ]; - - $body = [ - 'model' => $model, - 'prompt' => $prompt, - 'size' => '1024x1024', - 'quality' => 'standard', - 'n' => 1 - ]; - - $args = [ - 'method' => 'POST', - 'headers' => $headers, - 'body' => json_encode($body), - 'timeout' => 60, - ]; - - $response = wp_remote_post($url, $args); - - if (is_wp_error($response)) { - igny8_log_ai_event('runway_api_error', 'error', [ - 'message' => $response->get_error_message(), - 'prompt' => $prompt, - 'model' => $model - ]); - return $response; - } - - $response_code = wp_remote_retrieve_response_code($response); - $response_body = wp_remote_retrieve_body($response); - $response_data = json_decode($response_body, true); - - if ($response_code !== 200) { - $error_message = isset($response_data['error']['message']) ? $response_data['error']['message'] : 'Unknown error'; - igny8_log_ai_event('runway_api_error', 'error', [ - 'code' => $response_code, - 'message' => $error_message, - 'prompt' => $prompt, - 'model' => $model - ]); - return new WP_Error('api_error', $error_message); - } - - // Log successful API call - igny8_log_ai_event('runway_api_success', 'success', [ - 'model' => $model, - 'prompt_length' => strlen($prompt), - 'cost' => 0.036, // Runware pricing - 'service' => 'runware' - ]); - - return $response_data; -} - -/** - * Download and save image from Runware response - * - * @param array $response_data The API response data - * @param string $filename The desired filename - * @return string|WP_Error Saved file path or error - */ -function igny8_runway_save_image($response_data, $filename) { - if (!isset($response_data['data'][0]['url'])) { - return new WP_Error('no_image_url', 'No image URL in response'); - } - - $image_url = $response_data['data'][0]['url']; - - // Create uploads directory - $upload_dir = wp_upload_dir(); - $igny8_dir = $upload_dir['basedir'] . '/igny8-ai-images/'; - - if (!file_exists($igny8_dir)) { - wp_mkdir_p($igny8_dir); - } - - // Download image - $image_response = wp_remote_get($image_url); - - if (is_wp_error($image_response)) { - return $image_response; - } - - $image_data = wp_remote_retrieve_body($image_response); - $file_path = $igny8_dir . $filename; - - $saved = file_put_contents($file_path, $image_data); - - if ($saved === false) { - return new WP_Error('save_failed', 'Failed to save image file'); - } - - return $file_path; -} - -/** - * Test Runware API connection - * - * @return array Test result - */ -function igny8_test_runway_connection() { - $test_prompt = 'A simple test image: a red circle on white background'; - - $response = igny8_runway_generate_image($test_prompt); - - if (is_wp_error($response)) { - return [ - 'success' => false, - 'message' => $response->get_error_message(), - 'details' => 'Runware API connection failed' - ]; - } - - return [ - 'success' => true, - 'message' => 'Runware API connection successful', - 'details' => 'Test image generation completed successfully' - ]; -} - -/** - * Get available Runware models - * - * @return array Available models - */ -function igny8_get_runway_models() { - return [ - 'gen3a_turbo' => [ - 'name' => 'Gen-3 Alpha Turbo', - 'description' => 'Fast, high-quality image generation', - 'cost' => 0.055 - ], - 'gen3a' => [ - 'name' => 'Gen-3 Alpha', - 'description' => 'Standard quality image generation', - 'cost' => 0.055 - ] - ]; -} - -/** - * Log AI event for Runway API - * - * @param string $event Event type - * @param string $status Success/error status - * @param array $context Additional context data - */ -function igny8_log_runway_event($event, $status, $context = []) { - igny8_log_ai_event($event, $status, array_merge($context, [ - 'service' => 'runware', - 'timestamp' => current_time('mysql') - ])); -} diff --git a/igny8-wp-plugin-for-reference-olny/ai/writer/images/image-generation.php b/igny8-wp-plugin-for-reference-olny/ai/writer/images/image-generation.php deleted file mode 100644 index 45c8fa2a..00000000 --- a/igny8-wp-plugin-for-reference-olny/ai/writer/images/image-generation.php +++ /dev/null @@ -1,534 +0,0 @@ - false, 'error' => 'Post not found']; - } - - // Get featured image prompt from post meta - $featured_image_prompt = get_post_meta($post_id, '_igny8_featured_image_prompt', true); - - if (empty($featured_image_prompt)) { - return ['success' => false, 'error' => 'No featured image prompt found in post meta']; - } - - // Get image generation settings from prompts page - $image_type = get_option('igny8_image_type', 'realistic'); - $image_service = get_option('igny8_image_service', 'openai'); - $image_format = get_option('igny8_image_format', 'jpg'); - $negative_prompt = get_option('igny8_negative_prompt', 'text, watermark, logo, overlay, title, caption, writing on walls, writing on objects, UI, infographic elements, post title'); - - // Get image model settings based on service - $image_model = get_option('igny8_image_model', 'dall-e-3'); - $runware_model = get_option('igny8_runware_model', 'runware:97@1'); - $prompt_template = wp_unslash(get_option('igny8_image_prompt_template', 'Create a high-quality {image_type} image to use as a featured photo for a blog post titled "{post_title}". The image should visually represent the theme, mood, and subject implied by the image prompt: {image_prompt}. Focus on a realistic, well-composed scene that naturally communicates the topic without text or logos. Use balanced lighting, pleasing composition, and photographic detail suitable for lifestyle or editorial web content. Avoid adding any visible or readable text, brand names, or illustrative effects. **And make sure image is not blurry.**')); - - // Get dimensions based on image size type and service - $dimensions = igny8_get_image_dimensions($image_size_type, $image_service); - $image_width = $dimensions['width']; - $image_height = $dimensions['height']; - - // Get API keys - $openai_key = get_option('igny8_api_key', ''); - $runware_key = get_option('igny8_runware_api_key', ''); - - $required_key = ($image_service === 'runware') ? $runware_key : $openai_key; - if (empty($required_key)) { - return ['success' => false, 'error' => ($image_service === 'runware' ? 'Runware' : 'OpenAI') . ' API key not configured']; - } - - // Build final prompt - $prompt = str_replace( - ['{image_type}', '{post_title}', '{image_prompt}'], - [$image_type, $post->post_title, $featured_image_prompt], - $prompt_template - ); - - try { - // Event 7: API request sent - error_log('Igny8: IMAGE_GEN_EVENT_7 - API request sent to ' . $image_service . ' for post: ' . $post_id); - - if ($image_service === 'runware') { - // Runware API Call - $payload = [ - [ - 'taskType' => 'authentication', - 'apiKey' => $runware_key - ], - [ - 'taskType' => 'imageInference', - 'taskUUID' => wp_generate_uuid4(), - 'positivePrompt' => $prompt, - 'negativePrompt' => $negative_prompt, - 'model' => $runware_model, - 'width' => $image_width, - 'height' => $image_height, - 'steps' => 30, - 'CFGScale' => 7.5, - 'numberResults' => 1, - 'outputFormat' => $image_format - ] - ]; - - $response = wp_remote_post('https://api.runware.ai/v1', [ - 'headers' => ['Content-Type' => 'application/json'], - 'body' => json_encode($payload), - 'timeout' => 150, // Increased to 150 seconds for image generation - 'httpversion' => '1.1', - 'sslverify' => true - ]); - - if (is_wp_error($response)) { - error_log('Igny8: IMAGE_GEN_ERROR - Runware API request failed: ' . $response->get_error_message()); - return ['success' => false, 'error' => 'Runware API Error: ' . $response->get_error_message()]; - } - - $response_body = wp_remote_retrieve_body($response); - $response_data = json_decode($response_body, true); - - error_log('Igny8: IMAGE_GEN - Runware API response: ' . substr($response_body, 0, 200)); - - if (isset($response_data['data'][0]['imageURL'])) { - $image_url = $response_data['data'][0]['imageURL']; - - // Event 8: Image URL received - error_log('Igny8: IMAGE_GEN_EVENT_8 - Image URL received from ' . $image_service . ' for post: ' . $post_id); - - // Generate filename - $filename = sanitize_file_name($post->post_title) . '_featured_' . time() . '.' . $image_format; - - // Event 9: Image saved to WordPress - error_log('Igny8: IMAGE_GEN_EVENT_9 - Saving image to WordPress for post: ' . $post_id); - - // Download image from Runware URL - require_once(ABSPATH . 'wp-admin/includes/media.php'); - require_once(ABSPATH . 'wp-admin/includes/file.php'); - require_once(ABSPATH . 'wp-admin/includes/image.php'); - - error_log('Igny8: IMAGE_GEN - Downloading image from URL: ' . $image_url); - $temp_file = download_url($image_url); - - if (is_wp_error($temp_file)) { - error_log('Igny8: IMAGE_GEN_EVENT_9_ERROR - Failed to download image: ' . $temp_file->get_error_message()); - return ['success' => false, 'error' => 'Failed to download image: ' . $temp_file->get_error_message()]; - } - - error_log('Igny8: IMAGE_GEN - Image downloaded to temp file: ' . $temp_file); - - // Prepare file array for media_handle_sideload - $file_array = [ - 'name' => $filename, - 'tmp_name' => $temp_file - ]; - - // Upload to WordPress media library - error_log('Igny8: IMAGE_GEN - Uploading to media library with media_handle_sideload'); - $attachment_id = media_handle_sideload($file_array, $post_id, $post->post_title . ' Featured Image'); - - // Clean up temp file - if (file_exists($temp_file)) { - @unlink($temp_file); - } - - if (is_wp_error($attachment_id)) { - error_log('Igny8: IMAGE_GEN_EVENT_9_ERROR - media_handle_sideload failed: ' . $attachment_id->get_error_message()); - return ['success' => false, 'error' => 'Failed to upload image: ' . $attachment_id->get_error_message()]; - } - - error_log('Igny8: IMAGE_GEN - Successfully created attachment ID: ' . $attachment_id); - - if (!is_wp_error($attachment_id)) { - // Set as featured image - set_post_thumbnail($post_id, $attachment_id); - - // Generate attachment metadata - $attachment_data = wp_generate_attachment_metadata($attachment_id, get_attached_file($attachment_id)); - wp_update_attachment_metadata($attachment_id, $attachment_data); - - // Get attachment URL - $attachment_url = wp_get_attachment_url($attachment_id); - - error_log('Igny8: IMAGE_GEN_EVENT_9_SUCCESS - Image saved successfully, attachment ID: ' . $attachment_id); - - return [ - 'success' => true, - 'attachment_id' => $attachment_id, - 'image_url' => $attachment_url, - 'provider' => 'runware' - ]; - } else { - error_log('Igny8: IMAGE_GEN_EVENT_9_ERROR - Failed to save image: ' . $attachment_id->get_error_message()); - return ['success' => false, 'error' => 'Failed to register image: ' . $attachment_id->get_error_message()]; - } - } else { - error_log('Igny8: IMAGE_GEN_EVENT_8_ERROR - No image URL in response'); - $error_msg = isset($response_data['errors'][0]['message']) ? $response_data['errors'][0]['message'] : 'Unknown Runware API error'; - return ['success' => false, 'error' => $error_msg]; - } - } else { - // OpenAI API Call with selected model - $data = [ - 'model' => $image_model, - 'prompt' => $prompt, - 'n' => 1, - 'size' => $image_width . 'x' . $image_height - ]; - - $args = [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'Authorization' => 'Bearer ' . $openai_key, - ], - 'body' => json_encode($data), - 'timeout' => 60, - ]; - - $response = wp_remote_post('https://api.openai.com/v1/images/generations', $args); - - if (is_wp_error($response)) { - return ['success' => false, 'error' => 'OpenAI API Error: ' . $response->get_error_message()]; - } - - $response_code = wp_remote_retrieve_response_code($response); - $response_body = wp_remote_retrieve_body($response); - - if ($response_code === 200) { - $body = json_decode($response_body, true); - if (isset($body['data'][0]['url'])) { - $image_url = $body['data'][0]['url']; - $revised_prompt = $body['data'][0]['revised_prompt'] ?? null; - - // Generate filename (OpenAI always returns PNG) - $filename = sanitize_file_name($post->post_title) . '_featured_' . time() . '.png'; - - // Download and register in WordPress Media Library - $attachment_id = wp_insert_attachment([ - 'post_mime_type' => 'image/png', - 'post_title' => $post->post_title . ' Featured Image', - 'post_content' => '', - 'post_status' => 'inherit' - ], $image_url, $post_id); - - if (!is_wp_error($attachment_id)) { - // Set as featured image - set_post_thumbnail($post_id, $attachment_id); - - // Get attachment URL - $attachment_url = wp_get_attachment_url($attachment_id); - - return [ - 'success' => true, - 'attachment_id' => $attachment_id, - 'image_url' => $attachment_url, - 'provider' => 'openai', - 'revised_prompt' => $revised_prompt - ]; - } else { - return ['success' => false, 'error' => 'Failed to register image: ' . $attachment_id->get_error_message()]; - } - } - } else { - return ['success' => false, 'error' => 'HTTP ' . $response_code . ' error']; - } - } - } catch (Exception $e) { - return ['success' => false, 'error' => 'Exception: ' . $e->getMessage()]; - } - - return ['success' => false, 'error' => 'Unknown error occurred']; -} - -/** - * Generate single article image for post from post meta prompts - * - * @param int $post_id WordPress post ID - * @param string $device_type Device type: 'desktop' or 'mobile' - * @param int $index Image index (1-based) - * @return array Result with success status and attachment_id or error - */ -function igny8_generate_single_article_image($post_id, $device_type = 'desktop', $index = 1) { - // Get post - $post = get_post($post_id); - if (!$post) { - return ['success' => false, 'error' => 'Post not found']; - } - - // Get article image prompts from post meta - $article_images_data = get_post_meta($post_id, '_igny8_article_images_data', true); - - // Debug: Log the raw data to see what's actually stored - error_log('IGNY8 DEBUG: Raw article_images_data: ' . substr($article_images_data, 0, 200) . '...'); - - $article_images = json_decode($article_images_data, true); - - // Check for JSON decode errors - if (json_last_error() !== JSON_ERROR_NONE) { - error_log('IGNY8 DEBUG: JSON decode error: ' . json_last_error_msg()); - error_log('IGNY8 DEBUG: Raw data causing error: ' . $article_images_data); - - // 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 DEBUG: Still invalid JSON after cleaning: ' . json_last_error_msg()); - return ['success' => false, 'error' => 'Invalid JSON in article images data: ' . json_last_error_msg()]; - } else { - error_log('IGNY8 DEBUG: Successfully cleaned and parsed JSON'); - } - } - - if (empty($article_images) || !is_array($article_images)) { - return ['success' => false, 'error' => 'No article image prompts found in post meta']; - } - - // Find the prompt for the requested index - $image_key = 'prompt-img-' . $index; - $prompt = ''; - - foreach ($article_images as $image_data) { - if (isset($image_data[$image_key])) { - $prompt = $image_data[$image_key]; - break; - } - } - - if (empty($prompt)) { - return ['success' => false, 'error' => 'No prompt found for image index ' . $index]; - } - - // Get image generation settings - $image_type = get_option('igny8_image_type', 'realistic'); - $image_service = get_option('igny8_image_service', 'openai'); - $image_format = get_option('igny8_image_format', 'jpg'); - $negative_prompt = get_option('igny8_negative_prompt', 'text, watermark, logo, overlay, title, caption, writing on walls, writing on objects, UI, infographic elements, post title'); - - // Get image model settings based on service - $image_model = get_option('igny8_image_model', 'dall-e-3'); - $runware_model = get_option('igny8_runware_model', 'runware:97@1'); - - // Get dimensions based on device type and service - $dimensions = igny8_get_image_dimensions($device_type, $image_service); - $image_width = $dimensions['width']; - $image_height = $dimensions['height']; - - // Get API keys - $openai_key = get_option('igny8_api_key', ''); - $runware_key = get_option('igny8_runware_api_key', ''); - - $required_key = ($image_service === 'runware') ? $runware_key : $openai_key; - if (empty($required_key)) { - return ['success' => false, 'error' => ($image_service === 'runware' ? 'Runware' : 'OpenAI') . ' API key not configured']; - } - - // Enhance prompt if needed - $full_prompt = $prompt; - if (strlen($prompt) < 50 || strpos($prompt, 'Create') !== 0) { - $section = "Section " . $index; - $full_prompt = "Create a high-quality {$image_type} image for the section titled '{$section}'. {$prompt}"; - } - - try { - error_log('Igny8: ARTICLE_IMAGE_GEN - Generating ' . $device_type . ' image for post: ' . $post_id . ', index: ' . $index); - - if ($image_service === 'runware') { - // Runware API Call - $payload = [ - [ - 'taskType' => 'authentication', - 'apiKey' => $runware_key - ], - [ - 'taskType' => 'imageInference', - 'taskUUID' => wp_generate_uuid4(), - 'positivePrompt' => $full_prompt, - 'negativePrompt' => $negative_prompt, - 'model' => $runware_model, - 'width' => $image_width, - 'height' => $image_height, - 'steps' => 30, - 'CFGScale' => 7.5, - 'numberResults' => 1, - 'outputFormat' => $image_format - ] - ]; - - $response = wp_remote_post('https://api.runware.ai/v1', [ - 'headers' => ['Content-Type' => 'application/json'], - 'body' => json_encode($payload), - 'timeout' => 150, - 'httpversion' => '1.1', - 'sslverify' => true - ]); - - if (is_wp_error($response)) { - error_log('Igny8: ARTICLE_IMAGE_GEN_ERROR - Runware API request failed: ' . $response->get_error_message()); - return ['success' => false, 'error' => 'Runware API Error: ' . $response->get_error_message()]; - } - - $response_body = wp_remote_retrieve_body($response); - $response_data = json_decode($response_body, true); - - if (isset($response_data['data'][0]['imageURL'])) { - $image_url = $response_data['data'][0]['imageURL']; - - // Generate filename - $filename = sanitize_file_name($post->post_title) . '_' . $device_type . '_' . $index . '_' . time() . '.' . $image_format; - - // Download image from Runware URL - require_once(ABSPATH . 'wp-admin/includes/media.php'); - require_once(ABSPATH . 'wp-admin/includes/file.php'); - require_once(ABSPATH . 'wp-admin/includes/image.php'); - - $temp_file = download_url($image_url); - - if (is_wp_error($temp_file)) { - return ['success' => false, 'error' => 'Failed to download image: ' . $temp_file->get_error_message()]; - } - - // Prepare file array for media_handle_sideload - $file_array = [ - 'name' => $filename, - 'tmp_name' => $temp_file - ]; - - // Upload to WordPress media library - $attachment_id = media_handle_sideload($file_array, $post_id, $post->post_title . ' ' . ucfirst($device_type) . ' Image ' . $index); - - // Clean up temp file - if (file_exists($temp_file)) { - @unlink($temp_file); - } - - if (is_wp_error($attachment_id)) { - return ['success' => false, 'error' => 'Failed to upload image: ' . $attachment_id->get_error_message()]; - } - - // Generate attachment metadata - $attachment_data = wp_generate_attachment_metadata($attachment_id, get_attached_file($attachment_id)); - wp_update_attachment_metadata($attachment_id, $attachment_data); - - // Add custom metadata - update_post_meta($attachment_id, '_igny8_image_type', $device_type); - update_post_meta($attachment_id, '_igny8_provider', 'runware'); - update_post_meta($attachment_id, '_igny8_section', 'Section ' . $index); - - // Get attachment URL - $attachment_url = wp_get_attachment_url($attachment_id); - - error_log('Igny8: ARTICLE_IMAGE_GEN_SUCCESS - ' . $device_type . ' image generated, attachment ID: ' . $attachment_id); - - return [ - 'success' => true, - 'attachment_id' => $attachment_id, - 'image_url' => $attachment_url, - 'provider' => 'runware', - 'device_type' => $device_type, - 'index' => $index - ]; - } else { - $error_msg = isset($response_data['errors'][0]['message']) ? $response_data['errors'][0]['message'] : 'Unknown Runware API error'; - return ['success' => false, 'error' => $error_msg]; - } - } else { - // OpenAI API Call with selected model - $data = [ - 'model' => $image_model, - 'prompt' => $full_prompt, - 'n' => 1, - 'size' => '1024x1024' // OpenAI only supports square - ]; - - $args = [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'Authorization' => 'Bearer ' . $openai_key, - ], - 'body' => json_encode($data), - 'timeout' => 60, - ]; - - $response = wp_remote_post('https://api.openai.com/v1/images/generations', $args); - - if (is_wp_error($response)) { - return ['success' => false, 'error' => 'OpenAI API Error: ' . $response->get_error_message()]; - } - - $response_code = wp_remote_retrieve_response_code($response); - $response_body = wp_remote_retrieve_body($response); - - if ($response_code === 200) { - $body = json_decode($response_body, true); - if (isset($body['data'][0]['url'])) { - $image_url = $body['data'][0]['url']; - $revised_prompt = $body['data'][0]['revised_prompt'] ?? null; - - // Generate filename (OpenAI always returns PNG) - $filename = sanitize_file_name($post->post_title) . '_' . $device_type . '_' . $index . '_' . time() . '.png'; - - // Download and register in WordPress Media Library - $attachment_id = wp_insert_attachment([ - 'post_mime_type' => 'image/png', - 'post_title' => $post->post_title . ' ' . ucfirst($device_type) . ' Image ' . $index, - 'post_content' => '', - 'post_status' => 'inherit' - ], $image_url, $post_id); - - if (!is_wp_error($attachment_id)) { - // Add custom metadata - update_post_meta($attachment_id, '_igny8_image_type', $device_type); - update_post_meta($attachment_id, '_igny8_provider', 'openai'); - update_post_meta($attachment_id, '_igny8_section', 'Section ' . $index); - - // Get attachment URL - $attachment_url = wp_get_attachment_url($attachment_id); - - return [ - 'success' => true, - 'attachment_id' => $attachment_id, - 'image_url' => $attachment_url, - 'provider' => 'openai', - 'device_type' => $device_type, - 'index' => $index, - 'revised_prompt' => $revised_prompt - ]; - } else { - return ['success' => false, 'error' => 'Failed to register image: ' . $attachment_id->get_error_message()]; - } - } - } else { - return ['success' => false, 'error' => 'HTTP ' . $response_code . ' error']; - } - } - } catch (Exception $e) { - return ['success' => false, 'error' => 'Exception: ' . $e->getMessage()]; - } - - return ['success' => false, 'error' => 'Unknown error occurred']; -} diff --git a/igny8-wp-plugin-for-reference-olny/assets/css/core-backup.css b/igny8-wp-plugin-for-reference-olny/assets/css/core-backup.css deleted file mode 100644 index 666107d3..00000000 --- a/igny8-wp-plugin-for-reference-olny/assets/css/core-backup.css +++ /dev/null @@ -1,3039 +0,0 @@ -/* IGNY8 UNIFIED CORE CSS β A-Z COMPACT */ -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap'); - -.is-dismissible {display: none} -#wpcontent{padding-left: 0px} - -/* === 1. TOKENS === */ -:root { - /* Primary Brand Blue (Rocket Cyan-based) */ - --blue: #0693e3; /* Rocket vivid cyan blue β primary brand & main CTA */ - --blue-dark: #0472b8; /* Darkened cyan for hover / active / gradient depth */ - - /* Success Green (cooler to match cyan) */ - --green: #0bbf87; /* Slightly cooler teal-green for success states */ - --green-dark: #08966b; /* Deeper teal-green for hover / active */ - - /* Amber / Warning (warmed up to complement cyan) */ - --amber: #ff7a00; /* Rocket's vivid orange for highlight / warning */ - --amber-dark: #cc5f00; /* Darker orange for hover / strong warning */ - - /* Danger / Destructive */ - --red-dark: #d13333; /* Refreshed red with better contrast against cyan */ - - --purple: #5d4ae3; /* Purple for highlighting / special emphasis */ - --purple-dark:#3a2f94; /* Darker purple for hover / active */ - - --navy-bg: #0d1b2a; /* Sidebar background */ - --navy-bg-2: #142b3f; /* Slightly lighter navy, hover/active */ - --surface: #f8fafc; /* Page background (soft gray-white) */ - --panel: #ffffff; /* Cards / panel foreground */ - --panel-2: #f1f5f9; /* Sub-panel / hover card background */ - - --text: #555a68; /* main headings/body text */ - --text-dim: #64748b; /* secondary/subtext */ - --text-light: #e5eaf0; /* text on dark sidebar */ - --stroke: #e2e8f0; /* table/grid borders and dividers */ - - --radius:6px;--sidebar-width:220px;--header-height:75px - - /* === UNIFIED GRADIENTS === */ - --igny8-gradient-blue: linear-gradient(135deg, var(--blue) 0%, var(--blue-dark) 100%); - --igny8-gradient-panel: linear-gradient(180deg, var(--panel) 0%, var(--panel-2) 100%); - --igny8-gradient-success: linear-gradient(135deg, var(--green) 0%, var(--green-dark) 100%); - --igny8-gradient-warning: linear-gradient(135deg, var(--amber) 0%, var(--amber-dark) 100%); - --igny8-gradient-danger: linear-gradient(135deg, #ef4444 0%, var(--red-dark) 100%); - --igny8-gradient-info: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); - --igny8-gradient-purple: linear-gradient(135deg, var(--purple) 0%, var(--purple-dark) 100%); - --igny8-gradient-gray: linear-gradient(135deg, #6b7280 0%, #374151 100%); - --igny8-gradient-light: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%); - --igny8-gradient-dark: linear-gradient(135deg, #1f2937 0%, #111827 100%); - - /* === UNIFIED BACKGROUNDS === */ - --igny8-bg-success: #d4edda; - --igny8-bg-success-border: #c3e6cb; - --igny8-bg-success-text: #155724; - --igny8-bg-warning: #fff3cd; - --igny8-bg-warning-border: #ffeaa7; - --igny8-bg-warning-text: #856404; - --igny8-bg-danger: #f8d7da; - --igny8-bg-danger-border: #f5c6cb; - --igny8-bg-danger-text: #721c24; - --igny8-bg-info: #d1ecf1; - --igny8-bg-info-border: #bee5eb; - --igny8-bg-info-text: #0c5460; - --igny8-bg-light: #f8f9fa; - --igny8-bg-light-border: #e9ecef; - --igny8-bg-light-text: #495057; - } - - .igny8-card-header.gradient { background: var(--igny8-gradient-panel); padding:10px 14px; border-radius: var(--radius) var(--radius) 0 0; } - -/* === 1.5. UNIFIED BACKGROUNDS, BADGES & GRADIENTS === */ -/* Background Utilities */ -.igny8-bg-success { background: var(--igny8-bg-success); border: 1px solid var(--igny8-bg-success-border); color: var(--igny8-bg-success-text); } -.igny8-bg-warning { background: var(--igny8-bg-warning); border: 1px solid var(--igny8-bg-warning-border); color: var(--igny8-bg-warning-text); } -.igny8-bg-danger { background: var(--igny8-bg-danger); border: 1px solid var(--igny8-bg-danger-border); color: var(--igny8-bg-danger-text); } -.igny8-bg-info { background: var(--igny8-bg-info); border: 1px solid var(--igny8-bg-info-border); color: var(--igny8-bg-info-text); } -.igny8-bg-light { background: var(--igny8-bg-light); border: 1px solid var(--igny8-bg-light-border); color: var(--igny8-bg-light-text); } - -/* Gradient Backgrounds */ -.igny8-gradient-blue { background: var(--igny8-gradient-blue); } -.igny8-gradient-success { background: var(--igny8-gradient-success); } -.igny8-gradient-warning { background: var(--igny8-gradient-warning); } -.igny8-gradient-danger { background: var(--igny8-gradient-danger); } -.igny8-gradient-info { background: var(--igny8-gradient-info); } -.igny8-gradient-purple { background: var(--igny8-gradient-purple); } -.igny8-gradient-gray { background: var(--igny8-gradient-gray); } -.igny8-gradient-light { background: var(--igny8-gradient-light); } -.igny8-gradient-dark { background: var(--igny8-gradient-dark); } - -/* Unified Badge System */ -.igny8-badge { padding: 4px 10px; border-radius: 4px; font-size: 12px; font-weight: 500; color: #fff; white-space: nowrap; display: inline-block; } -.igny8-badge-primary { background: var(--blue); } -.igny8-badge-success { background: var(--green); } -.igny8-badge-warning { background: var(--amber); } -.igny8-badge-danger { background: #ef4444; } -.igny8-badge-info { background: #3b82f6; } -.igny8-badge-purple { background: var(--purple); } -.igny8-badge-gray { background: #6b7280; } -.igny8-badge-dark-red { background: var(--red-dark); } -.igny8-badge-outline { background: transparent; border: 1px solid rgba(255,255,255,0.3); color: rgba(255,255,255,0.9); } - -/* Badge with Gradients */ -.igny8-badge-gradient-blue { background: var(--igny8-gradient-blue); } -.igny8-badge-gradient-success { background: var(--igny8-gradient-success); } -.igny8-badge-gradient-warning { background: var(--igny8-gradient-warning); } -.igny8-badge-gradient-danger { background: var(--igny8-gradient-danger); } -.igny8-badge-gradient-info { background: var(--igny8-gradient-info); } -.igny8-badge-gradient-purple { background: var(--igny8-gradient-purple); } - -/* Badge Sizes */ -.igny8-badge-sm { padding: 2px 6px; font-size: 10px; } -.igny8-badge-lg { padding: 6px 14px; font-size: 14px; } - -/* Badge with Icons */ -.igny8-badge-icon { display: inline-flex; align-items: center; gap: 4px; } -.igny8-badge-icon .dashicons { font-size: 12px; } - -/* Title and Status with Badge Layouts */ -.igny8-title-with-badge, .igny8-status-with-badge { display: flex; align-items: center; justify-content: space-between; gap: 8px; } -.igny8-title-text, .igny8-status-text { flex: 1; } -.igny8-title-actions, .igny8-status-actions { display: flex; align-items: center; gap: 4px; } - -/* Progress Bar Gradients */ -.igny8-progress-bar { background: var(--panel-2); border-radius: 10px; height: 24px; overflow: hidden; position: relative; } -.igny8-progress-fill { background: var(--igny8-gradient-blue); transition: width 0.5s ease; position: relative; height: 100%; } -.igny8-progress-fill-success { background: var(--igny8-gradient-success); } -.igny8-progress-fill-warning { background: var(--igny8-gradient-warning); } -.igny8-progress-fill-danger { background: var(--igny8-gradient-danger); } - -/* Button Gradients */ -.igny8-btn-gradient-blue { background: var(--igny8-gradient-blue); } -.igny8-btn-gradient-success { background: var(--igny8-gradient-success); } -.igny8-btn-gradient-warning { background: var(--igny8-gradient-warning); } -.igny8-btn-gradient-danger { background: var(--igny8-gradient-danger); } -.igny8-btn-gradient-info { background: var(--igny8-gradient-info); } -.igny8-btn-gradient-purple { background: var(--igny8-gradient-purple); } -.igny8-btn-gradient-gray { background: var(--igny8-gradient-gray); } - -/* Card Gradients */ -.igny8-card-gradient { background: var(--igny8-gradient-panel); } -.igny8-card-gradient-blue { background: var(--igny8-gradient-blue); color: white; } -.igny8-card-gradient-success { background: var(--igny8-gradient-success); color: white; } -.igny8-card-gradient-warning { background: var(--igny8-gradient-warning); color: white; } -.igny8-card-gradient-danger { background: var(--igny8-gradient-danger); color: white; } - -/* Modal and Overlay Backgrounds */ -.igny8-modal-bg { background: rgba(0,0,0,0.5); } -.igny8-overlay-light { background: rgba(255,255,255,0.9); } -.igny8-overlay-dark { background: rgba(0,0,0,0.8); } - -/* Status Indicators */ -.igny8-status-success { background: var(--igny8-bg-success); border-left: 4px solid var(--green); } -.igny8-status-warning { background: var(--igny8-bg-warning); border-left: 4px solid var(--amber); } -.igny8-status-danger { background: var(--igny8-bg-danger); border-left: 4px solid #ef4444; } -.igny8-status-info { background: var(--igny8-bg-info); border-left: 4px solid #3b82f6; } - -/* === 2. RESET & BASE === */ -*{margin:0;padding:0;box-sizing:border-box} -body{font-family:Inter,system-ui,-apple-system,Segoe UI,Roboto,sans-serif;background:var(--surface);color:var(--text);line-height:1.4;font-size:14px;font-weight:600;} -a{text-decoration:none;color:inherit} - -/* === 3. LAYOUT === */ -.igny8-page-wrapper{display:flex;min-height:100vh;width: 100%;margin: auto;} -.igny8-main-area{flex:1;display:flex;flex-direction:column;background:var(--surface)} -.igny8-content{flex:1;padding:20px;background: #fff;box-shadow: 0 2px 6px 3px rgba(0, 0, 0, .08)} -.igny8-footer{background:var(--navy-bg-2);padding:10px 20px;text-align:center;color:var(--text-light);font-size:13px;border-top:1px solid rgba(255,255,255,.1)} - - -/* === 4. SIDEBAR === */ -.igny8-sidebar{width:var(--sidebar-width);background:var(--navy-bg-2);color:var(--text-light);display:flex;flex-direction:column;padding:16px 12px} -.igny8-sidebar-logo{font-size:20px;font-weight:600;text-align:center;margin-bottom:16px} -.igny8-version-badge{text-align:center;margin-bottom:16px} -.igny8-version-badge .igny8-badge{font-size:11px;font-weight:600;letter-spacing:0.5px} -.igny8-breadcrumb{font-size:12px;color:rgba(255,255,255,0.7);margin-bottom:20px;text-align:center;line-height:1.4} -.igny8-breadcrumb-link{color:#f59e0b;text-decoration:none;transition:color .2s} -.igny8-breadcrumb-link:hover{color:#fff} -.igny8-breadcrumb-separator{margin:0 6px;opacity:0.6} -.igny8-breadcrumb-current{color:rgba(255,255,255,0.9);font-weight:500} -.igny8-sidebar-nav{display:flex;flex-direction:column;gap:8px} -.igny8-sidebar-link{display:flex;align-items:center;gap:10px;font-size:14px;padding:8px 12px;border-radius:var(--radius);color:var(--text-light);transition:background .2s} -.igny8-sidebar-link:hover{background:rgba(255,255,255,.08);color: #fff;} -.igny8-sidebar-link.active{background:var(--blue);color:#fff} -.igny8-sidebar-metrics{margin-top:auto;display:flex;flex-direction:column;gap:8px;padding-top:24px;border-top:1px solid rgba(255,255,255,.1)} -.igny8-sidebar-metric{display:flex;justify-content:space-between;font-size:13px} -.igny8-sidebar-metric .label{opacity:.8} -.igny8-sidebar-footer-container{position:relative;width:100%;margin-top:auto;padding:16px 12px 16px 12px} -.igny8-sidebar-footer{border-top:1px solid rgba(255,255,255,.1);padding-top:12px;display:flex;flex-direction:column;gap:6px} - -/* === 5. HEADER === */ -.igny8-header{display:flex;align-items:center;justify-content:space-between;background:var(--navy-bg-2);height:var(--header-height);padding:0 20px;border-bottom:1px solid rgba(255,255,255,.1);color:var(--text-light)} -.igny8-header-left{display:flex;align-items:center;gap:20px} -.igny8-page-title h1{font-size:22px;font-weight:600;color:#fff;margin:0 0 4px 0;gap:20px} -.igny8-page-description{font-size:13px;color:rgba(255,255,255,0.8);margin:0;line-height:1.3} -.igny8-breadcrumbs{font-size:13px;color:var(--text-light);opacity:.8} -.igny8-breadcrumbs a{color:var(--blue);font-weight:500} -.igny8-header-center{display:flex;align-items:center;justify-content:center;flex:1;text-align:center} -.igny8-marquee-ticker{font-size:13px;color:var(--text-light);white-space:nowrap;overflow:hidden;text-overflow:ellipsis} -.igny8-header-right{display:flex;align-items:center;gap:20px} -.igny8-metrics{display:flex;align-items:center;gap:8px} -.igny8-badge{padding:4px 10px;border-radius:4px;font-size:12px;font-weight:500;color:#fff;white-space:nowrap} -.igny8-badge.igny8-btn-primary{background:var(--blue)} -.igny8-badge.igny8-btn-success{background:var(--green)} -.igny8-badge.igny8-btn-warning{background:var(--amber)} -.igny8-badge.igny8-btn-outline{background:transparent;border:1px solid rgba(255,255,255,0.3);color:rgba(255,255,255,0.9)} -.igny8-header-icons{display: flex; - align-items: center; - gap: 14px; - margin: 0 20px 10px 0; - align-content: center; - } -.igny8-header-icons .dashicons{font-size:26px;cursor:pointer;color:var(--text-light);transition:color .2s} -.igny8-header-icons .dashicons:hover{color:var(--blue)} - -/* Fix for dashicons in buttons */ -.igny8-btn .dashicons { font-family: dashicons !important; font-size: 16px; line-height: 1; text-decoration: none; font-weight: normal; font-style: normal; vertical-align: top; margin-right: 6px; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } -.igny8-icon-only svg { font-family: inherit !important; font-style: normal !important; font-weight: normal !important; } -.igny8-actions-cell button { font-family: inherit !important; } -.igny8-actions-cell button svg { font-family: inherit !important; } -.dashicons { font-variant: normal; text-transform: none; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } - -/* === 6. BUTTONS === */ -.igny8-btn{display:inline-flex;align-items:center;justify-content:center;padding:4px 12px;font-size:13px;font-weight:500;line-height:1.3;border:none;border-radius:var(--radius,6px);cursor:pointer;transition:all .2s ease-in-out;color:#fff;text-decoration:none;white-space:nowrap;margin: 0 5px} -.igny8-btn:disabled,.igny8-btn.disabled{opacity:.5;cursor:not-allowed} -.igny8-btn-primary{background:var(--blue,#3b82f6)} -.igny8-btn-primary:hover{background:var(--blue-dark,#2563eb)} -.igny8-btn-secondary{background:var(--text-dim,#64748b);color:#fff} -.igny8-btn-secondary:hover{background:#475569} -.igny8-btn-outline{background:transparent;border:1px solid var(--stroke,#e2e8f0);color:var(--text,#0f172a)} -.igny8-btn-outline:hover{background:rgba(0,0,0,.05)} -.igny8-btn-success{background:var(--green,#10b981)} -.igny8-btn-success:hover{background:var(--green-dark,#059669)} -.igny8-btn-accent{background:var(--amber,#f59e0b)} -.igny8-btn-accent:hover{background:var(--amber-dark,#d97706)} -.igny8-btn-danger{background:#ef4444} -.igny8-btn-danger:hover{opacity:.9} -.igny8-btn-icon{width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center} - -/* === 9. TABLE === */ -.igny8-table{width:100%;border-collapse:collapse;background:var(--panel);border:1px solid var(--stroke);border-radius:4px;overflow:visible;font-size:14px} -.igny8-table thead th{background:var(--navy-bg-2);color:var(--text-light);font-weight:500;text-align:left;padding:6px 10px;border-bottom:1px solid var(--stroke)} -.igny8-table tbody td{padding:6px 10px;border-bottom:1px solid var(--stroke);color:var(--text);overflow:visible;position:relative} -.igny8-table tbody tr:hover{background:var(--panel-2)} -.igny8-table th {font-size: 110%;} -.igny8-col-checkbox{width:36px;text-align:center} -.igny8-col-actions{width:80px;text-align:center} - -/* === 10. PAGINATION === */ -.igny8-pagination{margin: 25px 0;display:flex;justify-content:center;align-items:center;} -.igny8-btn-pagination{height:auto;padding:3px 9px;font-size:12px;border-radius:4px;color:var(--blue);border:1px solid var(--blue);background:transparent;cursor:pointer;transition:all .2s} -.igny8-btn-pagination:hover:not(:disabled){background:var(--blue);color:#fff} -.igny8-btn-pagination:disabled{opacity:.4;cursor:default} -.igny8-btn-pagination.active{background:var(--blue);color:#fff;border-color:var(--blue)} - -/* === 11. MODAL === */ -.igny8-modal{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.5);display:none;align-items:center;justify-content:center;z-index:10000} -.igny8-modal.open{display:flex} -.igny8-modal-content{background:#fff;border-radius:6px;box-shadow:0 20px 40px rgba(0,0,0,.25);max-width:500px;width:90%;max-height:90vh;overflow:auto} -.igny8-modal-header,.igny8-modal-footer{padding:12px 16px;border-bottom:1px solid var(--stroke);display:flex;justify-content:space-between;align-items:center} -.igny8-modal-footer{border-top:1px solid var(--stroke)} -.igny8-btn-close{background:none;border:none;font-size:18px;cursor:pointer;color:var(--text-dim)} - -/* === 12. UTILITIES === */ -.igny8-flex{display:flex}.igny8-ml-auto{margin-left:auto} -.igny8-text-muted{color:var(--text-dim)}.igny8-mb-20{margin-bottom:20px} -.igny8-error-box{background:#f8d7da;color:#721c24;border:1px solid #f5c6cb;border-radius:4px;padding:12px;margin:10px 0;font-size:13px} - -/* === 13. RESPONSIVE === */ -@media(max-width:768px){.igny8-sidebar{display:none}.igny8-filters{flex-direction:column;align-items:flex-start}.igny8-filter-bar{flex-wrap:wrap}.igny8-table-actions{flex-wrap:wrap;gap:8px}} - -.is-dismissible {display: none} -#wpcontent{padding-left: 0px} - -.igny8-metrics-row{display:flex;justify-content:space-between;align-items:center;margin-bottom:15px;} -.igny8-page-title{font-size:1.4rem;font-weight:600;margin:0;} -.igny8-metrics-bar{display:flex;gap:8px;} -.igny8-badge{padding:4px 10px;border-radius:4px;font-size:.85rem;font-weight:500;color:#fff;} -.igny8-badge-primary{background:var(--blue-dark)} -.igny8-badge-success{background:#10b981;} -.igny8-badge-warning{background:#f59e0b;} -.igny8-badge-info{background:#3b82f6;} - -/* === 7. Filters === */ -.igny8-filters{display:flex;align-items:center;justify-content: center;gap:10px;margin:25px 0} -.igny8-filter-bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;background:#f9fafb;border:1px solid #e2e8f0;border-radius:6px;padding:8px 12px;box-shadow: 0 2px 6px 3px rgba(0, 0, 0, .08)} -.igny8-filter-group{position:relative;display:flex;align-items:center;gap:6px} -.igny8-search-input{width:200px;padding:6px 10px;border:1px solid var(--igny8-stroke);border-radius:4px;font-size:14px} -.igny8-filter-actions{display:flex;align-items:center;gap:8px} - -/* === 8. Dropdowns === */ -.select{position:relative;min-width:120px} -.select-btn{display:flex;align-items:center;justify-content:space-between;width:100%;padding:6px 10px;font-size:13px;background:#fff;border:1px solid var(--igny8-stroke);border-radius:4px;cursor:pointer;box-shadow: 0 2px 6px 3px rgba(0, 0, 0, .08);color:var(--text);font-weight:500} -.select-list{display:none;position:absolute;top:calc(100% + 4px);left:0;right:0;background:#fff;border:1px solid var(--igny8-stroke);border-radius:4px;box-shadow:0 2px 6px rgba(0,0,0,.08);z-index:999999;max-height:200px;overflow-y:auto} -.select-item{padding:6px 10px;font-size:14px;cursor:pointer;border-bottom:1px solid #f1f5f9} -.select-item:last-child{border-bottom:none} -.select-item:hover{background:#f1f5f9} -.select.open .select-list {display:block;} -.select-arrow {font-size: 10px} -.igny8-table-actions{display:flex;align-items:center;gap:8px;justify-content: space-between; margin-bottom: 10px;} - -/* === 15. Icon Buttons === */ - -.igny8-icon-only{background:none;border:none;padding:0;margin:0 4px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:opacity .2s} -.igny8-icon-only svg{width:18px;height:18px} -.igny8-icon-edit svg{color:var(--blue,#3b82f6)} -.igny8-icon-save svg {color: #fff;} -.igny8-icon-save {background-color: var(--success, #10b981);} -.igny8-icon-cancel svg {color: #fff;} -.igny8-icon-cancel {background-color: var(--text-dim, #64748b);} -.igny8-icon-edit:hover svg{color:var(--text-dim)} -.igny8-icon-delete svg{color:#ef4444} -.igny8-icon-delete:hover svg{color:#dc2626} -.igny8-icon-save{background:var(--green,#10b981);color:#fff;border-radius:8px;padding:0px;transition:background .2s} -.igny8-icon-save:hover{background:var(--green-dark,#059669)} -.igny8-icon-cancel{background:var(--text-dim,#64748b);color:#fff;border-radius:8px;padding:0px;transition:background .2s} -.igny8-icon-cancel:hover{background:var(--text,#0f172a)} -.igny8-icon-play svg{color:var(--blue,#3b82f6)} -.igny8-icon-play:hover svg{color:var(--text-dim)} -.igny8-icon-external svg{color:var(--text-dim,#64748b)} -.igny8-icon-external:hover svg{color:var(--blue,#3b82f6)} -.igny8-actions{white-space:nowrap} - -.igny8-page-title {display: flex;flex-direction: row;justify-content: space-around;align-items: center;flex-wrap: wrap;} -.igny8-sidebar-logo h2 {color: #b8d3ff;font-size: 1.3em;margin: 10px 0;font-weight: 900} - -/* ---------- GRID LAYOUTS ---------- */ - -.igny8-grid { display: grid; gap: 20px; } -.igny8-grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; align-items: stretch; } -.igny8-grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px; margin:50px 0;} -.igny8-grid-4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 20px; } - - -.igny8-module-cards-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:22px;margin:24px 0;} -.igny8-dashboard-cards,.igny8-grid-3{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:22px;margin:5px;} - -.igny8-grid-4{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:22px;margin-bottom:24px;} - - - -.igny8-card { background: var(--panel); border: 1px solid var(--stroke); border-radius: var(--radius); padding: 18px; box-shadow: 0 2px 6px rgba(0,0,0,0.10), 0 4px 10px rgba(13,27,42,0.06); transition: box-shadow .25s ease, transform .2s ease; height: auto; display: flex; flex-direction: column; } -.igny8-card:hover { transform: translateY(-2px); box-shadow: 0 6px 14px rgba(0,0,0,0.14), 0 8px 20px rgba(13,27,42,0.10); } -.igny8-card-header { display:flex; align-items:center; justify-content:space-between; margin-bottom:12px; background:linear-gradient(90deg,var(--blue) 0%,var(--blue-dark) 100%); color:#fff; padding:12px 16px; border-radius:var(--radius) var(--radius) 0 0; margin:-10px -10px 12px -10px; } -.igny8-card-title { font:600 15px/1.4 'Inter',system-ui,sans-serif; margin:10px; color: #fff; display: flex;justify-content: space-between;} -.igny8-card-body { font:400 14px/1.55 'Inter',system-ui,sans-serif; color:var(--text); flex: 1; } - - -.igny8-help-text { font-size:13px; color:var(--text-dim); margin-top:6px; } -.igny8-status-badge { font-size:12px; font-weight:500; border-radius:4px; padding:2px 6px; line-height:1.2; } -.igny8-status-badge.mapped { background:#d1fae5; color:#065f46; } -.igny8-status-badge.unmapped { background:#fee2e2; color:#991b1b; } -.igny8-status-ok { color:var(--green); font-weight:500; } -.igny8-form-row { margin-bottom:18px; } -.igny8-form-row label { font-weight:500; font-size:14px; display:block; margin-bottom:6px; } -.igny8-form-row input[type="text"], .igny8-form-row select, .igny8-form-row textarea { width:100%; padding:8px 12px; font-size:14px; border:1px solid var(--stroke); border-radius:var(--radius); background:#fff; transition:border .2s, box-shadow .2s; } -.igny8-form-row input:focus, .igny8-form-row select:focus, .igny8-form-row textarea:focus { border-color:var(--blue); box-shadow:0 0 0 2px rgba(59,130,246,.18); outline:none; } -.igny8-radio-group, .igny8-checkbox-group { display:flex; flex-wrap:wrap; gap:16px; } -.igny8-radio-option, .igny8-checkbox-option { display:flex; align-items:center; gap:6px; font-size:14px; cursor:pointer; } -.igny8-toggle-switch { position:relative; width:42px; height:22px; display:inline-block; } -.igny8-toggle-switch input { opacity:0; width:0; height:0; } -.igny8-toggle-slider { position:absolute; cursor:pointer; top:0; left:0; right:0; bottom:0; background:#cbd5e1; border-radius:22px; transition:background .3s; } -.igny8-toggle-slider:before { content:""; position:absolute; height:18px; width:18px; left:2px; bottom:2px; background:#fff; border-radius:50%; transition:transform .3s, box-shadow .3s; box-shadow:0 1px 2px rgba(0,0,0,0.3); } -.igny8-toggle-switch input:checked + .igny8-toggle-slider { background:var(--blue); } -.igny8-toggle-switch input:checked + .igny8-toggle-slider:before { transform:translateX(20px); } -.igny8-table-compact th, .igny8-table-compact td { padding:6px 8px; font-size:13px; } -.igny8-flex { display:flex; gap: 20px} -.igny8-ml-auto { margin-left:auto; } -.igny8-pad-5 { padding:5px; } -.igny8-mb-20 { margin-bottom:20px; } -@media (max-width:1024px) { .igny8-grid-3 { grid-template-columns:repeat(2, minmax(0, 1fr)); } .igny8-grid-4 { grid-template-columns:repeat(2, minmax(0, 1fr)); } } -@media (max-width:768px) { .igny8-grid-2, .igny8-grid-3, .igny8-grid-4 { grid-template-columns:1fr; } } - -/* ---------- THEME ADD-ONS ---------- */ -/* Gradient helpers */ - -/* ---------- GLOBAL ELEMENTS ---------- */ -.igny8-content h1,.igny8-settings h2,.igny8-welcome-section h2{font:700 22px/1.3 'Inter',system-ui,sans-serif;color:var(--navy-bg);margin-bottom:16px;} -.igny8-content p,.igny8-settings-section p,.igny8-welcome-text{font-size:14px;color:var(--text-dim);} -.igny8-settings-section h3,.igny8-card h3{font:600 16px/1.3 'Inter',system-ui,sans-serif;margin-bottom:12px;color:var(--blue-dark);} -.igny8-help-text{font-size:12px;color:var(--text-dim);margin-top:8px;} - -/* ---------- SUBHEADER ---------- */ -.igny8-submenu-buttons a{font-size:13px;padding:6px 16px;border-radius:var(--radius);background:var(--blue);color:#fff;font-weight:500;border:none;box-shadow:0 2px 6px rgba(0,0,0,.15);transition:all .2s ease;} -.igny8-submenu-buttons a:hover{background:var(--blue-dark);transform:translateY(-1px);box-shadow:0 4px 10px rgba(0,0,0,.2);} -.igny8-submenu-buttons a.active{background:var(--green);} - - -/* ---------- CARDS ---------- */ -.igny8-card,.igny8-module-card{margin: 15px 0;border:1px solid var(--stroke);background:var(--panel);border-radius:var(--radius);padding:10px;box-shadow:0 2px 6px rgba(0,0,0,.08),0 4px 10px rgba(13,27,42,.06);transition:all .25s ease; width: 100%;} -.igny8-card:hover,.igny8-module-card:hover{transform:translateY(-2px);box-shadow:0 6px 14px rgba(0,0,0,.12),0 8px 22px rgba(13,27,42,.10);} -.igny8-card-header{border-bottom:1px solid var(--stroke);padding-bottom:6px;margin-bottom:12px;} -h3,h4,h5,h6,igny8-card-title,.igny8-card-header h3{margin:0;font:600 16px/1.4 'Inter',system-ui,sans-serif;color:var(--blue-dark);} -.igny8-card-body{font-size:14px;color:var(--text);padding: 5px 15px;} -.igny8-card-actions{margin-top:12px;display:flex;gap:12px;} - -/* ---------- MODULE CARD HEADER ACCENTS ---------- */ -.igny8-module-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;padding-bottom:6px;border-bottom:1px solid var(--stroke);} -.igny8-module-header h4{font:600 16px/1.3 'Inter',system-ui,sans-serif;margin:0;color:var(--navy-bg);} -.igny8-module-card:nth-child(1) .igny8-module-header h4{border-left:3px solid var(--blue);padding-left:6px;} -.igny8-module-card:nth-child(2) .igny8-module-header h4{border-left:3px solid var(--amber-dark);padding-left:6px;} -.igny8-module-card:nth-child(3) .igny8-module-header h4{border-left:3px solid var(--green);padding-left:6px;} -.igny8-module-card:nth-child(4) .igny8-module-header h4{border-left:3px solid var(--blue-dark);padding-left:6px;} -.igny8-module-card:nth-child(5) .igny8-module-header h4{border-left:3px solid var(--amber-dark);padding-left:6px;} -.igny8-module-card:nth-child(6) .igny8-module-header h4{border-left:3px solid var(--green-dark);padding-left:6px;} -.igny8-module-card:nth-child(7) .igny8-module-header h4{border-left:3px solid var(--blue);padding-left:6px;} -.igny8-module-icon{background:rgba(59,130,246,0.08);padding:8px;border-radius:50%;display:flex;align-items:center;justify-content:center;} -.igny8-module-description p{font-size:14px;color:var(--text-dim);line-height:1.5;margin-bottom:12px;} - -/* ---------- FORM ELEMENTS ---------- */ -.igny8-form-row{margin-bottom:16px;} -.igny8-form-row label{font:600 13px/1.4 'Inter',system-ui,sans-serif;margin-bottom:5px;display:block;color:var(--navy-bg);} -.igny8-form-row input[type="text"],.igny8-form-row textarea,.igny8-form-row select{width:100%;padding:7px 12px;font-size:14px;border:1px solid var(--stroke);border-radius:var(--radius);transition:border .2s,box-shadow .2s;} -.igny8-form-row input:focus,.igny8-form-row textarea:focus,.igny8-form-row select:focus{border-color:var(--blue);box-shadow:0 0 0 2px rgba(59,130,246,.18);outline:none;} -.igny8-radio-group,.igny8-checkbox-group{display:flex;flex-wrap:wrap;gap:14px;} -.igny8-radio-option,.igny8-checkbox-option{display:flex;align-items:center;gap:6px;font-size:13px;cursor:pointer;} -.igny8-radio-option input:checked+label{color:var(--blue-dark);font-weight:600;} -.igny8-checkbox-option input:checked+label{color:var(--green-dark);font-weight:600;} -.igny8-form-row textarea[name*="prompt"]{border-left:3px solid var(--amber-dark);} -.igny8-form-row select[name*="style"]{border-left:3px solid var(--green);} -.igny8-form-row input[type="text"]:focus{border-color:var(--blue-dark);} -/*textarea[name*="prompt"]{border-left:3px solid var(--amber-dark);}*/ -.igny8-textarea-orange {border-left: 3px solid var(--amber-dark);} -.igny8-textarea-green {border-left: 3px solid var(--green);} -.igny8-textarea-blue {border-left: 3px solid var(--blue);} - - -/* ---------- TABLES ---------- */ -.igny8-table-wrapper table,.igny8-table{width:100%;border-collapse:collapse;font-size:13px;} -.igny8-table-wrapper th,.igny8-table-wrapper td,.igny8-table th,.igny8-table td{border:1px solid var(--stroke);padding:6px 8px;text-align:left;} -.igny8-table-wrapper thead{background:var(--panel-2);} -.igny8-table-wrapper th,.igny8-table th{color:var(--navy-bg);font-weight:600;} -.igny8-table thead{background:var(--navy-bg);color:#fff;} -.igny8-table td code{color:var(--blue-dark);} -.igny8-status-ok{color:var(--green-dark);font-weight:500;} - -/* ---------- BUTTONS ---------- */ -.igny8-btn,.button.button-primary,#submit.button-primary{display:inline-flex;align-items:center;justify-content:center;padding:5px 10px;font-size:12px;font-weight:500;border-radius:var(--radius);cursor:pointer;transition:all .25s ease;text-decoration:none;box-shadow:0 2px 6px rgba(0,0,0,.15);} -.igny8-btn-primary{background:var(--blue);color:#fff;border:none;} -.igny8-btn-primary:hover{background:var(--blue-dark);transform:translateY(-1px);color: #fff;} -.igny8-btn-outline{background:transparent;border:1px solid var(--stroke);color:var(--text);} -.igny8-btn-outline:hover{background:var(--blue);color:#fff;box-shadow:0 4px 10px rgba(0,0,0,.15);} -.igny8-btn-danger{background:var(--red-dark);color:#fff;} -.igny8-btn-danger:hover{opacity:.9;} -#igny8-export-btn{border-color:var(--blue);color:var(--blue);} -#igny8-export-btn:hover{background:var(--blue);color:#fff;} -#igny8-import-btn{border-color:var(--green);color:var(--green);} -#igny8-import-btn:hover{background:var(--green);color:#fff;} -.button.button-primary,#submit.button-primary{background:var(--green);border:none;color:#fff;} -#submit.button-primary:hover{background:var(--green-dark);transform:translateY(-1px);box-shadow:0 4px 12px rgba(0,0,0,.2);} - -/* ---------- TOGGLE SWITCH ---------- */ -/* Toggle switch styles are defined above at lines 230-235 */ - -/* ---------- ALERT NOTICES ---------- */ -.notice-error{border-left:4px solid var(--red-dark)!important;background:rgba(185,28,28,0.05);} -.notice-warning{border-left:4px solid var(--amber-dark)!important;background:rgba(217,119,6,0.05);} -.notice p{font-size:13px;} - -/* ---------- DASHBOARD QUICK STATS ---------- */ -.igny8-stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(80px,1fr));gap:16px;} -.igny8-stat{text-align:center;background:var(--panel-2);padding:10px;border-radius:var(--radius);box-shadow:inset 0 1px 2px rgba(0,0,0,.05);} -.igny8-stat-number{display:block;font:600 18px/1.2 'Inter',system-ui,sans-serif;color:var(--blue-dark);} -.igny8-stat-label{font-size:13px;color:var(--text-dim);} - -/* ---------- DASHBOARD ACTIVITY ---------- */ -.igny8-activity-list{display:flex;flex-direction:column;gap:8px;} -.igny8-activity-item{display:flex;justify-content:space-between;padding:6px 10px;background:var(--panel-2);border-radius:var(--radius);} -.igny8-activity-time{font-size:13px;color:var(--amber-dark);font-weight:500;} -.igny8-activity-desc{font-size:13px;color:var(--text);} - - - -/* ---------- MODAL CORE ---------- */ -.igny8-modal-content{background:var(--panel);border-radius:var(--radius);width:420px;max-width:90%;margin:auto;padding:0;box-shadow:0 8px 24px rgba(0,0,0,.22),0 12px 32px rgba(13,27,42,.18);font-family:'Inter',system-ui,sans-serif;animation:fadeInScale .25s ease;} -@keyframes fadeInScale{0%{opacity:0;transform:scale(.96);}100%{opacity:1;transform:scale(1);}} - -/* ---------- HEADER ---------- */ -.igny8-modal-header{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid var(--stroke);background:linear-gradient(90deg,var(--panel-2) 0%,#fff 100%);} -.igny8-modal-header h3{margin:0;font:600 16px/1.3 'Inter',system-ui,sans-serif;color:var(--blue-dark);} -.igny8-btn-close{background:transparent;border:none;font-size:20px;line-height:1;color:var(--text-dim);cursor:pointer;transition:color .2s;} -.igny8-btn-close:hover{color:var(--red-dark);} - -/* ---------- BODY ---------- */ -.igny8-modal-body{padding:16px 18px;font-size:14px;color:var(--text);} -.igny8-modal-body p{margin-bottom:10px;} -.igny8-modal-body strong{font-weight:600;color:var(--navy-bg);} -.igny8-modal-body ul{margin:6px 0 0 18px;padding:0;font-size:13px;color:var(--text-dim);line-height:1.4;} -.igny8-modal-body ul li{list-style:disc;} -.igny8-text-danger{color:var(--red-dark);font-weight:500;margin-top:10px;} - -/* ---------- FOOTER ---------- */ -.igny8-modal-footer{display:flex;justify-content:flex-end;gap:10px;padding:14px 18px;border-top:1px solid var(--stroke);background:var(--panel-2);} -.igny8-btn-secondary{background:var(--text-dim);color:#fff;padding:6px 14px;font-size:13px;border:none;border-radius:var(--radius);cursor:pointer;transition:all .25s ease;box-shadow:0 2px 5px rgba(0,0,0,.15);} -.igny8-btn-secondary:hover{background:#475569;transform:translateY(-1px);} -.igny8-btn-danger{background:var(--red-dark);color:#fff;padding:6px 14px;font-size:13px;border:none;border-radius:var(--radius);cursor:pointer;transition:all .25s ease;box-shadow:0 2px 5px rgba(0,0,0,.15);} -.igny8-btn-danger:hover{background:#991b1b;transform:translateY(-1px);} - -/* Automation UI Components */ -.igny8-automation-table { - margin-top: 16px; -} - -.igny8-status-badge { - display: inline-block; - padding: 4px 8px; - border-radius: 4px; - font-size: 12px; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.igny8-status-success { - background: var(--green); - color: white; -} - -.igny8-status-disabled { - background: var(--text-dim); - color: white; -} - -.igny8-toggle { - position: relative; - display: inline-block; - width: 44px; - height: 24px; - cursor: pointer; -} - -.igny8-toggle input { - opacity: 0; - width: 0; - height: 0; -} - -.igny8-toggle-slider { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: var(--text-dim); - border-radius: 24px; - transition: 0.3s; -} - -.igny8-toggle-slider:before { - position: absolute; - content: ""; - height: 18px; - width: 18px; - left: 3px; - bottom: 3px; - background: white; - border-radius: 50%; - transition: 0.3s; -} - -.igny8-toggle input:checked + .igny8-toggle-slider { - background: var(--green); -} - -.igny8-toggle input:checked + .igny8-toggle-slider:before { - transform: translateX(20px); -} - -/* Mode Toggle Row */ -.igny8-mode-toggle-row { - display: flex; - align-items: center; - justify-content: center; - margin: 15px 0; -} - -.igny8-mode-toggle-label { - display: flex; - align-items: center; - gap: 12px; -} - -.igny8-mode-label { - font-size: 14px; - font-weight: 500; - color: var(--text); -} - -/* Cron Schedule Modal */ -.igny8-cron-config { - margin: 20px 0; -} - -.igny8-cron-item { - margin-bottom: 20px; - padding: 16px; - border: 1px solid var(--stroke); - border-radius: var(--radius); - background: var(--panel-2); -} - -.igny8-cron-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 12px; -} - -.igny8-cron-status { - font-size: 12px; - font-weight: 500; -} - -.igny8-cron-url { - display: flex; - align-items: center; - gap: 12px; - background: var(--panel); - padding: 12px; - border-radius: var(--radius); - border: 1px solid var(--stroke); -} - -.igny8-cron-url code { - flex: 1; - background: none; - color: var(--blue); - font-size: 13px; - word-break: break-all; -} - -.igny8-btn-copy { - background: var(--blue); - color: white; - border: none; - padding: 6px 12px; - border-radius: 4px; - font-size: 12px; - cursor: pointer; - transition: background 0.2s ease; -} - -.igny8-btn-copy:hover { - background: var(--blue-dark); -} - -.igny8-cron-info { - margin-top: 24px; - padding: 16px; - background: var(--panel-2); - border-radius: var(--radius); - border: 1px solid var(--stroke); -} - -.igny8-cron-info h4 { - margin: 0 0 8px 0; - font-size: 14px; - color: var(--text); -} - -.igny8-cron-info code { - background: var(--panel); - padding: 4px 8px; - border-radius: 4px; - font-family: monospace; - color: var(--blue); -} - -.igny8-sidebar-divider {border-bottom: 1px solid rgba(255, 255, 255, .1);margin: 10px 0} - -.igny8-pad-sm { padding: 5px; } -.igny8-pad-md { padding: 10px; } -.igny8-pad-lg { padding: 20px; } -.igny8-pad-xl { padding: 30px; } - - -td.igny8-col-actions button.igny8-btn.igny8-btn-success.igny8-btn-sm, td.igny8-col-actions button.igny8-btn.igny8-btn-danger.igny8-btn-sm {padding: 4px;} -th.igny8-col-actions {min-width:150px;} -td.igny8-col-actions {text-align: center;} - -/* === UI LAYER COMPONENTS === */ - -/* Page Layout Components */ -.igny8-main-content { flex: 1; display: flex; flex-direction: column; } -.igny8-header { padding: 10px 0; border-bottom: 1px solid var(--stroke)} -.igny8-header h1 { font-size: 24px; font-weight: 600; color: #fff; margin: 0; } - -/* Module Header */ -.igny8-module-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 20px; } -.igny8-module-info h2 { font-size: 20px; font-weight: 600; margin: 0 0 8px 0; } -.igny8-module-description { color: var(--text-dim); font-size: 14px; margin: 0; } -.igny8-module-kpis { display: flex; gap: 20px; } -.igny8-kpi-item { text-align: center; } -.igny8-kpi-value { display: block; font-size: 24px; font-weight: 700; color: var(--blue); } -.igny8-kpi-label { font-size: 12px; color: var(--text-dim); text-transform: uppercase; } - -/* Submodule Navigation */ -.igny8-submodule-nav { margin-bottom: 20px; } -.igny8-submodule-tabs { display: flex; list-style: none; border-bottom: 1px solid var(--stroke); } -.igny8-submodule-tab { margin-right: 2px; } -.igny8-submodule-tab a { display: block; padding: 12px 16px; color: var(--text-dim); border-bottom: 2px solid transparent; transition: all 0.2s; } -.igny8-submodule-tab.active a, .igny8-submodule-tab a:hover { color: var(--blue); border-bottom-color: var(--blue); } - -/* Submodule Header */ -.igny8-submodule-header { margin-bottom: 20px; } -.igny8-back-link { margin-bottom: 12px; } -.igny8-btn-back { display: inline-flex; align-items: center; gap: 6px; color: var(--text-dim); font-size: 14px; } -.igny8-btn-back:hover { color: var(--blue); } -.igny8-submodule-title { font-size: 18px; font-weight: 600; margin: 0 0 8px 0; } -.igny8-submodule-description { color: var(--text-dim); font-size: 14px; margin: 0; } - -/* Filters Bar */ -.igny8-filters-bar { background: var(--panel); border: 1px solid var(--stroke); border-radius: var(--radius); padding: 16px; margin-bottom: 20px; } -.igny8-filters-row { display: flex; gap: 16px; align-items: end; flex-wrap: wrap; } -.igny8-filter-item { display: flex; flex-direction: column; gap: 4px; min-width: 120px; } -.igny8-filter-label { font-size: 12px; font-weight: 500; color: var(--text); } -.igny8-filter-search, .igny8-filter-text, .igny8-filter-select, .igny8-filter-date { padding: 8px 12px; border: 1px solid var(--stroke); border-radius: var(--radius); font-size: 14px; } -.igny8-search-wrapper { position: relative; } -.igny8-search-icon { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); color: var(--text-dim); } -.igny8-filter-actions { display: flex; gap: 8px; } - -/* Table Actions */ -.igny8-table-actions { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; } -.igny8-actions-primary, .igny8-actions-secondary { display: flex; gap: 8px; } -.igny8-bulk-actions { display: flex; align-items: center; gap: 12px; } -.igny8-bulk-select-all { display: flex; align-items: center; gap: 6px; font-size: 14px; } -.igny8-bulk-action-select { padding: 6px 10px; border: 1px solid var(--stroke); border-radius: var(--radius); } -.igny8-bulk-count { font-size: 12px; color: var(--text-dim); } - -/* Table */ -.igny8-table-wrapper { background: var(--panel); border: 1px solid var(--stroke); border-radius: var(--radius); overflow: hidden; } -.igny8-table { width: 100%; border-collapse: collapse; } -.igny8-table th, .igny8-table td { padding: 12px; text-align: left; border-bottom: 1px solid var(--stroke); } -.igny8-table th { background: var(--panel-2); font-weight: 600; font-size: 13px; } -.igny8-table tbody tr:hover { background: var(--panel-2); } -.igny8-sortable-link { color: inherit; display: flex; align-items: center; gap: 4px; } -.igny8-sortable-link:hover { color: var(--blue); } -td.igny8-align-center, .igny8-align-center { text-align: center; } -.igny8-align-right { text-align: right; } -.igny8-empty-cell { text-align: center; color: var(--text-dim); font-style: italic; padding: 40px; } - -/* Row Actions */ -.igny8-row-actions { display: flex; gap: 8px; align-items: center; } -.igny8-action-separator { color: var(--text-dim); } - -/* Badges */ -.igny8-badge { padding: 4px 8px; border-radius: 12px; font-size: 11px; font-weight: 600; text-transform: capitalize; } -.igny8-badge-default { background: rgba(103, 112, 109, 0.1); color: var(--text); } -.igny8-badge-success { background: rgba(16,185,129,0.1); color: var(--green-dark); } -.igny8-badge-info { background: rgba(59,130,246,0.1); color: var(--blue-dark); } -.igny8-badge-warning { background: rgba(245,158,11,0.1); color: var(--amber-dark); } -.igny8-badge-danger { background: rgba(239,68,68,0.1); color: var(--red-dark); } -.igny8-badge-secondary { background: rgba(100,116,139,0.1); color: var(--text-dim); } -.igny8-badge-dark-red { background: rgba(220,38,38,0.1); color: #dc2626; } - -/* Pagination */ -.igny8-pagination-wrapper { display: flex; justify-content: space-between; align-items: center; padding: 16px; background: var(--panel-2); } -.igny8-pagination-info { font-size: 14px; color: var(--text-dim); } -.igny8-pagination-list { display: flex; list-style: none; gap: 4px; } -.igny8-pagination-btn { display: flex; align-items: center; gap: 4px; padding: 8px 12px; border: 1px solid var(--stroke); background: var(--panel); color: var(--text); border-radius: var(--radius); font-size: 14px; transition: all 0.2s; } -.igny8-pagination-btn:hover { background: var(--blue); color: white; border-color: var(--blue); } -.igny8-btn-current { background: var(--blue); color: white; border-color: var(--blue); } -.igny8-per-page-selector { display: flex; align-items: center; gap: 8px; font-size: 14px; } - -/* Forms */ -.igny8-form-wrapper { background: var(--panel); border: 1px solid var(--stroke); border-radius: var(--radius); padding: 20px; } -.igny8-form-title { font-size: 18px; font-weight: 600; margin-bottom: 20px; } -.igny8-form-fields { display: flex; flex-direction: column; gap: 16px; } - -/* Hidden elements - no inline styles */ -.igny8-count-hidden { display: none; } -/* Legacy notification hidden class - now handled by unified system */ - -/* Module cards grid */ -.igny8-module-cards-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 20px; - margin: 20px 0; -} -.igny8-form-field { display: flex; flex-direction: column; gap: 6px; } -.igny8-field-label { font-size: 14px; font-weight: 500; color: var(--text); } -.igny8-required { color: var(--red-dark); } -.igny8-input, .igny8-textarea, .igny8-select { padding: 10px 12px; border: 1px solid var(--stroke); border-radius: var(--radius); font-size: 14px; } -.igny8-input:focus, .igny8-textarea:focus, .igny8-select:focus { outline: none; border-color: var(--blue); box-shadow: 0 0 0 2px rgba(59,130,246,0.1); } -.igny8-field-description { font-size: 12px; color: var(--text-dim); } -.igny8-form-actions { display: flex; gap: 12px; margin-top: 20px; } - - -/* Layout Helpers */ -.igny8-submodule-layout { display: flex; flex-direction: column} - - -/*Styled Select Components (matching existing select styles) */ -.igny8-styled-select { position: relative; min-width: 120px; } -.igny8-styled-select-btn { display: flex; align-items: center; justify-content: space-between; width: 100%; padding: 6px 10px; font-size: 14px; background: #fff; border: 1px solid var(--igny8-stroke); border-radius: 4px; cursor: pointer; box-shadow: 0 2px 6px 3px rgba(0, 0, 0, .08); } -.igny8-styled-select-options { display: none; position: absolute; top: calc(100% + 4px); left: 0; right: 0; background: #fff; border: 1px solid var(--igny8-stroke); border-radius: 4px; box-shadow: 0 2px 6px rgba(0,0,0,.08); z-index: 999999; max-height: 200px; overflow-y: auto; } -.igny8-styled-select-item { padding: 6px 10px; font-size: 14px; cursor: pointer; border-bottom: 1px solid #f1f5f9; } -.igny8-styled-select-item:last-child { border-bottom: none; } -.igny8-styled-select-item:hover { background: #f1f5f9; } - -.dd-arrow { font-size: 10px; margin-left: 10px; } -.igny8-input-sm { width: 100%; padding: 6px 8px; font-size: 13px; border: 1px solid var(--igny8-stroke); border-radius: 4px; background: #fff; box-sizing: border-box; } -.igny8-text-sm { font-size: 13px; } -.igny8-mb-5 { margin-bottom: 5px; } -.igny8-text-muted { color: var(--text-dim); } -.igny8-p-5 { padding: 5px 10px !important; } -.igny8-text-xs { font-size: 12px !important; } -.igny8-flex { display: flex; } -.igny8-flex-gap-10 { gap: 10px; } -.igny8-styled-select-options { min-width: 250px; padding: 12px; box-sizing: border-box; } -.igny8-dropdown-panel { pointer-events: auto; } -.igny8-dropdown-panel input, .igny8-dropdown-panel button { pointer-events: auto; } -/* Legacy planner notification - now handled by unified system */ - -/* === CHARTS SYSTEM === */ -/* === 14. Charts & Metrics=== */ - - -/* Header Metrics Container - 2 Row Layout */ -.igny8-header .metrics-container { - display: grid; - grid-template-columns: repeat(3, 1fr); - grid-template-rows: repeat(2, 1fr); - gap: 8px; - margin: 0; - height: 100%; - max-width: 600px; -} - -/* Header Metric Cards - Clean Modern Design */ -.igny8-header .igny8-metric-card { - background: rgba(255, 255, 255, 0.08); - border: 1px solid rgba(255, 255, 255, 0.15); - border-radius: 6px; - padding: 6px 8px; - text-align: center; - transition: all 0.2s ease; - cursor: default; - backdrop-filter: blur(8px); - position: relative; - overflow: hidden; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - min-height: 32px; -} - -.igny8-header .igny8-metric-card:hover { - background: rgba(255, 255, 255, 0.12); - border-color: rgba(255, 255, 255, 0.25); - transform: translateY(-1px); -} - -.igny8-header .igny8-metric-card.blue { - border-top: 2px solid var(--blue); -} - -.igny8-header .igny8-metric-card.green { - border-top: 2px solid var(--green); -} - -.igny8-header .igny8-metric-card.amber { - border-top: 2px solid var(--amber); -} - -.igny8-header .igny8-metric-card.purple { - border-top: 2px solid var(--purple); -} - -.igny8-header .igny8-metric-card.orange { - border-top: 2px solid var(--amber); -} - -.igny8-header .igny8-metric-card.red { - border-top: 2px solid var(--red-dark); -} - -.igny8-header .igny8-metric-card.gray { - border-top: 2px solid #6b7280; -} - -.igny8-header .igny8-metric-number { - font-size: 14px; - font-weight: 700; - margin: 0; - color: #ffffff; - text-shadow: 0 1px 2px rgba(0,0,0,0.4); - line-height: 1; -} - -.igny8-header .igny8-metric-label { - font-size: 9px; - font-weight: 500; - margin: 1px 0 0 0; - color: rgba(255, 255, 255, 0.75); - text-transform: uppercase; - letter-spacing: 0.3px; - line-height: 1; -} - - - -/* =================================================================== - STATUS CIRCLE STYLES FOR SYSTEM SUMMARY - =================================================================== */ - -.bg-circle { - width: 40px; - height: 40px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-weight: 600; - font-size: 11px; - color: white; - cursor: help; - transition: all 0.2s ease; - border: 2px solid transparent; -} - -.bg-circle:hover { - transform: scale(1.1); - border-color: rgba(255, 255, 255, 0.3); -} - -.bg-success { - background-color: var(--green); - box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3); -} - -.bg-error { - background-color: var(--red); - box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3); -} - -.bg-success:hover { - background-color: var(--green-dark); - box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4); -} - -.bg-error:hover { - background-color: var(--red-dark); - box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4); -} - -/* =================================================================== - RADIO BUTTON STYLES FOR DEBUG SETTINGS - =================================================================== */ - -input[type="radio"] { - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; - width: 16px; - height: 16px; - border: 2px solid #d0d1d3; - border-radius: 50%; - background-color: var(--panel); - cursor: pointer; - position: relative; - transition: all 0.2s ease; -} - -input[type="radio"]:hover { - border-color: var(--blue); -} - -input[type="radio"]:checked { - border-color: var(--blue); - background-color: var(--blue); -} - -input[type="radio"]:checked::after { - content: ''; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 6px; - height: 6px; - border-radius: 50%; - background-color: white; -} - -input[type="radio"]:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.bg-circle {width: 50px;height: 50px;border-radius: 50%;} -.bg-circle-sm {width: 18px;height: 18px;border-radius: 50%;} -.bg-success {background: var(--green);} -.bg-danger,.bg-error {background: var(--red-dark);} -.bg-warning,.bg-amber {background: var(--amber);} -.bg-info {background: var(--blue);} -.bg-secondary {background: var(--text-dim);} - - -.igny8-form-group, .igny8-radio-group {padding: 5px 0} -.igny8-radio-group {margin: 0 15px} -/* =================================================================== - SYSTEM-WIDE DEBUG TABLE STYLES - =================================================================== */ - -/* ========================================= - Planner Settings Styles - ========================================= */ - .igny8-metrics-compact { - display: grid; - grid-template-columns: repeat(4, auto); - gap: 6px 15px; - padding: 4px 6px; - background: #526e8d3b; - border-radius: 8px; - width: fit-content; - float: right; -} - - .metric { - background: rgba(255, 255, 255, 0.05); - padding: 4px 8px; - border-radius: 6px; - text-align: center; - font-family: 'Inter', sans-serif; - min-width: 90px; - transition: 0.15s ease-in-out; - border-left: 3px solid rgba(255, 255, 255, 0.1); - display: flex; - flex-direction: row-reverse; - gap: 10px; - justify-content: space-between; - } - - .metric:hover { - background: rgba(255,255,255,0.08); - transform: translateY(-1px); - } - - .metric .val { - display: block; - font-size: 14px; - font-weight: 600; - color: #fff; - line-height: 1.2; - } - - .metric .lbl { - font-size: 10px; - letter-spacing: 0.3px; - color: rgba(255,255,255,0.6); - text-transform: uppercase; - } - - /* Color Variants */ - .metric.green { border-left-color: var(--green, #00c985); } - .metric.amber { border-left-color: var(--amber, #f39c12); } - .metric.purple { border-left-color: var(--purple, #9b59b6); } - .metric.blue { border-left-color: var(--blue, #3498db); } - .metric.teal { border-left-color: var(--teal, #1abc9c); } - -/* === DASHBOARD OVERVIEW STYLES === */ - -/* Dashboard Sections */ -.igny8-dashboard-section { - margin-bottom: 24px; - height: 100%; - display: flex; - flex-direction: column; -} - -/* Progress Bar Styles */ -.igny8-progress-item { - margin-bottom: 20px; - padding-bottom: 16px; - border-bottom: 1px solid var(--stroke); -} - -.igny8-progress-item:last-child { - border-bottom: none; - margin-bottom: 0; - padding-bottom: 0; -} - -.igny8-progress-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 8px; -} - -.igny8-progress-label { - font: 500 14px/1.4 'Inter', system-ui, sans-serif; - color: var(--text); -} - -.igny8-progress-percent { - font: 600 14px/1.4 'Inter', system-ui, sans-serif; - color: var(--blue-dark); -} - -.igny8-progress-bar { - width: 100%; - height: 8px; - background: var(--panel-2); - border-radius: 4px; - overflow: hidden; - margin-bottom: 6px; -} - -.igny8-progress-fill { - height: 100%; - border-radius: 4px; - transition: width 0.3s ease; -} - -.igny8-progress-blue { - background: linear-gradient(90deg, var(--blue) 0%, var(--blue-dark) 100%); -} - -.igny8-progress-green { - background: linear-gradient(90deg, var(--green) 0%, var(--green-dark) 100%); -} - -.igny8-progress-amber { - background: linear-gradient(90deg, var(--amber) 0%, var(--amber-dark) 100%); -} - -.igny8-progress-purple { - background: linear-gradient(90deg, #8b5cf6 0%, #7c3aed 100%); -} - -.igny8-progress-text-dim { - background: linear-gradient(90deg, var(--text-dim) 0%, #64748b 100%); -} - -.igny8-progress-red { - background: linear-gradient(90deg, #e53e3e 0%, #c53030 100%); -} - -.igny8-progress-details { - font: 400 12px/1.4 'Inter', system-ui, sans-serif; - color: var(--text-dim); -} - -/* Status Cards */ -.igny8-status-cards { - gap: 20px; -} - -.igny8-status-card { - cursor: pointer; - transition: all 0.2s ease; - border: none; - background: var(--panel); -} - -.igny8-status-card:hover { - transform: translateY(-3px); - box-shadow: 0 8px 20px rgba(0,0,0,0.15), 0 12px 28px rgba(13,27,42,0.12); -} - -/* Colored Status Card Variants */ -.igny8-status-blue { - background: linear-gradient(135deg, var(--blue) 0%, var(--blue-dark) 100%); - color: white; -} - -.igny8-status-green { - background: linear-gradient(135deg, var(--green) 0%, var(--green-dark) 100%); - color: white; -} - -.igny8-status-amber { - background: linear-gradient(135deg, var(--amber) 0%, var(--amber-dark) 100%); - color: white; -} - -.igny8-clickable-card { - cursor: pointer; -} - -.igny8-status-metric { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 4px; -} - -.igny8-status-count { - font: 700 28px/1.2 'Inter', system-ui, sans-serif; - color: rgba(255, 255, 255, 0.95); - margin: 0; - text-shadow: 0 1px 2px rgba(0,0,0,0.1); -} - -.igny8-status-label { - font: 500 13px/1.3 'Inter', system-ui, sans-serif; - color: rgba(255, 255, 255, 0.85); - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.igny8-status-icon { - position: absolute; - top: 16px; - right: 16px; - opacity: 0.7; -} - -.igny8-status-card .igny8-card-body { - position: relative; - padding: 0 20px; - display: flex; - align-items: center; - justify-content: space-between; -} - -/* Next Actions Panel */ -.igny8-next-actions { - display: flex; - flex-direction: column; - gap: 12px; -} - -.igny8-action-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 12px 16px; - background: var(--panel-2); - border-radius: 6px; - border-left: 3px solid var(--blue); - transition: all 0.2s ease; -} - -.igny8-action-item:hover { - background: #f8fafc; - border-left-color: var(--blue-dark); -} - -.igny8-action-item.igny8-action-complete { - border-left-color: var(--green); - background: #f0fdf4; -} - -.igny8-action-text { - font: 500 14px/1.4 'Inter', system-ui, sans-serif; - color: var(--text); -} - -.igny8-action-status { - font: 500 13px/1.4 'Inter', system-ui, sans-serif; - color: var(--green); -} - -.igny8-btn-text { - background: none; - border: none; - color: var(--blue); - font: 500 13px/1.4 'Inter', system-ui, sans-serif; - padding: 4px 8px; - border-radius: 4px; - text-decoration: none; - transition: all 0.2s ease; -} - -.igny8-btn-text:hover { - background: var(--blue); - color: white; - text-decoration: none; -} - -/* Info Box Styles */ -.igny8-info-box { - background: #f0f8ff; - border: 1px solid #b3d9ff; - border-radius: 6px; - padding: 16px; - margin: 16px 0; -} - -.igny8-info-box p { - margin: 0 0 12px 0; - color: #555; -} - -.igny8-info-box p:last-child { - margin-bottom: 0; -} - -/* =================================================================== - UNIFIED NOTIFICATION SYSTEM - =================================================================== */ - -/* Single Global Notification Container */ -#igny8-global-notification { - position: fixed; - top: 20px; - right: 20px; - padding: 12px 20px; - border-radius: 6px; - color: white; - font-weight: 500; - z-index: 9999; - max-width: 400px; - box-shadow: 0 4px 12px rgba(0,0,0,0.15); - transition: all 0.3s ease; - transform: translateX(0); - font-family: 'Inter', system-ui, sans-serif; - font-size: 14px; - line-height: 1.4; - display: none; -} - -/* Notification Type Styles */ -#igny8-global-notification.success { - background: var(--green); - border-left: 4px solid var(--green-dark); -} - -#igny8-global-notification.error { - background: var(--red-dark); - border-left: 4px solid #b91c1c; -} - -#igny8-global-notification.warning { - background: var(--amber); - border-left: 4px solid var(--amber-dark); -} - -#igny8-global-notification.info { - background: var(--blue); - border-left: 4px solid var(--blue-dark); -} - -/* Animation States */ -#igny8-global-notification.show { - display: block !important; - animation: slideInRight 0.3s ease-out; - opacity: 1; - visibility: visible; -} - -#igny8-global-notification.hide { - animation: slideOutRight 0.3s ease-in; - opacity: 0; - visibility: hidden; -} - -@keyframes slideInRight { - from { - opacity: 0; - transform: translateX(100%); - } - to { - opacity: 1; - transform: translateX(0); - } -} - -@keyframes slideOutRight { - from { - opacity: 1; - transform: translateX(0); - } - to { - opacity: 0; - transform: translateX(100%); - } -} - -/* Hover Effects */ -#igny8-global-notification:hover { - transform: translateX(-5px); - box-shadow: 0 6px 16px rgba(0,0,0,0.2); -} - -/* Textarea Color Variants */ -.igny8-textarea-green { - border-left: 3px solid var(--green); -} - -.igny8-textarea-orange { - border-left: 3px solid var(--amber); -} - -.igny8-textarea-blue { - border-left: 3px solid var(--blue); -} - -.igny8-textarea-purple { - border-left: 3px solid var(--purple); -} - -.igny8-textarea-teal { - border-left: 3px solid var(--teal); -} - -.igny8-textarea-indigo { - border-left: 3px solid var(--indigo); -} - -/* Recent Activity Styles */ -.igny8-recent-activity { - display: flex; - flex-direction: column; - gap: 12px; -} - -.igny8-list-item { - padding: 12px; - border: 1px solid var(--stroke); - border-radius: var(--radius); - background: var(--panel); - transition: all 0.2s ease; -} - -.igny8-list-item:hover { - border-color: var(--blue); - box-shadow: 0 2px 8px rgba(59, 130, 246, 0.1); -} - -.igny8-item-content { - display: flex; - flex-direction: column; - gap: 6px; -} - -.igny8-item-title a { - font-weight: 600; - color: var(--text); - text-decoration: none; - font-size: 14px; -} - -.igny8-item-title a:hover { - color: var(--blue); -} - -.igny8-item-meta { - display: flex; - align-items: center; - gap: 12px; - font-size: 12px; - color: var(--text-muted); -} - -.igny8-item-cluster { - color: var(--text-muted); -} - -.igny8-item-date { - color: var(--text-muted); -} - -.igny8-empty-state { - text-align: center; - padding: 40px 20px; - color: var(--text-muted); -} - -.igny8-empty-state p { - margin-bottom: 16px; -} - -/* Content Types and Publishing Stats */ -.igny8-content-types, .igny8-publishing-stats { - display: flex; - flex-direction: column; - gap: 16px; -} - -.igny8-type-item, .igny8-stat-item { - display: flex; - flex-direction: column; - gap: 8px; -} - -.igny8-type-info, .igny8-stat-header { - display: flex; - justify-content: space-between; - align-items: center; -} - -.igny8-type-name, .igny8-stat-label { - font-weight: 600; - color: var(--text); - font-size: 14px; -} - -.igny8-type-count, .igny8-stat-count { - font-weight: 700; - color: var(--blue); - font-size: 16px; -} - -.igny8-type-bar, .igny8-stat-bar { - height: 6px; - background: var(--stroke); - border-radius: 3px; - overflow: hidden; -} - -.igny8-type-progress, .igny8-stat-progress { - height: 100%; - background: var(--blue); - border-radius: 3px; - transition: width 0.3s ease; -} - -.igny8-stat-progress.igny8-progress-amber { - background: var(--amber); -} - -.igny8-stat-progress.igny8-progress-green { - background: var(--green); -} - -.igny8-stat-progress.igny8-progress-purple { - background: var(--purple); -} - -/* Enhanced Analytics Cards */ -.igny8-equal-height { - align-items: stretch; -} - -.igny8-analytics-card .igny8-card { - height: 100%; - display: flex; - flex-direction: column; -} - -.igny8-card-header { - display: flex; - justify-content: space-between; - align-items: flex-start; - margin-bottom: 20px; - padding-bottom: 16px; - border-bottom: 1px solid var(--stroke); -} - -.igny8-card-header-content { - display: flex; - align-items: flex-start; - gap: 12px; - flex: 1; -} - -.igny8-card-icon { - flex-shrink: 0; - width: 40px; - height: 40px; - background: rgba(59, 130, 246, 0.1); - border-radius: 8px; - display: flex; - align-items: center; - justify-content: center; -} - -.igny8-card-title-text h3 { - margin: 0 0 4px 0; - font-size: 16px; - font-weight: 600; - color: var(--text); -} - -.igny8-card-subtitle { - margin: 0; - font-size: 12px; - color: var(--text-dim); - font-weight: 400; -} - -.igny8-card-subtitle.igny8-centered { - text-align: center; -} - -/* Standard Dashboard Card Headers */ -.igny8-standard-header { - background: #fff !important; - border-bottom: 2px solid var(--blue) !important; - margin-bottom: 16px !important; - padding: 16px !important; - border-radius: 0 !important; -} - -.igny8-standard-header .igny8-card-title-text h3 { - color: var(--navy-bg-2) !important; - font-weight: 600 !important; - font-size: 26px !important; - margin: 0 0 4px 0 !important; -} - -.igny8-standard-header .igny8-card-subtitle { - color: var(--text-dim) !important; - font-size: 13px !important; - font-weight: 400 !important; -} - -.igny8-standard-header .igny8-card-icon { - background: rgba(59, 130, 246, 0.08) !important; - border-radius: 8px !important; - width: 40px !important; - height: 40px !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - flex-shrink: 0 !important; -} - -.igny8-standard-header .igny8-card-header-content { - display: flex !important; - align-items: center !important; - justify-content: space-between !important; - width: 100% !important; -} - -/* Dashboard Icon Styles */ -.igny8-dashboard-icon-sm { - color: rgba(255,255,255,0.7) !important; - font-size: 24px !important; -} - -.igny8-dashboard-icon-lg { - font-size: 26px !important; -} - -.igny8-dashboard-icon-blue { - color: var(--blue) !important; -} - -.igny8-dashboard-icon-amber { - color: var(--amber) !important; -} - -.igny8-dashboard-icon-green { - color: var(--green) !important; -} - -.igny8-dashboard-icon-purple { - color: #8b5cf6 !important; -} - -.igny8-dashboard-icon-dim { - color: var(--text-dim) !important; - font-size: 32px !important; - margin-bottom: 12px !important; -} - -.igny8-card-metric { - text-align: right; - flex-shrink: 0; -} - -.igny8-metric-value { - display: block; - font-size: 24px; - font-weight: 700; - color: var(--blue); - line-height: 1; -} - -.igny8-metric-label { - display: block; - font-size: 11px; - color: var(--text-dim); - text-transform: uppercase; - font-weight: 500; - margin-top: 2px; -} - -.igny8-analytics-list { - display: flex; - flex-direction: column; - gap: 16px; -} - -.igny8-analytics-item { - display: flex; - flex-direction: column; - gap: 8px; -} - -.igny8-analytics-item.igny8-analytics-total { - padding-top: 16px; - border-top: 1px solid var(--stroke); - margin-top: 8px; -} - -.igny8-item-info { - display: flex; - justify-content: space-between; - align-items: center; -} - -.igny8-item-label { - font-size: 14px; - font-weight: 500; - color: var(--text); -} - -.igny8-item-value { - font-size: 16px; - font-weight: 700; - color: var(--blue); -} - -.igny8-item-progress { - display: flex; - align-items: center; - gap: 12px; -} - -.igny8-progress-track { - flex: 1; - height: 6px; - background: var(--stroke); - border-radius: 3px; - overflow: hidden; -} - -.igny8-progress-percent { - font-size: 12px; - font-weight: 600; - color: var(--text-dim); - min-width: 32px; - text-align: right; -} - -.igny8-empty-analytics { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 40px 20px; - text-align: center; - color: var(--text-dim); -} - -.igny8-empty-analytics p { - margin: 12px 0 16px 0; - font-size: 14px; -} - -.igny8-btn-sm { - padding: 6px 12px; - font-size: 12px; -} - -.igny8-status-desc { - font-size: 12px; - color: #fff; -} - -/* Step-by-Step UX Guide */ -.igny8-step-guide { - background: var(--panel); - border: 1px solid var(--stroke); - border-radius: var(--radius); - padding: 20px; - margin-bottom: 20px; - box-shadow: 0 2px 6px rgba(0,0,0,0.08), 0 4px 10px rgba(13,27,42,0.06); -} - -.igny8-step-guide-header { - display: flex; - align-items: center; - margin-bottom: 15px; -} - -.igny8-step-guide-header h3 { - margin: 0; - color: var(--blue-dark); - font-size: 16px; - font-weight: 600; -} - -.igny8-step-guide-header .dashicons { - margin-right: 8px; - color: var(--blue); -} - -.igny8-steps-container { - display: flex; - gap: 15px; - overflow-x: auto; - padding-bottom: 10px; - scrollbar-width: thin; - scrollbar-color: var(--stroke) transparent; -} - -.igny8-steps-container::-webkit-scrollbar { - height: 6px; -} - -.igny8-steps-container::-webkit-scrollbar-track { - background: transparent; -} - -.igny8-steps-container::-webkit-scrollbar-thumb { - background: var(--stroke); - border-radius: 3px; -} - -.igny8-step { - flex: 0 0 auto; - min-width: 180px; - max-width: 220px; - background: var(--bg); - border: 1px solid var(--stroke); - border-radius: var(--radius); - padding: 15px; - position: relative; - transition: all 0.2s ease; -} - -.igny8-step:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0,0,0,0.1); -} - -.igny8-step-number { - display: inline-flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - background: var(--blue); - color: white; - border-radius: 50%; - font-size: 12px; - font-weight: 600; - margin-bottom: 8px; -} - -.igny8-step.completed .igny8-step-number { - background: var(--green); -} - -.igny8-step.current .igny8-step-number { - background: var(--amber); - color: var(--text); -} - -.igny8-step-title { - font-size: 14px; - font-weight: 600; - color: var(--text); - margin-bottom: 6px; - line-height: 1.3; -} - -.igny8-step-status { - display: flex; - align-items: center; - gap: 6px; - margin-bottom: 8px; -} - -.igny8-step-status-icon { - font-size: 14px; -} - -.igny8-step-status-text { - font-size: 12px; - font-weight: 500; - color: var(--text-dim); -} - -.igny8-step.completed .igny8-step-status-text { - color: var(--green-dark); -} - -.igny8-step.current .igny8-step-status-text { - color: var(--amber-dark); -} - -.igny8-step.completed .igny8-step-status-icon { - color: var(--green); -} - -.igny8-step.current .igny8-step-status-icon { - color: var(--amber); -} - -.igny8-step-data { - font-size: 11px; - color: var(--text-dim); - margin-bottom: 8px; - line-height: 1.3; -} - -.igny8-step-action { - margin-top: 8px; -} - -.igny8-step-action .igny8-btn { - font-size: 11px; - padding: 4px 8px; - border-radius: 3px; -} - -.igny8-step-connector { - position: absolute; - top: 50%; - right: -8px; - width: 16px; - height: 2px; - background: var(--stroke); - transform: translateY(-50%); -} - -.igny8-step:last-child .igny8-step-connector { - display: none; -} - -.igny8-step.completed + .igny8-step .igny8-step-connector { - background: var(--green); -} - -/* Responsive adjustments */ -@media (max-width: 768px) { - .igny8-step { - min-width: 160px; - } - - .igny8-steps-container { - gap: 10px; - } -} - -/* System-Wide Workflow Guide */ -.igny8-system-workflow { - background: var(--panel); - border: 1px solid var(--stroke); - border-radius: var(--radius); - padding: 25px; - margin-bottom: 25px; - box-shadow: 0 4px 12px rgba(0,0,0,0.08), 0 6px 16px rgba(13,27,42,0.06); -} - -.igny8-system-workflow-header { - display: flex; - align-items: center; - margin-bottom: 20px; -} - -.igny8-system-workflow-header h2 { - margin: 0; - color: var(--blue-dark); - font-size: 20px; - font-weight: 700; -} - -.igny8-system-workflow-header .dashicons { - margin-right: 12px; - color: var(--blue); - font-size: 24px; -} - -.igny8-system-workflow-subtitle { - color: var(--text-dim); - font-size: 14px; - margin-top: 5px; - margin-bottom: 0; -} - -.igny8-system-steps-container { - display: flex; - gap: 12px; - overflow-x: auto; - padding-bottom: 15px; - scrollbar-width: thin; - scrollbar-color: var(--stroke) transparent; - max-width: 1200px; -} - -.igny8-system-steps-container::-webkit-scrollbar { - height: 8px; -} - -.igny8-system-steps-container::-webkit-scrollbar-track { - background: transparent; -} - -.igny8-system-steps-container::-webkit-scrollbar-thumb { - background: var(--stroke); - border-radius: 4px; -} - -.igny8-system-step { - flex: 0 0 auto; - min-width: 160px; - max-width: 180px; - background: var(--bg); - border: 1px solid var(--stroke); - border-radius: var(--radius); - padding: 16px; - position: relative; - transition: all 0.3s ease; - cursor: pointer; -} - -.igny8-system-step:hover { - transform: translateY(-3px); - box-shadow: 0 6px 20px rgba(0,0,0,0.12); - border-color: var(--blue); -} - -.igny8-system-step.disabled { - opacity: 0.6; - cursor: not-allowed; -} - -.igny8-system-step.disabled:hover { - transform: none; - box-shadow: none; - border-color: var(--stroke); -} - -.igny8-system-step-number { - display: inline-flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - background: var(--blue); - color: white; - border-radius: 50%; - font-size: 13px; - font-weight: 700; - margin-bottom: 10px; -} - -.igny8-system-step.completed .igny8-system-step-number { - background: var(--green); -} - -.igny8-system-step.in_progress .igny8-system-step-number { - background: var(--amber); - color: var(--text); -} - -.igny8-system-step.missing .igny8-system-step-number { - background: var(--text-dim); -} - -.igny8-system-step.disabled .igny8-system-step-number { - background: var(--stroke); - color: var(--text-dim); -} - -.igny8-system-step-title { - font-size: 13px; - font-weight: 600; - color: var(--text); - margin-bottom: 8px; - line-height: 1.3; -} - -.igny8-system-step-status { - display: flex; - align-items: center; - gap: 6px; - margin-bottom: 8px; -} - -.igny8-system-step-status-icon { - font-size: 16px; -} - -.igny8-system-step-status-text { - font-size: 11px; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.igny8-system-step.completed .igny8-system-step-status-text { - color: var(--green-dark); -} - -.igny8-system-step.in_progress .igny8-system-step-status-text { - color: var(--amber-dark); -} - -.igny8-system-step.missing .igny8-system-step-status-text { - color: var(--text-dim); -} - -.igny8-system-step.completed .igny8-system-step-status-icon { - color: var(--green); -} - -.igny8-system-step.in_progress .igny8-system-step-status-icon { - color: var(--amber); -} - -.igny8-system-step.missing .igny8-system-step-status-icon { - color: var(--text-dim); -} - -.igny8-system-step-data { - font-size: 10px; - color: var(--text-dim); - margin-bottom: 10px; - line-height: 1.4; -} - -.igny8-system-step-action { - margin-top: 8px; -} - -.igny8-system-step-action .igny8-btn { - font-size: 10px; - padding: 4px 8px; - border-radius: 3px; - width: 100%; - text-align: center; -} - -.igny8-system-step-connector { - position: absolute; - top: 50%; - right: -7px; - width: 14px; - height: 2px; - background: var(--stroke); - transform: translateY(-50%); - z-index: 1; -} - -.igny8-system-step:last-child .igny8-system-step-connector { - display: none; -} - -.igny8-system-step.completed + .igny8-system-step .igny8-system-step-connector { - background: var(--green); -} - -.igny8-system-step.in_progress + .igny8-system-step .igny8-system-step-connector { - background: var(--amber); -} - -/* System workflow responsive adjustments */ -@media (max-width: 1200px) { - .igny8-system-step { - min-width: 140px; - max-width: 160px; - } -} - -@media (max-width: 768px) { - .igny8-system-step { - min-width: 120px; - max-width: 140px; - padding: 12px; - } - - .igny8-system-steps-container { - gap: 8px; - } - - .igny8-system-step-number { - width: 24px; - height: 24px; - font-size: 11px; - } - - .igny8-system-step-title { - font-size: 12px; - } -} -.workflow-steps { - display: flex; -} -/* === WORDPRESS ADMIN STYLES === */ -/* Ensure WordPress admin styles are available for cron pages */ -.wp-list-table { - border: 1px solid #c3c4c7; - border-spacing: 0; - width: 100%; - clear: both; - margin: 0; -} - -.wp-list-table.widefat { - border-collapse: collapse; -} - -.wp-list-table.fixed { - table-layout: fixed; -} - -.wp-list-table.striped tbody tr:nth-child(odd) { - background-color: #f6f7f7; -} - -.wp-list-table.striped tbody tr:nth-child(even) { - background-color: #fff; -} - -.wp-list-table th, -.wp-list-table td { - border-bottom: 1px solid #c3c4c7; - padding: 8px 10px; - text-align: left; - vertical-align: top; -} - -.wp-list-table th { - background-color: #f1f1f1; - font-weight: 600; - color: #1d2327; -} - -.wp-list-table tbody tr:hover { - background-color: #f0f6fc; -} - - - - - - -/* WordPress admin wrap styles */ -.wrap { - margin: 0 20px 0 2px; -} - -.wrap h1 { - margin: 0 0 20px; - padding: 0; - font-size: 23px; - font-weight: 400; - line-height: 1.3; - color: #1d2327; -} - - - - - - -/* WordPress admin notice styles */ -.notice { - background: #fff; - border-left: 4px solid #fff; - box-shadow: 0 1px 1px 0 rgba(0,0,0,.1); - margin: 5px 15px 2px; - padding: 1px 12px; -} - -.notice.notice-success { - border-left-color: #00a32a; -} - -.notice p { - margin: .5em 0; - padding: 2px; -} - -/* WordPress admin submit styles */ -.submit { - padding: 0; - margin: 0; -} - - -.ai-integration, .new-content-status { - border-right: 3px solid #ccc; - margin-right: 25px; - padding-right: 25px; -} -#igny8-ai-integration-form .igny8-form-group h4 {margin-bottom: 35px;} -.new-content-status .igny8-form-group h4 {margin-bottom: 20px;} - - -.igny8-flex-row { - display: flex; - align-items: center; - align-content: center; - -} - -/* Workflow section styling */ -.igny8-workflow-section { - margin-top: 30px; -} - -.igny8-step-card { - border-left: 4px solid #e5e7eb; - transition: all 0.3s ease; -} - -.igny8-step-card.completed { - border-left-color: #10b981; - background: linear-gradient(135deg, #f0fdf4 0%, #ecfdf5 100%); -} - -.igny8-step-card.current { - border-left-color: #3b82f6; - background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%); -} - -.igny8-step-card.pending { - border-left-color: #f59e0b; - background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%); -} - -.igny8-step-header { - display: flex; - align-items: center; - gap: 15px; - margin-bottom: 15px; -} - -.igny8-step-number { - background: #6b7280; - color: white; - width: 35px; - height: 35px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-weight: bold; - font-size: 16px; - flex-shrink: 0; -} - -.igny8-step-card.completed .igny8-step-number { - background: #10b981; -} - -.igny8-step-card.current .igny8-step-number { - background: #3b82f6; -} - -.igny8-step-card.pending .igny8-step-number { - background: #f59e0b; -} - -.igny8-step-title { - font-size: 18px; - font-weight: 600; - color: #1f2937; - margin: 0; -} - -.igny8-step-status { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 10px; -} - -.igny8-step-status-icon { - font-size: 16px; -} - -.igny8-step-status-text { - font-size: 14px; - font-weight: 500; -} - -.igny8-step-card.completed .igny8-step-status-text { - color: #10b981; -} - -.igny8-step-card.current .igny8-step-status-text { - color: #3b82f6; -} - -.igny8-step-card.pending .igny8-step-status-text { - color: #f59e0b; -} - -.igny8-step-data { - color: #6b7280; - font-size: 14px; - margin-bottom: 15px; -} - -.igny8-step-action { - margin-top: 15px; -} - -.igny8-grid-4 { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 20px; -} - -/* Card layout optimization for settings cards */ -.igny8-flex-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 20px; - width: 100%; -} - -.igny8-card-header-content { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - gap: 20px; -} - -.igny8-card-title-text { - display: flex; - flex-direction: column; - align-items: flex-start; - flex: 1; - min-width: 0; -} - -.igny8-card-title-text h3 { - margin: 0 0 5px 0; - font-size: 18px; - font-weight: 600; - color: #1f2937; -} - -.igny8-card-title-text .igny8-card-subtitle { - margin: 0; - font-size: 14px; - color: #6b7280; -} - -.igny8-flex-row form { - display: flex; - align-items: flex-end; - gap: 20px; - flex: 1; - justify-content: flex-end; - flex-direction: column; - align-content: flex-end; -} - -.igny8-form-group {display: flex;align-items: flex-start;gap: 5px;flex: 1;flex-direction: column;} - -/* Editor Type Selection Styles */ -.igny8-editor-option { - display: block; - margin-bottom: 15px; - padding: 15px; - border: 2px solid #e1e5e9; - border-radius: 8px; - cursor: pointer; - transition: all 0.3s ease; - background: #fff; -} - -.igny8-editor-option:hover { - border-color: #0073aa; - background-color: #f8f9fa; -} - -.igny8-editor-option.selected { - border-color: #0073aa; - background-color: #f0f8ff; - box-shadow: 0 2px 8px rgba(0, 115, 170, 0.1); -} - -.igny8-editor-option input[type="radio"] { - margin-right: 10px; - transform: scale(1.2); -} - -.igny8-editor-option-content { - display: inline-block; - vertical-align: top; - width: calc(100% - 30px); -} - -.igny8-editor-option-title { - font-size: 16px; - font-weight: 600; - color: #333; - margin: 0 0 5px 0; -} - -.igny8-editor-option-description { - margin: 5px 0 0 0; - color: #666; - font-size: 14px; - line-height: 1.4; -} - -.igny8-form-actions { - display: flex; - align-items: center; - flex-shrink: 0; - justify-content: flex-end; -} - -.igny8-card-body { - padding: 20px; -} - -.igny8-mode-toggle-label, -.igny8-radio-group { - display: flex; - align-items: center; - gap: 10px; - white-space: nowrap; -} - -.igny8-mode-toggle-label { - gap: 15px; -} - - - -/* Responsive adjustments */ -@media (max-width: 768px) { - .igny8-grid-4 { - grid-template-columns: 1fr; - } - - .igny8-step-header { - flex-direction: column; - text-align: center; - gap: 10px; - } - - .igny8-flex-row { - flex-direction: column; - align-items: stretch; - gap: 15px; - } - - .igny8-card-header-content { - flex-direction: column; - align-items: stretch; - } - - .igny8-flex-row form { - flex-direction: column; - align-items: stretch; - } - - .igny8-form-group { - justify-content: flex-start; - } -} - -#igny8-new-content-form .igny8-flex-row .igny8-form-actions, #igny8-ai-integration-form .igny8-flex-row .igny8-form-actions {margin-top: 0;} - - -.igny8-card .igny8-standard-header .igny8-card-title-text h3 { - font-size: 20px !important; -} - -.igny8-error-log { - width: 800px; -} -.igny8-form-group select{min-width: 200px;} -.igny8-form-group textarea {width: 80%;} - -/* Title with Badge Layout */ -.igny8-title-with-badge { - display: flex; - align-items: center; - gap: 8px; -} - -.igny8-title-actions { - display: flex; - align-items: center; - gap: 4px; - margin-left: auto; -} - -.igny8-title-text { - flex: 1; -} - -.igny8-menu-toggle { - padding: 8px; - border: none; - background: transparent; - cursor: pointer; - transition: all 0.2s ease; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 4px; -} - -.igny8-menu-toggle:hover { - background: rgba(0, 0, 0, 0.05); - transform: scale(1.1); -} - -.igny8-hamburger { - display: flex; - flex-direction: column; - gap: 3px; - width: 16px; - height: 14px; -} - -.igny8-hamburger span { - display: block; - width: 100%; - height: 2px; - background: var(--blue); - border-radius: 1px; - transition: all 0.2s ease; -} - -.igny8-menu-toggle:hover .igny8-hamburger span { - background: var(--blue-dark); -} - -/* Expandable Description Row */ -.igny8-description-row { - display: none; - background: var(--panel-2); - border-top: 1px solid var(--border); -} - -.igny8-description-row.expanded { - display: table-row; -} - -.igny8-description-content-cell { - padding: 16px; - color: var(--text); - line-height: 1.5; -} - -.igny8-description-content { - background: var(--panel-1); - border-radius: 6px; - padding: 12px; - border: 1px solid var(--border); -} - -.igny8-description-content p { - margin: 0 0 8px 0; -} - -.igny8-description-content p:last-child { - margin-bottom: 0; -} - -/* Description Section Styling */ -.description-section { - margin-bottom: 16px; - padding: 12px; - background: var(--panel-2); - border-radius: 6px; - border-left: 4px solid var(--blue); -} - -.description-section:last-child { - margin-bottom: 0; -} - -.section-heading { - margin: 0 0 8px 0; - color: var(--blue); - font-size: 14px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.section-content { - position: relative; -} - -.content-type-badge { - display: inline-block; - background: var(--blue); - color: white; - padding: 2px 8px; - border-radius: 12px; - font-size: 10px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - margin-bottom: 8px; -} - -.content-details { - color: var(--text); - line-height: 1.5; - font-size: 13px; -} - -.description-item { - margin-bottom: 8px; - padding: 8px; - background: var(--panel-2); - border-radius: 4px; - border-left: 3px solid var(--blue); -} - -.description-item:last-child { - margin-bottom: 0; -} - -.description-item strong { - color: var(--blue); - display: block; - margin-bottom: 4px; - font-size: 12px; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.description-text { - color: var(--text); - line-height: 1.5; - white-space: pre-wrap; -} - -/* Image Prompts Toggle Styles */ -.igny8-image-prompts-display { - display: flex; - align-items: center; - gap: 8px; -} - -.igny8-image-icon { - font-size: 16px; - display: inline-block; - width: 16px; - height: 16px; - line-height: 1; -} - -.igny8-image-prompts-toggle { - background: none; - border: none; - padding: 8px; - margin-bottom: 3px; - cursor: pointer; - border-radius: 4px; - transition: all 0.2s ease; - -} - -.igny8-image-prompts-toggle:hover { - background: rgba(0, 0, 0, 0.05); - transform: scale(1.1); -} - -/* Expandable Image Prompts Row */ -.igny8-image-prompts-row { - display: none; - background: var(--panel-2); - border-top: 1px solid var(--border); -} - -.igny8-image-prompts-row.expanded { - display: table-row; -} - -.igny8-image-prompts-content-cell { - padding: 16px; - color: var(--text); - line-height: 1.5; -} - -.igny8-image-prompts-content { - background: var(--panel-1); - border-radius: 6px; - padding: 12px; - border: 1px solid var(--border); -} - -.igny8-image-prompts-content .prompt-item { - margin-bottom: 8px; - padding: 8px; - background: var(--panel-2); - border-radius: 4px; - border-left: 3px solid var(--blue); -} - -.igny8-image-prompts-content .prompt-item:last-child { - margin-bottom: 0; -} - -.igny8-image-prompts-content .prompt-item strong { - color: var(--blue); - display: block; - margin-bottom: 4px; - font-size: 12px; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.igny8-image-prompts-content .prompt-item:not(:last-child) { - margin-bottom: 12px; -} - -/* Ensure dashicons are properly styled */ -.igny8-image-icon.dashicons { - font-family: dashicons; - font-size: 16px; - color: var(--blue); - vertical-align: middle; -} - -.igny8-image-icon.dashicons:hover { - color: var(--blue-dark); -} - -/* Status with Badge Layout */ -.igny8-status-with-badge { - display: flex; - align-items: center; - gap: 8px; - flex-wrap: wrap; -} - -.igny8-status-text { - flex: 1; - min-width: 0; -} - -.igny8-status-with-badge .igny8-badge { - font-size: 10px; - padding: 2px 6px; - border-radius: 8px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -/* Static image size options styling */ -.igny8-size-options-static { - display: flex; - gap: 10px; - margin-bottom: 10px; -} - -.igny8-size-static { - flex: 1; - padding: 12px 8px; - border: 2px solid var(--border-light); - border-radius: 8px; - text-align: center; - min-height: 60px; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - opacity: 0.7; -} - -.igny8-size-static .size-label { - font-weight: 600; - font-size: 14px; - margin-bottom: 4px; -} - -.igny8-size-static .size-dimensions { - font-size: 12px; - opacity: 0.8; -} - -/* Different colors for each size option */ -/* DALL-E sizes */ -.igny8-size-square { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - border-color: #667eea; -} - -.igny8-size-portrait { - background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); - color: white; - border-color: #f093fb; -} - -.igny8-size-landscape { - background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); - color: white; - border-color: #4facfe; -} - -/* Runware sizes */ -.igny8-size-featured { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - border-color: #667eea; -} - -.igny8-size-desktop { - background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); - color: white; - border-color: #f093fb; -} - -.igny8-size-mobile { - background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); - color: white; - border-color: #4facfe; -} - -/* Image provider styling */ -.igny8-provider-info { - margin-bottom: 10px; -} - -.igny8-provider-badge { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 12px 16px; - background: var(--bg-light); - border: 1px solid var(--border-light); - border-radius: 8px; - font-weight: 500; - color: var(--text-primary); -} - -.igny8-provider-badge .dashicons { - color: var(--blue); - font-size: 16px; -} -.igny8-card.igny8-prompt-section { - display: flex; - flex-direction: row; -} - -.igny8-card.igny8-prompt-section .igny8-dashboard-section { - width: 100%; -} - -/* Image Size Checkbox and Quantity Input Styling */ -.igny8-size-checkbox-container { - display: flex; - flex-direction: column; - gap: 15px; - margin-top: 10px; -} - -.igny8-size-option { - display: flex; - align-items: center; - padding: 15px; - background: #f8fafc; - border: 1px solid #e2e8f0; - border-radius: 8px; - transition: all 0.2s ease; - justify-content: space-between; -} - -.igny8-size-option:hover { - background: #f1f5f9; - border-color: #cbd5e1; -} - -.igny8-checkbox-label { - display: flex; - align-items: center; - cursor: pointer; - font-weight: 500; - color: #374151; - margin-right: 15px; -} - -.igny8-checkbox-label input[type="checkbox"] { - margin-right: 8px; - width: 16px; - height: 16px; - accent-color: var(--blue); -} - -.igny8-checkbox-text { - font-size: 14px; - font-weight: 500; -} - -.igny8-quantity-input { - display: flex; - align-items: center; - gap: 5px; -} - -.igny8-quantity-input label { - font-size: 12px; - color: #6b7280; - font-weight: 500; -} - -.igny8-quantity-input input[type="number"] { - padding: 4px 8px; - border: 1px solid #d1d5db; - border-radius: 4px; - font-size: 12px; - text-align: center; -} - -.igny8-quantity-input input[type="number"]:disabled { - background-color: #f3f4f6; - color: #9ca3af; - cursor: not-allowed; -} - -.igny8-size-info { - - font-size: 12px; - color: #6b7280; - background: #e5e7eb; - padding: 4px 8px; - border-radius: 4px; - font-weight: 500; -} - -/* Featured Image Row Styling */ -.igny8-featured-image-row { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - border-color: #667eea; -} - -.igny8-featured-image-row .igny8-size-info { - background: rgba(255, 255, 255, 0.2); - color: white; - font-weight: 600; -} - -.igny8-featured-image-row .igny8-size-info:first-child { - font-size: 14px; - font-weight: 700; -} \ No newline at end of file diff --git a/igny8-wp-plugin-for-reference-olny/assets/css/core.css b/igny8-wp-plugin-for-reference-olny/assets/css/core.css deleted file mode 100644 index 666107d3..00000000 --- a/igny8-wp-plugin-for-reference-olny/assets/css/core.css +++ /dev/null @@ -1,3039 +0,0 @@ -/* IGNY8 UNIFIED CORE CSS β A-Z COMPACT */ -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap'); - -.is-dismissible {display: none} -#wpcontent{padding-left: 0px} - -/* === 1. TOKENS === */ -:root { - /* Primary Brand Blue (Rocket Cyan-based) */ - --blue: #0693e3; /* Rocket vivid cyan blue β primary brand & main CTA */ - --blue-dark: #0472b8; /* Darkened cyan for hover / active / gradient depth */ - - /* Success Green (cooler to match cyan) */ - --green: #0bbf87; /* Slightly cooler teal-green for success states */ - --green-dark: #08966b; /* Deeper teal-green for hover / active */ - - /* Amber / Warning (warmed up to complement cyan) */ - --amber: #ff7a00; /* Rocket's vivid orange for highlight / warning */ - --amber-dark: #cc5f00; /* Darker orange for hover / strong warning */ - - /* Danger / Destructive */ - --red-dark: #d13333; /* Refreshed red with better contrast against cyan */ - - --purple: #5d4ae3; /* Purple for highlighting / special emphasis */ - --purple-dark:#3a2f94; /* Darker purple for hover / active */ - - --navy-bg: #0d1b2a; /* Sidebar background */ - --navy-bg-2: #142b3f; /* Slightly lighter navy, hover/active */ - --surface: #f8fafc; /* Page background (soft gray-white) */ - --panel: #ffffff; /* Cards / panel foreground */ - --panel-2: #f1f5f9; /* Sub-panel / hover card background */ - - --text: #555a68; /* main headings/body text */ - --text-dim: #64748b; /* secondary/subtext */ - --text-light: #e5eaf0; /* text on dark sidebar */ - --stroke: #e2e8f0; /* table/grid borders and dividers */ - - --radius:6px;--sidebar-width:220px;--header-height:75px - - /* === UNIFIED GRADIENTS === */ - --igny8-gradient-blue: linear-gradient(135deg, var(--blue) 0%, var(--blue-dark) 100%); - --igny8-gradient-panel: linear-gradient(180deg, var(--panel) 0%, var(--panel-2) 100%); - --igny8-gradient-success: linear-gradient(135deg, var(--green) 0%, var(--green-dark) 100%); - --igny8-gradient-warning: linear-gradient(135deg, var(--amber) 0%, var(--amber-dark) 100%); - --igny8-gradient-danger: linear-gradient(135deg, #ef4444 0%, var(--red-dark) 100%); - --igny8-gradient-info: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); - --igny8-gradient-purple: linear-gradient(135deg, var(--purple) 0%, var(--purple-dark) 100%); - --igny8-gradient-gray: linear-gradient(135deg, #6b7280 0%, #374151 100%); - --igny8-gradient-light: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%); - --igny8-gradient-dark: linear-gradient(135deg, #1f2937 0%, #111827 100%); - - /* === UNIFIED BACKGROUNDS === */ - --igny8-bg-success: #d4edda; - --igny8-bg-success-border: #c3e6cb; - --igny8-bg-success-text: #155724; - --igny8-bg-warning: #fff3cd; - --igny8-bg-warning-border: #ffeaa7; - --igny8-bg-warning-text: #856404; - --igny8-bg-danger: #f8d7da; - --igny8-bg-danger-border: #f5c6cb; - --igny8-bg-danger-text: #721c24; - --igny8-bg-info: #d1ecf1; - --igny8-bg-info-border: #bee5eb; - --igny8-bg-info-text: #0c5460; - --igny8-bg-light: #f8f9fa; - --igny8-bg-light-border: #e9ecef; - --igny8-bg-light-text: #495057; - } - - .igny8-card-header.gradient { background: var(--igny8-gradient-panel); padding:10px 14px; border-radius: var(--radius) var(--radius) 0 0; } - -/* === 1.5. UNIFIED BACKGROUNDS, BADGES & GRADIENTS === */ -/* Background Utilities */ -.igny8-bg-success { background: var(--igny8-bg-success); border: 1px solid var(--igny8-bg-success-border); color: var(--igny8-bg-success-text); } -.igny8-bg-warning { background: var(--igny8-bg-warning); border: 1px solid var(--igny8-bg-warning-border); color: var(--igny8-bg-warning-text); } -.igny8-bg-danger { background: var(--igny8-bg-danger); border: 1px solid var(--igny8-bg-danger-border); color: var(--igny8-bg-danger-text); } -.igny8-bg-info { background: var(--igny8-bg-info); border: 1px solid var(--igny8-bg-info-border); color: var(--igny8-bg-info-text); } -.igny8-bg-light { background: var(--igny8-bg-light); border: 1px solid var(--igny8-bg-light-border); color: var(--igny8-bg-light-text); } - -/* Gradient Backgrounds */ -.igny8-gradient-blue { background: var(--igny8-gradient-blue); } -.igny8-gradient-success { background: var(--igny8-gradient-success); } -.igny8-gradient-warning { background: var(--igny8-gradient-warning); } -.igny8-gradient-danger { background: var(--igny8-gradient-danger); } -.igny8-gradient-info { background: var(--igny8-gradient-info); } -.igny8-gradient-purple { background: var(--igny8-gradient-purple); } -.igny8-gradient-gray { background: var(--igny8-gradient-gray); } -.igny8-gradient-light { background: var(--igny8-gradient-light); } -.igny8-gradient-dark { background: var(--igny8-gradient-dark); } - -/* Unified Badge System */ -.igny8-badge { padding: 4px 10px; border-radius: 4px; font-size: 12px; font-weight: 500; color: #fff; white-space: nowrap; display: inline-block; } -.igny8-badge-primary { background: var(--blue); } -.igny8-badge-success { background: var(--green); } -.igny8-badge-warning { background: var(--amber); } -.igny8-badge-danger { background: #ef4444; } -.igny8-badge-info { background: #3b82f6; } -.igny8-badge-purple { background: var(--purple); } -.igny8-badge-gray { background: #6b7280; } -.igny8-badge-dark-red { background: var(--red-dark); } -.igny8-badge-outline { background: transparent; border: 1px solid rgba(255,255,255,0.3); color: rgba(255,255,255,0.9); } - -/* Badge with Gradients */ -.igny8-badge-gradient-blue { background: var(--igny8-gradient-blue); } -.igny8-badge-gradient-success { background: var(--igny8-gradient-success); } -.igny8-badge-gradient-warning { background: var(--igny8-gradient-warning); } -.igny8-badge-gradient-danger { background: var(--igny8-gradient-danger); } -.igny8-badge-gradient-info { background: var(--igny8-gradient-info); } -.igny8-badge-gradient-purple { background: var(--igny8-gradient-purple); } - -/* Badge Sizes */ -.igny8-badge-sm { padding: 2px 6px; font-size: 10px; } -.igny8-badge-lg { padding: 6px 14px; font-size: 14px; } - -/* Badge with Icons */ -.igny8-badge-icon { display: inline-flex; align-items: center; gap: 4px; } -.igny8-badge-icon .dashicons { font-size: 12px; } - -/* Title and Status with Badge Layouts */ -.igny8-title-with-badge, .igny8-status-with-badge { display: flex; align-items: center; justify-content: space-between; gap: 8px; } -.igny8-title-text, .igny8-status-text { flex: 1; } -.igny8-title-actions, .igny8-status-actions { display: flex; align-items: center; gap: 4px; } - -/* Progress Bar Gradients */ -.igny8-progress-bar { background: var(--panel-2); border-radius: 10px; height: 24px; overflow: hidden; position: relative; } -.igny8-progress-fill { background: var(--igny8-gradient-blue); transition: width 0.5s ease; position: relative; height: 100%; } -.igny8-progress-fill-success { background: var(--igny8-gradient-success); } -.igny8-progress-fill-warning { background: var(--igny8-gradient-warning); } -.igny8-progress-fill-danger { background: var(--igny8-gradient-danger); } - -/* Button Gradients */ -.igny8-btn-gradient-blue { background: var(--igny8-gradient-blue); } -.igny8-btn-gradient-success { background: var(--igny8-gradient-success); } -.igny8-btn-gradient-warning { background: var(--igny8-gradient-warning); } -.igny8-btn-gradient-danger { background: var(--igny8-gradient-danger); } -.igny8-btn-gradient-info { background: var(--igny8-gradient-info); } -.igny8-btn-gradient-purple { background: var(--igny8-gradient-purple); } -.igny8-btn-gradient-gray { background: var(--igny8-gradient-gray); } - -/* Card Gradients */ -.igny8-card-gradient { background: var(--igny8-gradient-panel); } -.igny8-card-gradient-blue { background: var(--igny8-gradient-blue); color: white; } -.igny8-card-gradient-success { background: var(--igny8-gradient-success); color: white; } -.igny8-card-gradient-warning { background: var(--igny8-gradient-warning); color: white; } -.igny8-card-gradient-danger { background: var(--igny8-gradient-danger); color: white; } - -/* Modal and Overlay Backgrounds */ -.igny8-modal-bg { background: rgba(0,0,0,0.5); } -.igny8-overlay-light { background: rgba(255,255,255,0.9); } -.igny8-overlay-dark { background: rgba(0,0,0,0.8); } - -/* Status Indicators */ -.igny8-status-success { background: var(--igny8-bg-success); border-left: 4px solid var(--green); } -.igny8-status-warning { background: var(--igny8-bg-warning); border-left: 4px solid var(--amber); } -.igny8-status-danger { background: var(--igny8-bg-danger); border-left: 4px solid #ef4444; } -.igny8-status-info { background: var(--igny8-bg-info); border-left: 4px solid #3b82f6; } - -/* === 2. RESET & BASE === */ -*{margin:0;padding:0;box-sizing:border-box} -body{font-family:Inter,system-ui,-apple-system,Segoe UI,Roboto,sans-serif;background:var(--surface);color:var(--text);line-height:1.4;font-size:14px;font-weight:600;} -a{text-decoration:none;color:inherit} - -/* === 3. LAYOUT === */ -.igny8-page-wrapper{display:flex;min-height:100vh;width: 100%;margin: auto;} -.igny8-main-area{flex:1;display:flex;flex-direction:column;background:var(--surface)} -.igny8-content{flex:1;padding:20px;background: #fff;box-shadow: 0 2px 6px 3px rgba(0, 0, 0, .08)} -.igny8-footer{background:var(--navy-bg-2);padding:10px 20px;text-align:center;color:var(--text-light);font-size:13px;border-top:1px solid rgba(255,255,255,.1)} - - -/* === 4. SIDEBAR === */ -.igny8-sidebar{width:var(--sidebar-width);background:var(--navy-bg-2);color:var(--text-light);display:flex;flex-direction:column;padding:16px 12px} -.igny8-sidebar-logo{font-size:20px;font-weight:600;text-align:center;margin-bottom:16px} -.igny8-version-badge{text-align:center;margin-bottom:16px} -.igny8-version-badge .igny8-badge{font-size:11px;font-weight:600;letter-spacing:0.5px} -.igny8-breadcrumb{font-size:12px;color:rgba(255,255,255,0.7);margin-bottom:20px;text-align:center;line-height:1.4} -.igny8-breadcrumb-link{color:#f59e0b;text-decoration:none;transition:color .2s} -.igny8-breadcrumb-link:hover{color:#fff} -.igny8-breadcrumb-separator{margin:0 6px;opacity:0.6} -.igny8-breadcrumb-current{color:rgba(255,255,255,0.9);font-weight:500} -.igny8-sidebar-nav{display:flex;flex-direction:column;gap:8px} -.igny8-sidebar-link{display:flex;align-items:center;gap:10px;font-size:14px;padding:8px 12px;border-radius:var(--radius);color:var(--text-light);transition:background .2s} -.igny8-sidebar-link:hover{background:rgba(255,255,255,.08);color: #fff;} -.igny8-sidebar-link.active{background:var(--blue);color:#fff} -.igny8-sidebar-metrics{margin-top:auto;display:flex;flex-direction:column;gap:8px;padding-top:24px;border-top:1px solid rgba(255,255,255,.1)} -.igny8-sidebar-metric{display:flex;justify-content:space-between;font-size:13px} -.igny8-sidebar-metric .label{opacity:.8} -.igny8-sidebar-footer-container{position:relative;width:100%;margin-top:auto;padding:16px 12px 16px 12px} -.igny8-sidebar-footer{border-top:1px solid rgba(255,255,255,.1);padding-top:12px;display:flex;flex-direction:column;gap:6px} - -/* === 5. HEADER === */ -.igny8-header{display:flex;align-items:center;justify-content:space-between;background:var(--navy-bg-2);height:var(--header-height);padding:0 20px;border-bottom:1px solid rgba(255,255,255,.1);color:var(--text-light)} -.igny8-header-left{display:flex;align-items:center;gap:20px} -.igny8-page-title h1{font-size:22px;font-weight:600;color:#fff;margin:0 0 4px 0;gap:20px} -.igny8-page-description{font-size:13px;color:rgba(255,255,255,0.8);margin:0;line-height:1.3} -.igny8-breadcrumbs{font-size:13px;color:var(--text-light);opacity:.8} -.igny8-breadcrumbs a{color:var(--blue);font-weight:500} -.igny8-header-center{display:flex;align-items:center;justify-content:center;flex:1;text-align:center} -.igny8-marquee-ticker{font-size:13px;color:var(--text-light);white-space:nowrap;overflow:hidden;text-overflow:ellipsis} -.igny8-header-right{display:flex;align-items:center;gap:20px} -.igny8-metrics{display:flex;align-items:center;gap:8px} -.igny8-badge{padding:4px 10px;border-radius:4px;font-size:12px;font-weight:500;color:#fff;white-space:nowrap} -.igny8-badge.igny8-btn-primary{background:var(--blue)} -.igny8-badge.igny8-btn-success{background:var(--green)} -.igny8-badge.igny8-btn-warning{background:var(--amber)} -.igny8-badge.igny8-btn-outline{background:transparent;border:1px solid rgba(255,255,255,0.3);color:rgba(255,255,255,0.9)} -.igny8-header-icons{display: flex; - align-items: center; - gap: 14px; - margin: 0 20px 10px 0; - align-content: center; - } -.igny8-header-icons .dashicons{font-size:26px;cursor:pointer;color:var(--text-light);transition:color .2s} -.igny8-header-icons .dashicons:hover{color:var(--blue)} - -/* Fix for dashicons in buttons */ -.igny8-btn .dashicons { font-family: dashicons !important; font-size: 16px; line-height: 1; text-decoration: none; font-weight: normal; font-style: normal; vertical-align: top; margin-right: 6px; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } -.igny8-icon-only svg { font-family: inherit !important; font-style: normal !important; font-weight: normal !important; } -.igny8-actions-cell button { font-family: inherit !important; } -.igny8-actions-cell button svg { font-family: inherit !important; } -.dashicons { font-variant: normal; text-transform: none; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } - -/* === 6. BUTTONS === */ -.igny8-btn{display:inline-flex;align-items:center;justify-content:center;padding:4px 12px;font-size:13px;font-weight:500;line-height:1.3;border:none;border-radius:var(--radius,6px);cursor:pointer;transition:all .2s ease-in-out;color:#fff;text-decoration:none;white-space:nowrap;margin: 0 5px} -.igny8-btn:disabled,.igny8-btn.disabled{opacity:.5;cursor:not-allowed} -.igny8-btn-primary{background:var(--blue,#3b82f6)} -.igny8-btn-primary:hover{background:var(--blue-dark,#2563eb)} -.igny8-btn-secondary{background:var(--text-dim,#64748b);color:#fff} -.igny8-btn-secondary:hover{background:#475569} -.igny8-btn-outline{background:transparent;border:1px solid var(--stroke,#e2e8f0);color:var(--text,#0f172a)} -.igny8-btn-outline:hover{background:rgba(0,0,0,.05)} -.igny8-btn-success{background:var(--green,#10b981)} -.igny8-btn-success:hover{background:var(--green-dark,#059669)} -.igny8-btn-accent{background:var(--amber,#f59e0b)} -.igny8-btn-accent:hover{background:var(--amber-dark,#d97706)} -.igny8-btn-danger{background:#ef4444} -.igny8-btn-danger:hover{opacity:.9} -.igny8-btn-icon{width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center} - -/* === 9. TABLE === */ -.igny8-table{width:100%;border-collapse:collapse;background:var(--panel);border:1px solid var(--stroke);border-radius:4px;overflow:visible;font-size:14px} -.igny8-table thead th{background:var(--navy-bg-2);color:var(--text-light);font-weight:500;text-align:left;padding:6px 10px;border-bottom:1px solid var(--stroke)} -.igny8-table tbody td{padding:6px 10px;border-bottom:1px solid var(--stroke);color:var(--text);overflow:visible;position:relative} -.igny8-table tbody tr:hover{background:var(--panel-2)} -.igny8-table th {font-size: 110%;} -.igny8-col-checkbox{width:36px;text-align:center} -.igny8-col-actions{width:80px;text-align:center} - -/* === 10. PAGINATION === */ -.igny8-pagination{margin: 25px 0;display:flex;justify-content:center;align-items:center;} -.igny8-btn-pagination{height:auto;padding:3px 9px;font-size:12px;border-radius:4px;color:var(--blue);border:1px solid var(--blue);background:transparent;cursor:pointer;transition:all .2s} -.igny8-btn-pagination:hover:not(:disabled){background:var(--blue);color:#fff} -.igny8-btn-pagination:disabled{opacity:.4;cursor:default} -.igny8-btn-pagination.active{background:var(--blue);color:#fff;border-color:var(--blue)} - -/* === 11. MODAL === */ -.igny8-modal{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.5);display:none;align-items:center;justify-content:center;z-index:10000} -.igny8-modal.open{display:flex} -.igny8-modal-content{background:#fff;border-radius:6px;box-shadow:0 20px 40px rgba(0,0,0,.25);max-width:500px;width:90%;max-height:90vh;overflow:auto} -.igny8-modal-header,.igny8-modal-footer{padding:12px 16px;border-bottom:1px solid var(--stroke);display:flex;justify-content:space-between;align-items:center} -.igny8-modal-footer{border-top:1px solid var(--stroke)} -.igny8-btn-close{background:none;border:none;font-size:18px;cursor:pointer;color:var(--text-dim)} - -/* === 12. UTILITIES === */ -.igny8-flex{display:flex}.igny8-ml-auto{margin-left:auto} -.igny8-text-muted{color:var(--text-dim)}.igny8-mb-20{margin-bottom:20px} -.igny8-error-box{background:#f8d7da;color:#721c24;border:1px solid #f5c6cb;border-radius:4px;padding:12px;margin:10px 0;font-size:13px} - -/* === 13. RESPONSIVE === */ -@media(max-width:768px){.igny8-sidebar{display:none}.igny8-filters{flex-direction:column;align-items:flex-start}.igny8-filter-bar{flex-wrap:wrap}.igny8-table-actions{flex-wrap:wrap;gap:8px}} - -.is-dismissible {display: none} -#wpcontent{padding-left: 0px} - -.igny8-metrics-row{display:flex;justify-content:space-between;align-items:center;margin-bottom:15px;} -.igny8-page-title{font-size:1.4rem;font-weight:600;margin:0;} -.igny8-metrics-bar{display:flex;gap:8px;} -.igny8-badge{padding:4px 10px;border-radius:4px;font-size:.85rem;font-weight:500;color:#fff;} -.igny8-badge-primary{background:var(--blue-dark)} -.igny8-badge-success{background:#10b981;} -.igny8-badge-warning{background:#f59e0b;} -.igny8-badge-info{background:#3b82f6;} - -/* === 7. Filters === */ -.igny8-filters{display:flex;align-items:center;justify-content: center;gap:10px;margin:25px 0} -.igny8-filter-bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;background:#f9fafb;border:1px solid #e2e8f0;border-radius:6px;padding:8px 12px;box-shadow: 0 2px 6px 3px rgba(0, 0, 0, .08)} -.igny8-filter-group{position:relative;display:flex;align-items:center;gap:6px} -.igny8-search-input{width:200px;padding:6px 10px;border:1px solid var(--igny8-stroke);border-radius:4px;font-size:14px} -.igny8-filter-actions{display:flex;align-items:center;gap:8px} - -/* === 8. Dropdowns === */ -.select{position:relative;min-width:120px} -.select-btn{display:flex;align-items:center;justify-content:space-between;width:100%;padding:6px 10px;font-size:13px;background:#fff;border:1px solid var(--igny8-stroke);border-radius:4px;cursor:pointer;box-shadow: 0 2px 6px 3px rgba(0, 0, 0, .08);color:var(--text);font-weight:500} -.select-list{display:none;position:absolute;top:calc(100% + 4px);left:0;right:0;background:#fff;border:1px solid var(--igny8-stroke);border-radius:4px;box-shadow:0 2px 6px rgba(0,0,0,.08);z-index:999999;max-height:200px;overflow-y:auto} -.select-item{padding:6px 10px;font-size:14px;cursor:pointer;border-bottom:1px solid #f1f5f9} -.select-item:last-child{border-bottom:none} -.select-item:hover{background:#f1f5f9} -.select.open .select-list {display:block;} -.select-arrow {font-size: 10px} -.igny8-table-actions{display:flex;align-items:center;gap:8px;justify-content: space-between; margin-bottom: 10px;} - -/* === 15. Icon Buttons === */ - -.igny8-icon-only{background:none;border:none;padding:0;margin:0 4px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:opacity .2s} -.igny8-icon-only svg{width:18px;height:18px} -.igny8-icon-edit svg{color:var(--blue,#3b82f6)} -.igny8-icon-save svg {color: #fff;} -.igny8-icon-save {background-color: var(--success, #10b981);} -.igny8-icon-cancel svg {color: #fff;} -.igny8-icon-cancel {background-color: var(--text-dim, #64748b);} -.igny8-icon-edit:hover svg{color:var(--text-dim)} -.igny8-icon-delete svg{color:#ef4444} -.igny8-icon-delete:hover svg{color:#dc2626} -.igny8-icon-save{background:var(--green,#10b981);color:#fff;border-radius:8px;padding:0px;transition:background .2s} -.igny8-icon-save:hover{background:var(--green-dark,#059669)} -.igny8-icon-cancel{background:var(--text-dim,#64748b);color:#fff;border-radius:8px;padding:0px;transition:background .2s} -.igny8-icon-cancel:hover{background:var(--text,#0f172a)} -.igny8-icon-play svg{color:var(--blue,#3b82f6)} -.igny8-icon-play:hover svg{color:var(--text-dim)} -.igny8-icon-external svg{color:var(--text-dim,#64748b)} -.igny8-icon-external:hover svg{color:var(--blue,#3b82f6)} -.igny8-actions{white-space:nowrap} - -.igny8-page-title {display: flex;flex-direction: row;justify-content: space-around;align-items: center;flex-wrap: wrap;} -.igny8-sidebar-logo h2 {color: #b8d3ff;font-size: 1.3em;margin: 10px 0;font-weight: 900} - -/* ---------- GRID LAYOUTS ---------- */ - -.igny8-grid { display: grid; gap: 20px; } -.igny8-grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; align-items: stretch; } -.igny8-grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px; margin:50px 0;} -.igny8-grid-4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 20px; } - - -.igny8-module-cards-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:22px;margin:24px 0;} -.igny8-dashboard-cards,.igny8-grid-3{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:22px;margin:5px;} - -.igny8-grid-4{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:22px;margin-bottom:24px;} - - - -.igny8-card { background: var(--panel); border: 1px solid var(--stroke); border-radius: var(--radius); padding: 18px; box-shadow: 0 2px 6px rgba(0,0,0,0.10), 0 4px 10px rgba(13,27,42,0.06); transition: box-shadow .25s ease, transform .2s ease; height: auto; display: flex; flex-direction: column; } -.igny8-card:hover { transform: translateY(-2px); box-shadow: 0 6px 14px rgba(0,0,0,0.14), 0 8px 20px rgba(13,27,42,0.10); } -.igny8-card-header { display:flex; align-items:center; justify-content:space-between; margin-bottom:12px; background:linear-gradient(90deg,var(--blue) 0%,var(--blue-dark) 100%); color:#fff; padding:12px 16px; border-radius:var(--radius) var(--radius) 0 0; margin:-10px -10px 12px -10px; } -.igny8-card-title { font:600 15px/1.4 'Inter',system-ui,sans-serif; margin:10px; color: #fff; display: flex;justify-content: space-between;} -.igny8-card-body { font:400 14px/1.55 'Inter',system-ui,sans-serif; color:var(--text); flex: 1; } - - -.igny8-help-text { font-size:13px; color:var(--text-dim); margin-top:6px; } -.igny8-status-badge { font-size:12px; font-weight:500; border-radius:4px; padding:2px 6px; line-height:1.2; } -.igny8-status-badge.mapped { background:#d1fae5; color:#065f46; } -.igny8-status-badge.unmapped { background:#fee2e2; color:#991b1b; } -.igny8-status-ok { color:var(--green); font-weight:500; } -.igny8-form-row { margin-bottom:18px; } -.igny8-form-row label { font-weight:500; font-size:14px; display:block; margin-bottom:6px; } -.igny8-form-row input[type="text"], .igny8-form-row select, .igny8-form-row textarea { width:100%; padding:8px 12px; font-size:14px; border:1px solid var(--stroke); border-radius:var(--radius); background:#fff; transition:border .2s, box-shadow .2s; } -.igny8-form-row input:focus, .igny8-form-row select:focus, .igny8-form-row textarea:focus { border-color:var(--blue); box-shadow:0 0 0 2px rgba(59,130,246,.18); outline:none; } -.igny8-radio-group, .igny8-checkbox-group { display:flex; flex-wrap:wrap; gap:16px; } -.igny8-radio-option, .igny8-checkbox-option { display:flex; align-items:center; gap:6px; font-size:14px; cursor:pointer; } -.igny8-toggle-switch { position:relative; width:42px; height:22px; display:inline-block; } -.igny8-toggle-switch input { opacity:0; width:0; height:0; } -.igny8-toggle-slider { position:absolute; cursor:pointer; top:0; left:0; right:0; bottom:0; background:#cbd5e1; border-radius:22px; transition:background .3s; } -.igny8-toggle-slider:before { content:""; position:absolute; height:18px; width:18px; left:2px; bottom:2px; background:#fff; border-radius:50%; transition:transform .3s, box-shadow .3s; box-shadow:0 1px 2px rgba(0,0,0,0.3); } -.igny8-toggle-switch input:checked + .igny8-toggle-slider { background:var(--blue); } -.igny8-toggle-switch input:checked + .igny8-toggle-slider:before { transform:translateX(20px); } -.igny8-table-compact th, .igny8-table-compact td { padding:6px 8px; font-size:13px; } -.igny8-flex { display:flex; gap: 20px} -.igny8-ml-auto { margin-left:auto; } -.igny8-pad-5 { padding:5px; } -.igny8-mb-20 { margin-bottom:20px; } -@media (max-width:1024px) { .igny8-grid-3 { grid-template-columns:repeat(2, minmax(0, 1fr)); } .igny8-grid-4 { grid-template-columns:repeat(2, minmax(0, 1fr)); } } -@media (max-width:768px) { .igny8-grid-2, .igny8-grid-3, .igny8-grid-4 { grid-template-columns:1fr; } } - -/* ---------- THEME ADD-ONS ---------- */ -/* Gradient helpers */ - -/* ---------- GLOBAL ELEMENTS ---------- */ -.igny8-content h1,.igny8-settings h2,.igny8-welcome-section h2{font:700 22px/1.3 'Inter',system-ui,sans-serif;color:var(--navy-bg);margin-bottom:16px;} -.igny8-content p,.igny8-settings-section p,.igny8-welcome-text{font-size:14px;color:var(--text-dim);} -.igny8-settings-section h3,.igny8-card h3{font:600 16px/1.3 'Inter',system-ui,sans-serif;margin-bottom:12px;color:var(--blue-dark);} -.igny8-help-text{font-size:12px;color:var(--text-dim);margin-top:8px;} - -/* ---------- SUBHEADER ---------- */ -.igny8-submenu-buttons a{font-size:13px;padding:6px 16px;border-radius:var(--radius);background:var(--blue);color:#fff;font-weight:500;border:none;box-shadow:0 2px 6px rgba(0,0,0,.15);transition:all .2s ease;} -.igny8-submenu-buttons a:hover{background:var(--blue-dark);transform:translateY(-1px);box-shadow:0 4px 10px rgba(0,0,0,.2);} -.igny8-submenu-buttons a.active{background:var(--green);} - - -/* ---------- CARDS ---------- */ -.igny8-card,.igny8-module-card{margin: 15px 0;border:1px solid var(--stroke);background:var(--panel);border-radius:var(--radius);padding:10px;box-shadow:0 2px 6px rgba(0,0,0,.08),0 4px 10px rgba(13,27,42,.06);transition:all .25s ease; width: 100%;} -.igny8-card:hover,.igny8-module-card:hover{transform:translateY(-2px);box-shadow:0 6px 14px rgba(0,0,0,.12),0 8px 22px rgba(13,27,42,.10);} -.igny8-card-header{border-bottom:1px solid var(--stroke);padding-bottom:6px;margin-bottom:12px;} -h3,h4,h5,h6,igny8-card-title,.igny8-card-header h3{margin:0;font:600 16px/1.4 'Inter',system-ui,sans-serif;color:var(--blue-dark);} -.igny8-card-body{font-size:14px;color:var(--text);padding: 5px 15px;} -.igny8-card-actions{margin-top:12px;display:flex;gap:12px;} - -/* ---------- MODULE CARD HEADER ACCENTS ---------- */ -.igny8-module-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;padding-bottom:6px;border-bottom:1px solid var(--stroke);} -.igny8-module-header h4{font:600 16px/1.3 'Inter',system-ui,sans-serif;margin:0;color:var(--navy-bg);} -.igny8-module-card:nth-child(1) .igny8-module-header h4{border-left:3px solid var(--blue);padding-left:6px;} -.igny8-module-card:nth-child(2) .igny8-module-header h4{border-left:3px solid var(--amber-dark);padding-left:6px;} -.igny8-module-card:nth-child(3) .igny8-module-header h4{border-left:3px solid var(--green);padding-left:6px;} -.igny8-module-card:nth-child(4) .igny8-module-header h4{border-left:3px solid var(--blue-dark);padding-left:6px;} -.igny8-module-card:nth-child(5) .igny8-module-header h4{border-left:3px solid var(--amber-dark);padding-left:6px;} -.igny8-module-card:nth-child(6) .igny8-module-header h4{border-left:3px solid var(--green-dark);padding-left:6px;} -.igny8-module-card:nth-child(7) .igny8-module-header h4{border-left:3px solid var(--blue);padding-left:6px;} -.igny8-module-icon{background:rgba(59,130,246,0.08);padding:8px;border-radius:50%;display:flex;align-items:center;justify-content:center;} -.igny8-module-description p{font-size:14px;color:var(--text-dim);line-height:1.5;margin-bottom:12px;} - -/* ---------- FORM ELEMENTS ---------- */ -.igny8-form-row{margin-bottom:16px;} -.igny8-form-row label{font:600 13px/1.4 'Inter',system-ui,sans-serif;margin-bottom:5px;display:block;color:var(--navy-bg);} -.igny8-form-row input[type="text"],.igny8-form-row textarea,.igny8-form-row select{width:100%;padding:7px 12px;font-size:14px;border:1px solid var(--stroke);border-radius:var(--radius);transition:border .2s,box-shadow .2s;} -.igny8-form-row input:focus,.igny8-form-row textarea:focus,.igny8-form-row select:focus{border-color:var(--blue);box-shadow:0 0 0 2px rgba(59,130,246,.18);outline:none;} -.igny8-radio-group,.igny8-checkbox-group{display:flex;flex-wrap:wrap;gap:14px;} -.igny8-radio-option,.igny8-checkbox-option{display:flex;align-items:center;gap:6px;font-size:13px;cursor:pointer;} -.igny8-radio-option input:checked+label{color:var(--blue-dark);font-weight:600;} -.igny8-checkbox-option input:checked+label{color:var(--green-dark);font-weight:600;} -.igny8-form-row textarea[name*="prompt"]{border-left:3px solid var(--amber-dark);} -.igny8-form-row select[name*="style"]{border-left:3px solid var(--green);} -.igny8-form-row input[type="text"]:focus{border-color:var(--blue-dark);} -/*textarea[name*="prompt"]{border-left:3px solid var(--amber-dark);}*/ -.igny8-textarea-orange {border-left: 3px solid var(--amber-dark);} -.igny8-textarea-green {border-left: 3px solid var(--green);} -.igny8-textarea-blue {border-left: 3px solid var(--blue);} - - -/* ---------- TABLES ---------- */ -.igny8-table-wrapper table,.igny8-table{width:100%;border-collapse:collapse;font-size:13px;} -.igny8-table-wrapper th,.igny8-table-wrapper td,.igny8-table th,.igny8-table td{border:1px solid var(--stroke);padding:6px 8px;text-align:left;} -.igny8-table-wrapper thead{background:var(--panel-2);} -.igny8-table-wrapper th,.igny8-table th{color:var(--navy-bg);font-weight:600;} -.igny8-table thead{background:var(--navy-bg);color:#fff;} -.igny8-table td code{color:var(--blue-dark);} -.igny8-status-ok{color:var(--green-dark);font-weight:500;} - -/* ---------- BUTTONS ---------- */ -.igny8-btn,.button.button-primary,#submit.button-primary{display:inline-flex;align-items:center;justify-content:center;padding:5px 10px;font-size:12px;font-weight:500;border-radius:var(--radius);cursor:pointer;transition:all .25s ease;text-decoration:none;box-shadow:0 2px 6px rgba(0,0,0,.15);} -.igny8-btn-primary{background:var(--blue);color:#fff;border:none;} -.igny8-btn-primary:hover{background:var(--blue-dark);transform:translateY(-1px);color: #fff;} -.igny8-btn-outline{background:transparent;border:1px solid var(--stroke);color:var(--text);} -.igny8-btn-outline:hover{background:var(--blue);color:#fff;box-shadow:0 4px 10px rgba(0,0,0,.15);} -.igny8-btn-danger{background:var(--red-dark);color:#fff;} -.igny8-btn-danger:hover{opacity:.9;} -#igny8-export-btn{border-color:var(--blue);color:var(--blue);} -#igny8-export-btn:hover{background:var(--blue);color:#fff;} -#igny8-import-btn{border-color:var(--green);color:var(--green);} -#igny8-import-btn:hover{background:var(--green);color:#fff;} -.button.button-primary,#submit.button-primary{background:var(--green);border:none;color:#fff;} -#submit.button-primary:hover{background:var(--green-dark);transform:translateY(-1px);box-shadow:0 4px 12px rgba(0,0,0,.2);} - -/* ---------- TOGGLE SWITCH ---------- */ -/* Toggle switch styles are defined above at lines 230-235 */ - -/* ---------- ALERT NOTICES ---------- */ -.notice-error{border-left:4px solid var(--red-dark)!important;background:rgba(185,28,28,0.05);} -.notice-warning{border-left:4px solid var(--amber-dark)!important;background:rgba(217,119,6,0.05);} -.notice p{font-size:13px;} - -/* ---------- DASHBOARD QUICK STATS ---------- */ -.igny8-stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(80px,1fr));gap:16px;} -.igny8-stat{text-align:center;background:var(--panel-2);padding:10px;border-radius:var(--radius);box-shadow:inset 0 1px 2px rgba(0,0,0,.05);} -.igny8-stat-number{display:block;font:600 18px/1.2 'Inter',system-ui,sans-serif;color:var(--blue-dark);} -.igny8-stat-label{font-size:13px;color:var(--text-dim);} - -/* ---------- DASHBOARD ACTIVITY ---------- */ -.igny8-activity-list{display:flex;flex-direction:column;gap:8px;} -.igny8-activity-item{display:flex;justify-content:space-between;padding:6px 10px;background:var(--panel-2);border-radius:var(--radius);} -.igny8-activity-time{font-size:13px;color:var(--amber-dark);font-weight:500;} -.igny8-activity-desc{font-size:13px;color:var(--text);} - - - -/* ---------- MODAL CORE ---------- */ -.igny8-modal-content{background:var(--panel);border-radius:var(--radius);width:420px;max-width:90%;margin:auto;padding:0;box-shadow:0 8px 24px rgba(0,0,0,.22),0 12px 32px rgba(13,27,42,.18);font-family:'Inter',system-ui,sans-serif;animation:fadeInScale .25s ease;} -@keyframes fadeInScale{0%{opacity:0;transform:scale(.96);}100%{opacity:1;transform:scale(1);}} - -/* ---------- HEADER ---------- */ -.igny8-modal-header{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid var(--stroke);background:linear-gradient(90deg,var(--panel-2) 0%,#fff 100%);} -.igny8-modal-header h3{margin:0;font:600 16px/1.3 'Inter',system-ui,sans-serif;color:var(--blue-dark);} -.igny8-btn-close{background:transparent;border:none;font-size:20px;line-height:1;color:var(--text-dim);cursor:pointer;transition:color .2s;} -.igny8-btn-close:hover{color:var(--red-dark);} - -/* ---------- BODY ---------- */ -.igny8-modal-body{padding:16px 18px;font-size:14px;color:var(--text);} -.igny8-modal-body p{margin-bottom:10px;} -.igny8-modal-body strong{font-weight:600;color:var(--navy-bg);} -.igny8-modal-body ul{margin:6px 0 0 18px;padding:0;font-size:13px;color:var(--text-dim);line-height:1.4;} -.igny8-modal-body ul li{list-style:disc;} -.igny8-text-danger{color:var(--red-dark);font-weight:500;margin-top:10px;} - -/* ---------- FOOTER ---------- */ -.igny8-modal-footer{display:flex;justify-content:flex-end;gap:10px;padding:14px 18px;border-top:1px solid var(--stroke);background:var(--panel-2);} -.igny8-btn-secondary{background:var(--text-dim);color:#fff;padding:6px 14px;font-size:13px;border:none;border-radius:var(--radius);cursor:pointer;transition:all .25s ease;box-shadow:0 2px 5px rgba(0,0,0,.15);} -.igny8-btn-secondary:hover{background:#475569;transform:translateY(-1px);} -.igny8-btn-danger{background:var(--red-dark);color:#fff;padding:6px 14px;font-size:13px;border:none;border-radius:var(--radius);cursor:pointer;transition:all .25s ease;box-shadow:0 2px 5px rgba(0,0,0,.15);} -.igny8-btn-danger:hover{background:#991b1b;transform:translateY(-1px);} - -/* Automation UI Components */ -.igny8-automation-table { - margin-top: 16px; -} - -.igny8-status-badge { - display: inline-block; - padding: 4px 8px; - border-radius: 4px; - font-size: 12px; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.igny8-status-success { - background: var(--green); - color: white; -} - -.igny8-status-disabled { - background: var(--text-dim); - color: white; -} - -.igny8-toggle { - position: relative; - display: inline-block; - width: 44px; - height: 24px; - cursor: pointer; -} - -.igny8-toggle input { - opacity: 0; - width: 0; - height: 0; -} - -.igny8-toggle-slider { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: var(--text-dim); - border-radius: 24px; - transition: 0.3s; -} - -.igny8-toggle-slider:before { - position: absolute; - content: ""; - height: 18px; - width: 18px; - left: 3px; - bottom: 3px; - background: white; - border-radius: 50%; - transition: 0.3s; -} - -.igny8-toggle input:checked + .igny8-toggle-slider { - background: var(--green); -} - -.igny8-toggle input:checked + .igny8-toggle-slider:before { - transform: translateX(20px); -} - -/* Mode Toggle Row */ -.igny8-mode-toggle-row { - display: flex; - align-items: center; - justify-content: center; - margin: 15px 0; -} - -.igny8-mode-toggle-label { - display: flex; - align-items: center; - gap: 12px; -} - -.igny8-mode-label { - font-size: 14px; - font-weight: 500; - color: var(--text); -} - -/* Cron Schedule Modal */ -.igny8-cron-config { - margin: 20px 0; -} - -.igny8-cron-item { - margin-bottom: 20px; - padding: 16px; - border: 1px solid var(--stroke); - border-radius: var(--radius); - background: var(--panel-2); -} - -.igny8-cron-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 12px; -} - -.igny8-cron-status { - font-size: 12px; - font-weight: 500; -} - -.igny8-cron-url { - display: flex; - align-items: center; - gap: 12px; - background: var(--panel); - padding: 12px; - border-radius: var(--radius); - border: 1px solid var(--stroke); -} - -.igny8-cron-url code { - flex: 1; - background: none; - color: var(--blue); - font-size: 13px; - word-break: break-all; -} - -.igny8-btn-copy { - background: var(--blue); - color: white; - border: none; - padding: 6px 12px; - border-radius: 4px; - font-size: 12px; - cursor: pointer; - transition: background 0.2s ease; -} - -.igny8-btn-copy:hover { - background: var(--blue-dark); -} - -.igny8-cron-info { - margin-top: 24px; - padding: 16px; - background: var(--panel-2); - border-radius: var(--radius); - border: 1px solid var(--stroke); -} - -.igny8-cron-info h4 { - margin: 0 0 8px 0; - font-size: 14px; - color: var(--text); -} - -.igny8-cron-info code { - background: var(--panel); - padding: 4px 8px; - border-radius: 4px; - font-family: monospace; - color: var(--blue); -} - -.igny8-sidebar-divider {border-bottom: 1px solid rgba(255, 255, 255, .1);margin: 10px 0} - -.igny8-pad-sm { padding: 5px; } -.igny8-pad-md { padding: 10px; } -.igny8-pad-lg { padding: 20px; } -.igny8-pad-xl { padding: 30px; } - - -td.igny8-col-actions button.igny8-btn.igny8-btn-success.igny8-btn-sm, td.igny8-col-actions button.igny8-btn.igny8-btn-danger.igny8-btn-sm {padding: 4px;} -th.igny8-col-actions {min-width:150px;} -td.igny8-col-actions {text-align: center;} - -/* === UI LAYER COMPONENTS === */ - -/* Page Layout Components */ -.igny8-main-content { flex: 1; display: flex; flex-direction: column; } -.igny8-header { padding: 10px 0; border-bottom: 1px solid var(--stroke)} -.igny8-header h1 { font-size: 24px; font-weight: 600; color: #fff; margin: 0; } - -/* Module Header */ -.igny8-module-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 20px; } -.igny8-module-info h2 { font-size: 20px; font-weight: 600; margin: 0 0 8px 0; } -.igny8-module-description { color: var(--text-dim); font-size: 14px; margin: 0; } -.igny8-module-kpis { display: flex; gap: 20px; } -.igny8-kpi-item { text-align: center; } -.igny8-kpi-value { display: block; font-size: 24px; font-weight: 700; color: var(--blue); } -.igny8-kpi-label { font-size: 12px; color: var(--text-dim); text-transform: uppercase; } - -/* Submodule Navigation */ -.igny8-submodule-nav { margin-bottom: 20px; } -.igny8-submodule-tabs { display: flex; list-style: none; border-bottom: 1px solid var(--stroke); } -.igny8-submodule-tab { margin-right: 2px; } -.igny8-submodule-tab a { display: block; padding: 12px 16px; color: var(--text-dim); border-bottom: 2px solid transparent; transition: all 0.2s; } -.igny8-submodule-tab.active a, .igny8-submodule-tab a:hover { color: var(--blue); border-bottom-color: var(--blue); } - -/* Submodule Header */ -.igny8-submodule-header { margin-bottom: 20px; } -.igny8-back-link { margin-bottom: 12px; } -.igny8-btn-back { display: inline-flex; align-items: center; gap: 6px; color: var(--text-dim); font-size: 14px; } -.igny8-btn-back:hover { color: var(--blue); } -.igny8-submodule-title { font-size: 18px; font-weight: 600; margin: 0 0 8px 0; } -.igny8-submodule-description { color: var(--text-dim); font-size: 14px; margin: 0; } - -/* Filters Bar */ -.igny8-filters-bar { background: var(--panel); border: 1px solid var(--stroke); border-radius: var(--radius); padding: 16px; margin-bottom: 20px; } -.igny8-filters-row { display: flex; gap: 16px; align-items: end; flex-wrap: wrap; } -.igny8-filter-item { display: flex; flex-direction: column; gap: 4px; min-width: 120px; } -.igny8-filter-label { font-size: 12px; font-weight: 500; color: var(--text); } -.igny8-filter-search, .igny8-filter-text, .igny8-filter-select, .igny8-filter-date { padding: 8px 12px; border: 1px solid var(--stroke); border-radius: var(--radius); font-size: 14px; } -.igny8-search-wrapper { position: relative; } -.igny8-search-icon { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); color: var(--text-dim); } -.igny8-filter-actions { display: flex; gap: 8px; } - -/* Table Actions */ -.igny8-table-actions { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; } -.igny8-actions-primary, .igny8-actions-secondary { display: flex; gap: 8px; } -.igny8-bulk-actions { display: flex; align-items: center; gap: 12px; } -.igny8-bulk-select-all { display: flex; align-items: center; gap: 6px; font-size: 14px; } -.igny8-bulk-action-select { padding: 6px 10px; border: 1px solid var(--stroke); border-radius: var(--radius); } -.igny8-bulk-count { font-size: 12px; color: var(--text-dim); } - -/* Table */ -.igny8-table-wrapper { background: var(--panel); border: 1px solid var(--stroke); border-radius: var(--radius); overflow: hidden; } -.igny8-table { width: 100%; border-collapse: collapse; } -.igny8-table th, .igny8-table td { padding: 12px; text-align: left; border-bottom: 1px solid var(--stroke); } -.igny8-table th { background: var(--panel-2); font-weight: 600; font-size: 13px; } -.igny8-table tbody tr:hover { background: var(--panel-2); } -.igny8-sortable-link { color: inherit; display: flex; align-items: center; gap: 4px; } -.igny8-sortable-link:hover { color: var(--blue); } -td.igny8-align-center, .igny8-align-center { text-align: center; } -.igny8-align-right { text-align: right; } -.igny8-empty-cell { text-align: center; color: var(--text-dim); font-style: italic; padding: 40px; } - -/* Row Actions */ -.igny8-row-actions { display: flex; gap: 8px; align-items: center; } -.igny8-action-separator { color: var(--text-dim); } - -/* Badges */ -.igny8-badge { padding: 4px 8px; border-radius: 12px; font-size: 11px; font-weight: 600; text-transform: capitalize; } -.igny8-badge-default { background: rgba(103, 112, 109, 0.1); color: var(--text); } -.igny8-badge-success { background: rgba(16,185,129,0.1); color: var(--green-dark); } -.igny8-badge-info { background: rgba(59,130,246,0.1); color: var(--blue-dark); } -.igny8-badge-warning { background: rgba(245,158,11,0.1); color: var(--amber-dark); } -.igny8-badge-danger { background: rgba(239,68,68,0.1); color: var(--red-dark); } -.igny8-badge-secondary { background: rgba(100,116,139,0.1); color: var(--text-dim); } -.igny8-badge-dark-red { background: rgba(220,38,38,0.1); color: #dc2626; } - -/* Pagination */ -.igny8-pagination-wrapper { display: flex; justify-content: space-between; align-items: center; padding: 16px; background: var(--panel-2); } -.igny8-pagination-info { font-size: 14px; color: var(--text-dim); } -.igny8-pagination-list { display: flex; list-style: none; gap: 4px; } -.igny8-pagination-btn { display: flex; align-items: center; gap: 4px; padding: 8px 12px; border: 1px solid var(--stroke); background: var(--panel); color: var(--text); border-radius: var(--radius); font-size: 14px; transition: all 0.2s; } -.igny8-pagination-btn:hover { background: var(--blue); color: white; border-color: var(--blue); } -.igny8-btn-current { background: var(--blue); color: white; border-color: var(--blue); } -.igny8-per-page-selector { display: flex; align-items: center; gap: 8px; font-size: 14px; } - -/* Forms */ -.igny8-form-wrapper { background: var(--panel); border: 1px solid var(--stroke); border-radius: var(--radius); padding: 20px; } -.igny8-form-title { font-size: 18px; font-weight: 600; margin-bottom: 20px; } -.igny8-form-fields { display: flex; flex-direction: column; gap: 16px; } - -/* Hidden elements - no inline styles */ -.igny8-count-hidden { display: none; } -/* Legacy notification hidden class - now handled by unified system */ - -/* Module cards grid */ -.igny8-module-cards-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 20px; - margin: 20px 0; -} -.igny8-form-field { display: flex; flex-direction: column; gap: 6px; } -.igny8-field-label { font-size: 14px; font-weight: 500; color: var(--text); } -.igny8-required { color: var(--red-dark); } -.igny8-input, .igny8-textarea, .igny8-select { padding: 10px 12px; border: 1px solid var(--stroke); border-radius: var(--radius); font-size: 14px; } -.igny8-input:focus, .igny8-textarea:focus, .igny8-select:focus { outline: none; border-color: var(--blue); box-shadow: 0 0 0 2px rgba(59,130,246,0.1); } -.igny8-field-description { font-size: 12px; color: var(--text-dim); } -.igny8-form-actions { display: flex; gap: 12px; margin-top: 20px; } - - -/* Layout Helpers */ -.igny8-submodule-layout { display: flex; flex-direction: column} - - -/*Styled Select Components (matching existing select styles) */ -.igny8-styled-select { position: relative; min-width: 120px; } -.igny8-styled-select-btn { display: flex; align-items: center; justify-content: space-between; width: 100%; padding: 6px 10px; font-size: 14px; background: #fff; border: 1px solid var(--igny8-stroke); border-radius: 4px; cursor: pointer; box-shadow: 0 2px 6px 3px rgba(0, 0, 0, .08); } -.igny8-styled-select-options { display: none; position: absolute; top: calc(100% + 4px); left: 0; right: 0; background: #fff; border: 1px solid var(--igny8-stroke); border-radius: 4px; box-shadow: 0 2px 6px rgba(0,0,0,.08); z-index: 999999; max-height: 200px; overflow-y: auto; } -.igny8-styled-select-item { padding: 6px 10px; font-size: 14px; cursor: pointer; border-bottom: 1px solid #f1f5f9; } -.igny8-styled-select-item:last-child { border-bottom: none; } -.igny8-styled-select-item:hover { background: #f1f5f9; } - -.dd-arrow { font-size: 10px; margin-left: 10px; } -.igny8-input-sm { width: 100%; padding: 6px 8px; font-size: 13px; border: 1px solid var(--igny8-stroke); border-radius: 4px; background: #fff; box-sizing: border-box; } -.igny8-text-sm { font-size: 13px; } -.igny8-mb-5 { margin-bottom: 5px; } -.igny8-text-muted { color: var(--text-dim); } -.igny8-p-5 { padding: 5px 10px !important; } -.igny8-text-xs { font-size: 12px !important; } -.igny8-flex { display: flex; } -.igny8-flex-gap-10 { gap: 10px; } -.igny8-styled-select-options { min-width: 250px; padding: 12px; box-sizing: border-box; } -.igny8-dropdown-panel { pointer-events: auto; } -.igny8-dropdown-panel input, .igny8-dropdown-panel button { pointer-events: auto; } -/* Legacy planner notification - now handled by unified system */ - -/* === CHARTS SYSTEM === */ -/* === 14. Charts & Metrics=== */ - - -/* Header Metrics Container - 2 Row Layout */ -.igny8-header .metrics-container { - display: grid; - grid-template-columns: repeat(3, 1fr); - grid-template-rows: repeat(2, 1fr); - gap: 8px; - margin: 0; - height: 100%; - max-width: 600px; -} - -/* Header Metric Cards - Clean Modern Design */ -.igny8-header .igny8-metric-card { - background: rgba(255, 255, 255, 0.08); - border: 1px solid rgba(255, 255, 255, 0.15); - border-radius: 6px; - padding: 6px 8px; - text-align: center; - transition: all 0.2s ease; - cursor: default; - backdrop-filter: blur(8px); - position: relative; - overflow: hidden; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - min-height: 32px; -} - -.igny8-header .igny8-metric-card:hover { - background: rgba(255, 255, 255, 0.12); - border-color: rgba(255, 255, 255, 0.25); - transform: translateY(-1px); -} - -.igny8-header .igny8-metric-card.blue { - border-top: 2px solid var(--blue); -} - -.igny8-header .igny8-metric-card.green { - border-top: 2px solid var(--green); -} - -.igny8-header .igny8-metric-card.amber { - border-top: 2px solid var(--amber); -} - -.igny8-header .igny8-metric-card.purple { - border-top: 2px solid var(--purple); -} - -.igny8-header .igny8-metric-card.orange { - border-top: 2px solid var(--amber); -} - -.igny8-header .igny8-metric-card.red { - border-top: 2px solid var(--red-dark); -} - -.igny8-header .igny8-metric-card.gray { - border-top: 2px solid #6b7280; -} - -.igny8-header .igny8-metric-number { - font-size: 14px; - font-weight: 700; - margin: 0; - color: #ffffff; - text-shadow: 0 1px 2px rgba(0,0,0,0.4); - line-height: 1; -} - -.igny8-header .igny8-metric-label { - font-size: 9px; - font-weight: 500; - margin: 1px 0 0 0; - color: rgba(255, 255, 255, 0.75); - text-transform: uppercase; - letter-spacing: 0.3px; - line-height: 1; -} - - - -/* =================================================================== - STATUS CIRCLE STYLES FOR SYSTEM SUMMARY - =================================================================== */ - -.bg-circle { - width: 40px; - height: 40px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-weight: 600; - font-size: 11px; - color: white; - cursor: help; - transition: all 0.2s ease; - border: 2px solid transparent; -} - -.bg-circle:hover { - transform: scale(1.1); - border-color: rgba(255, 255, 255, 0.3); -} - -.bg-success { - background-color: var(--green); - box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3); -} - -.bg-error { - background-color: var(--red); - box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3); -} - -.bg-success:hover { - background-color: var(--green-dark); - box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4); -} - -.bg-error:hover { - background-color: var(--red-dark); - box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4); -} - -/* =================================================================== - RADIO BUTTON STYLES FOR DEBUG SETTINGS - =================================================================== */ - -input[type="radio"] { - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; - width: 16px; - height: 16px; - border: 2px solid #d0d1d3; - border-radius: 50%; - background-color: var(--panel); - cursor: pointer; - position: relative; - transition: all 0.2s ease; -} - -input[type="radio"]:hover { - border-color: var(--blue); -} - -input[type="radio"]:checked { - border-color: var(--blue); - background-color: var(--blue); -} - -input[type="radio"]:checked::after { - content: ''; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 6px; - height: 6px; - border-radius: 50%; - background-color: white; -} - -input[type="radio"]:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.bg-circle {width: 50px;height: 50px;border-radius: 50%;} -.bg-circle-sm {width: 18px;height: 18px;border-radius: 50%;} -.bg-success {background: var(--green);} -.bg-danger,.bg-error {background: var(--red-dark);} -.bg-warning,.bg-amber {background: var(--amber);} -.bg-info {background: var(--blue);} -.bg-secondary {background: var(--text-dim);} - - -.igny8-form-group, .igny8-radio-group {padding: 5px 0} -.igny8-radio-group {margin: 0 15px} -/* =================================================================== - SYSTEM-WIDE DEBUG TABLE STYLES - =================================================================== */ - -/* ========================================= - Planner Settings Styles - ========================================= */ - .igny8-metrics-compact { - display: grid; - grid-template-columns: repeat(4, auto); - gap: 6px 15px; - padding: 4px 6px; - background: #526e8d3b; - border-radius: 8px; - width: fit-content; - float: right; -} - - .metric { - background: rgba(255, 255, 255, 0.05); - padding: 4px 8px; - border-radius: 6px; - text-align: center; - font-family: 'Inter', sans-serif; - min-width: 90px; - transition: 0.15s ease-in-out; - border-left: 3px solid rgba(255, 255, 255, 0.1); - display: flex; - flex-direction: row-reverse; - gap: 10px; - justify-content: space-between; - } - - .metric:hover { - background: rgba(255,255,255,0.08); - transform: translateY(-1px); - } - - .metric .val { - display: block; - font-size: 14px; - font-weight: 600; - color: #fff; - line-height: 1.2; - } - - .metric .lbl { - font-size: 10px; - letter-spacing: 0.3px; - color: rgba(255,255,255,0.6); - text-transform: uppercase; - } - - /* Color Variants */ - .metric.green { border-left-color: var(--green, #00c985); } - .metric.amber { border-left-color: var(--amber, #f39c12); } - .metric.purple { border-left-color: var(--purple, #9b59b6); } - .metric.blue { border-left-color: var(--blue, #3498db); } - .metric.teal { border-left-color: var(--teal, #1abc9c); } - -/* === DASHBOARD OVERVIEW STYLES === */ - -/* Dashboard Sections */ -.igny8-dashboard-section { - margin-bottom: 24px; - height: 100%; - display: flex; - flex-direction: column; -} - -/* Progress Bar Styles */ -.igny8-progress-item { - margin-bottom: 20px; - padding-bottom: 16px; - border-bottom: 1px solid var(--stroke); -} - -.igny8-progress-item:last-child { - border-bottom: none; - margin-bottom: 0; - padding-bottom: 0; -} - -.igny8-progress-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 8px; -} - -.igny8-progress-label { - font: 500 14px/1.4 'Inter', system-ui, sans-serif; - color: var(--text); -} - -.igny8-progress-percent { - font: 600 14px/1.4 'Inter', system-ui, sans-serif; - color: var(--blue-dark); -} - -.igny8-progress-bar { - width: 100%; - height: 8px; - background: var(--panel-2); - border-radius: 4px; - overflow: hidden; - margin-bottom: 6px; -} - -.igny8-progress-fill { - height: 100%; - border-radius: 4px; - transition: width 0.3s ease; -} - -.igny8-progress-blue { - background: linear-gradient(90deg, var(--blue) 0%, var(--blue-dark) 100%); -} - -.igny8-progress-green { - background: linear-gradient(90deg, var(--green) 0%, var(--green-dark) 100%); -} - -.igny8-progress-amber { - background: linear-gradient(90deg, var(--amber) 0%, var(--amber-dark) 100%); -} - -.igny8-progress-purple { - background: linear-gradient(90deg, #8b5cf6 0%, #7c3aed 100%); -} - -.igny8-progress-text-dim { - background: linear-gradient(90deg, var(--text-dim) 0%, #64748b 100%); -} - -.igny8-progress-red { - background: linear-gradient(90deg, #e53e3e 0%, #c53030 100%); -} - -.igny8-progress-details { - font: 400 12px/1.4 'Inter', system-ui, sans-serif; - color: var(--text-dim); -} - -/* Status Cards */ -.igny8-status-cards { - gap: 20px; -} - -.igny8-status-card { - cursor: pointer; - transition: all 0.2s ease; - border: none; - background: var(--panel); -} - -.igny8-status-card:hover { - transform: translateY(-3px); - box-shadow: 0 8px 20px rgba(0,0,0,0.15), 0 12px 28px rgba(13,27,42,0.12); -} - -/* Colored Status Card Variants */ -.igny8-status-blue { - background: linear-gradient(135deg, var(--blue) 0%, var(--blue-dark) 100%); - color: white; -} - -.igny8-status-green { - background: linear-gradient(135deg, var(--green) 0%, var(--green-dark) 100%); - color: white; -} - -.igny8-status-amber { - background: linear-gradient(135deg, var(--amber) 0%, var(--amber-dark) 100%); - color: white; -} - -.igny8-clickable-card { - cursor: pointer; -} - -.igny8-status-metric { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 4px; -} - -.igny8-status-count { - font: 700 28px/1.2 'Inter', system-ui, sans-serif; - color: rgba(255, 255, 255, 0.95); - margin: 0; - text-shadow: 0 1px 2px rgba(0,0,0,0.1); -} - -.igny8-status-label { - font: 500 13px/1.3 'Inter', system-ui, sans-serif; - color: rgba(255, 255, 255, 0.85); - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.igny8-status-icon { - position: absolute; - top: 16px; - right: 16px; - opacity: 0.7; -} - -.igny8-status-card .igny8-card-body { - position: relative; - padding: 0 20px; - display: flex; - align-items: center; - justify-content: space-between; -} - -/* Next Actions Panel */ -.igny8-next-actions { - display: flex; - flex-direction: column; - gap: 12px; -} - -.igny8-action-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 12px 16px; - background: var(--panel-2); - border-radius: 6px; - border-left: 3px solid var(--blue); - transition: all 0.2s ease; -} - -.igny8-action-item:hover { - background: #f8fafc; - border-left-color: var(--blue-dark); -} - -.igny8-action-item.igny8-action-complete { - border-left-color: var(--green); - background: #f0fdf4; -} - -.igny8-action-text { - font: 500 14px/1.4 'Inter', system-ui, sans-serif; - color: var(--text); -} - -.igny8-action-status { - font: 500 13px/1.4 'Inter', system-ui, sans-serif; - color: var(--green); -} - -.igny8-btn-text { - background: none; - border: none; - color: var(--blue); - font: 500 13px/1.4 'Inter', system-ui, sans-serif; - padding: 4px 8px; - border-radius: 4px; - text-decoration: none; - transition: all 0.2s ease; -} - -.igny8-btn-text:hover { - background: var(--blue); - color: white; - text-decoration: none; -} - -/* Info Box Styles */ -.igny8-info-box { - background: #f0f8ff; - border: 1px solid #b3d9ff; - border-radius: 6px; - padding: 16px; - margin: 16px 0; -} - -.igny8-info-box p { - margin: 0 0 12px 0; - color: #555; -} - -.igny8-info-box p:last-child { - margin-bottom: 0; -} - -/* =================================================================== - UNIFIED NOTIFICATION SYSTEM - =================================================================== */ - -/* Single Global Notification Container */ -#igny8-global-notification { - position: fixed; - top: 20px; - right: 20px; - padding: 12px 20px; - border-radius: 6px; - color: white; - font-weight: 500; - z-index: 9999; - max-width: 400px; - box-shadow: 0 4px 12px rgba(0,0,0,0.15); - transition: all 0.3s ease; - transform: translateX(0); - font-family: 'Inter', system-ui, sans-serif; - font-size: 14px; - line-height: 1.4; - display: none; -} - -/* Notification Type Styles */ -#igny8-global-notification.success { - background: var(--green); - border-left: 4px solid var(--green-dark); -} - -#igny8-global-notification.error { - background: var(--red-dark); - border-left: 4px solid #b91c1c; -} - -#igny8-global-notification.warning { - background: var(--amber); - border-left: 4px solid var(--amber-dark); -} - -#igny8-global-notification.info { - background: var(--blue); - border-left: 4px solid var(--blue-dark); -} - -/* Animation States */ -#igny8-global-notification.show { - display: block !important; - animation: slideInRight 0.3s ease-out; - opacity: 1; - visibility: visible; -} - -#igny8-global-notification.hide { - animation: slideOutRight 0.3s ease-in; - opacity: 0; - visibility: hidden; -} - -@keyframes slideInRight { - from { - opacity: 0; - transform: translateX(100%); - } - to { - opacity: 1; - transform: translateX(0); - } -} - -@keyframes slideOutRight { - from { - opacity: 1; - transform: translateX(0); - } - to { - opacity: 0; - transform: translateX(100%); - } -} - -/* Hover Effects */ -#igny8-global-notification:hover { - transform: translateX(-5px); - box-shadow: 0 6px 16px rgba(0,0,0,0.2); -} - -/* Textarea Color Variants */ -.igny8-textarea-green { - border-left: 3px solid var(--green); -} - -.igny8-textarea-orange { - border-left: 3px solid var(--amber); -} - -.igny8-textarea-blue { - border-left: 3px solid var(--blue); -} - -.igny8-textarea-purple { - border-left: 3px solid var(--purple); -} - -.igny8-textarea-teal { - border-left: 3px solid var(--teal); -} - -.igny8-textarea-indigo { - border-left: 3px solid var(--indigo); -} - -/* Recent Activity Styles */ -.igny8-recent-activity { - display: flex; - flex-direction: column; - gap: 12px; -} - -.igny8-list-item { - padding: 12px; - border: 1px solid var(--stroke); - border-radius: var(--radius); - background: var(--panel); - transition: all 0.2s ease; -} - -.igny8-list-item:hover { - border-color: var(--blue); - box-shadow: 0 2px 8px rgba(59, 130, 246, 0.1); -} - -.igny8-item-content { - display: flex; - flex-direction: column; - gap: 6px; -} - -.igny8-item-title a { - font-weight: 600; - color: var(--text); - text-decoration: none; - font-size: 14px; -} - -.igny8-item-title a:hover { - color: var(--blue); -} - -.igny8-item-meta { - display: flex; - align-items: center; - gap: 12px; - font-size: 12px; - color: var(--text-muted); -} - -.igny8-item-cluster { - color: var(--text-muted); -} - -.igny8-item-date { - color: var(--text-muted); -} - -.igny8-empty-state { - text-align: center; - padding: 40px 20px; - color: var(--text-muted); -} - -.igny8-empty-state p { - margin-bottom: 16px; -} - -/* Content Types and Publishing Stats */ -.igny8-content-types, .igny8-publishing-stats { - display: flex; - flex-direction: column; - gap: 16px; -} - -.igny8-type-item, .igny8-stat-item { - display: flex; - flex-direction: column; - gap: 8px; -} - -.igny8-type-info, .igny8-stat-header { - display: flex; - justify-content: space-between; - align-items: center; -} - -.igny8-type-name, .igny8-stat-label { - font-weight: 600; - color: var(--text); - font-size: 14px; -} - -.igny8-type-count, .igny8-stat-count { - font-weight: 700; - color: var(--blue); - font-size: 16px; -} - -.igny8-type-bar, .igny8-stat-bar { - height: 6px; - background: var(--stroke); - border-radius: 3px; - overflow: hidden; -} - -.igny8-type-progress, .igny8-stat-progress { - height: 100%; - background: var(--blue); - border-radius: 3px; - transition: width 0.3s ease; -} - -.igny8-stat-progress.igny8-progress-amber { - background: var(--amber); -} - -.igny8-stat-progress.igny8-progress-green { - background: var(--green); -} - -.igny8-stat-progress.igny8-progress-purple { - background: var(--purple); -} - -/* Enhanced Analytics Cards */ -.igny8-equal-height { - align-items: stretch; -} - -.igny8-analytics-card .igny8-card { - height: 100%; - display: flex; - flex-direction: column; -} - -.igny8-card-header { - display: flex; - justify-content: space-between; - align-items: flex-start; - margin-bottom: 20px; - padding-bottom: 16px; - border-bottom: 1px solid var(--stroke); -} - -.igny8-card-header-content { - display: flex; - align-items: flex-start; - gap: 12px; - flex: 1; -} - -.igny8-card-icon { - flex-shrink: 0; - width: 40px; - height: 40px; - background: rgba(59, 130, 246, 0.1); - border-radius: 8px; - display: flex; - align-items: center; - justify-content: center; -} - -.igny8-card-title-text h3 { - margin: 0 0 4px 0; - font-size: 16px; - font-weight: 600; - color: var(--text); -} - -.igny8-card-subtitle { - margin: 0; - font-size: 12px; - color: var(--text-dim); - font-weight: 400; -} - -.igny8-card-subtitle.igny8-centered { - text-align: center; -} - -/* Standard Dashboard Card Headers */ -.igny8-standard-header { - background: #fff !important; - border-bottom: 2px solid var(--blue) !important; - margin-bottom: 16px !important; - padding: 16px !important; - border-radius: 0 !important; -} - -.igny8-standard-header .igny8-card-title-text h3 { - color: var(--navy-bg-2) !important; - font-weight: 600 !important; - font-size: 26px !important; - margin: 0 0 4px 0 !important; -} - -.igny8-standard-header .igny8-card-subtitle { - color: var(--text-dim) !important; - font-size: 13px !important; - font-weight: 400 !important; -} - -.igny8-standard-header .igny8-card-icon { - background: rgba(59, 130, 246, 0.08) !important; - border-radius: 8px !important; - width: 40px !important; - height: 40px !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - flex-shrink: 0 !important; -} - -.igny8-standard-header .igny8-card-header-content { - display: flex !important; - align-items: center !important; - justify-content: space-between !important; - width: 100% !important; -} - -/* Dashboard Icon Styles */ -.igny8-dashboard-icon-sm { - color: rgba(255,255,255,0.7) !important; - font-size: 24px !important; -} - -.igny8-dashboard-icon-lg { - font-size: 26px !important; -} - -.igny8-dashboard-icon-blue { - color: var(--blue) !important; -} - -.igny8-dashboard-icon-amber { - color: var(--amber) !important; -} - -.igny8-dashboard-icon-green { - color: var(--green) !important; -} - -.igny8-dashboard-icon-purple { - color: #8b5cf6 !important; -} - -.igny8-dashboard-icon-dim { - color: var(--text-dim) !important; - font-size: 32px !important; - margin-bottom: 12px !important; -} - -.igny8-card-metric { - text-align: right; - flex-shrink: 0; -} - -.igny8-metric-value { - display: block; - font-size: 24px; - font-weight: 700; - color: var(--blue); - line-height: 1; -} - -.igny8-metric-label { - display: block; - font-size: 11px; - color: var(--text-dim); - text-transform: uppercase; - font-weight: 500; - margin-top: 2px; -} - -.igny8-analytics-list { - display: flex; - flex-direction: column; - gap: 16px; -} - -.igny8-analytics-item { - display: flex; - flex-direction: column; - gap: 8px; -} - -.igny8-analytics-item.igny8-analytics-total { - padding-top: 16px; - border-top: 1px solid var(--stroke); - margin-top: 8px; -} - -.igny8-item-info { - display: flex; - justify-content: space-between; - align-items: center; -} - -.igny8-item-label { - font-size: 14px; - font-weight: 500; - color: var(--text); -} - -.igny8-item-value { - font-size: 16px; - font-weight: 700; - color: var(--blue); -} - -.igny8-item-progress { - display: flex; - align-items: center; - gap: 12px; -} - -.igny8-progress-track { - flex: 1; - height: 6px; - background: var(--stroke); - border-radius: 3px; - overflow: hidden; -} - -.igny8-progress-percent { - font-size: 12px; - font-weight: 600; - color: var(--text-dim); - min-width: 32px; - text-align: right; -} - -.igny8-empty-analytics { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 40px 20px; - text-align: center; - color: var(--text-dim); -} - -.igny8-empty-analytics p { - margin: 12px 0 16px 0; - font-size: 14px; -} - -.igny8-btn-sm { - padding: 6px 12px; - font-size: 12px; -} - -.igny8-status-desc { - font-size: 12px; - color: #fff; -} - -/* Step-by-Step UX Guide */ -.igny8-step-guide { - background: var(--panel); - border: 1px solid var(--stroke); - border-radius: var(--radius); - padding: 20px; - margin-bottom: 20px; - box-shadow: 0 2px 6px rgba(0,0,0,0.08), 0 4px 10px rgba(13,27,42,0.06); -} - -.igny8-step-guide-header { - display: flex; - align-items: center; - margin-bottom: 15px; -} - -.igny8-step-guide-header h3 { - margin: 0; - color: var(--blue-dark); - font-size: 16px; - font-weight: 600; -} - -.igny8-step-guide-header .dashicons { - margin-right: 8px; - color: var(--blue); -} - -.igny8-steps-container { - display: flex; - gap: 15px; - overflow-x: auto; - padding-bottom: 10px; - scrollbar-width: thin; - scrollbar-color: var(--stroke) transparent; -} - -.igny8-steps-container::-webkit-scrollbar { - height: 6px; -} - -.igny8-steps-container::-webkit-scrollbar-track { - background: transparent; -} - -.igny8-steps-container::-webkit-scrollbar-thumb { - background: var(--stroke); - border-radius: 3px; -} - -.igny8-step { - flex: 0 0 auto; - min-width: 180px; - max-width: 220px; - background: var(--bg); - border: 1px solid var(--stroke); - border-radius: var(--radius); - padding: 15px; - position: relative; - transition: all 0.2s ease; -} - -.igny8-step:hover { - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0,0,0,0.1); -} - -.igny8-step-number { - display: inline-flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - background: var(--blue); - color: white; - border-radius: 50%; - font-size: 12px; - font-weight: 600; - margin-bottom: 8px; -} - -.igny8-step.completed .igny8-step-number { - background: var(--green); -} - -.igny8-step.current .igny8-step-number { - background: var(--amber); - color: var(--text); -} - -.igny8-step-title { - font-size: 14px; - font-weight: 600; - color: var(--text); - margin-bottom: 6px; - line-height: 1.3; -} - -.igny8-step-status { - display: flex; - align-items: center; - gap: 6px; - margin-bottom: 8px; -} - -.igny8-step-status-icon { - font-size: 14px; -} - -.igny8-step-status-text { - font-size: 12px; - font-weight: 500; - color: var(--text-dim); -} - -.igny8-step.completed .igny8-step-status-text { - color: var(--green-dark); -} - -.igny8-step.current .igny8-step-status-text { - color: var(--amber-dark); -} - -.igny8-step.completed .igny8-step-status-icon { - color: var(--green); -} - -.igny8-step.current .igny8-step-status-icon { - color: var(--amber); -} - -.igny8-step-data { - font-size: 11px; - color: var(--text-dim); - margin-bottom: 8px; - line-height: 1.3; -} - -.igny8-step-action { - margin-top: 8px; -} - -.igny8-step-action .igny8-btn { - font-size: 11px; - padding: 4px 8px; - border-radius: 3px; -} - -.igny8-step-connector { - position: absolute; - top: 50%; - right: -8px; - width: 16px; - height: 2px; - background: var(--stroke); - transform: translateY(-50%); -} - -.igny8-step:last-child .igny8-step-connector { - display: none; -} - -.igny8-step.completed + .igny8-step .igny8-step-connector { - background: var(--green); -} - -/* Responsive adjustments */ -@media (max-width: 768px) { - .igny8-step { - min-width: 160px; - } - - .igny8-steps-container { - gap: 10px; - } -} - -/* System-Wide Workflow Guide */ -.igny8-system-workflow { - background: var(--panel); - border: 1px solid var(--stroke); - border-radius: var(--radius); - padding: 25px; - margin-bottom: 25px; - box-shadow: 0 4px 12px rgba(0,0,0,0.08), 0 6px 16px rgba(13,27,42,0.06); -} - -.igny8-system-workflow-header { - display: flex; - align-items: center; - margin-bottom: 20px; -} - -.igny8-system-workflow-header h2 { - margin: 0; - color: var(--blue-dark); - font-size: 20px; - font-weight: 700; -} - -.igny8-system-workflow-header .dashicons { - margin-right: 12px; - color: var(--blue); - font-size: 24px; -} - -.igny8-system-workflow-subtitle { - color: var(--text-dim); - font-size: 14px; - margin-top: 5px; - margin-bottom: 0; -} - -.igny8-system-steps-container { - display: flex; - gap: 12px; - overflow-x: auto; - padding-bottom: 15px; - scrollbar-width: thin; - scrollbar-color: var(--stroke) transparent; - max-width: 1200px; -} - -.igny8-system-steps-container::-webkit-scrollbar { - height: 8px; -} - -.igny8-system-steps-container::-webkit-scrollbar-track { - background: transparent; -} - -.igny8-system-steps-container::-webkit-scrollbar-thumb { - background: var(--stroke); - border-radius: 4px; -} - -.igny8-system-step { - flex: 0 0 auto; - min-width: 160px; - max-width: 180px; - background: var(--bg); - border: 1px solid var(--stroke); - border-radius: var(--radius); - padding: 16px; - position: relative; - transition: all 0.3s ease; - cursor: pointer; -} - -.igny8-system-step:hover { - transform: translateY(-3px); - box-shadow: 0 6px 20px rgba(0,0,0,0.12); - border-color: var(--blue); -} - -.igny8-system-step.disabled { - opacity: 0.6; - cursor: not-allowed; -} - -.igny8-system-step.disabled:hover { - transform: none; - box-shadow: none; - border-color: var(--stroke); -} - -.igny8-system-step-number { - display: inline-flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - background: var(--blue); - color: white; - border-radius: 50%; - font-size: 13px; - font-weight: 700; - margin-bottom: 10px; -} - -.igny8-system-step.completed .igny8-system-step-number { - background: var(--green); -} - -.igny8-system-step.in_progress .igny8-system-step-number { - background: var(--amber); - color: var(--text); -} - -.igny8-system-step.missing .igny8-system-step-number { - background: var(--text-dim); -} - -.igny8-system-step.disabled .igny8-system-step-number { - background: var(--stroke); - color: var(--text-dim); -} - -.igny8-system-step-title { - font-size: 13px; - font-weight: 600; - color: var(--text); - margin-bottom: 8px; - line-height: 1.3; -} - -.igny8-system-step-status { - display: flex; - align-items: center; - gap: 6px; - margin-bottom: 8px; -} - -.igny8-system-step-status-icon { - font-size: 16px; -} - -.igny8-system-step-status-text { - font-size: 11px; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.igny8-system-step.completed .igny8-system-step-status-text { - color: var(--green-dark); -} - -.igny8-system-step.in_progress .igny8-system-step-status-text { - color: var(--amber-dark); -} - -.igny8-system-step.missing .igny8-system-step-status-text { - color: var(--text-dim); -} - -.igny8-system-step.completed .igny8-system-step-status-icon { - color: var(--green); -} - -.igny8-system-step.in_progress .igny8-system-step-status-icon { - color: var(--amber); -} - -.igny8-system-step.missing .igny8-system-step-status-icon { - color: var(--text-dim); -} - -.igny8-system-step-data { - font-size: 10px; - color: var(--text-dim); - margin-bottom: 10px; - line-height: 1.4; -} - -.igny8-system-step-action { - margin-top: 8px; -} - -.igny8-system-step-action .igny8-btn { - font-size: 10px; - padding: 4px 8px; - border-radius: 3px; - width: 100%; - text-align: center; -} - -.igny8-system-step-connector { - position: absolute; - top: 50%; - right: -7px; - width: 14px; - height: 2px; - background: var(--stroke); - transform: translateY(-50%); - z-index: 1; -} - -.igny8-system-step:last-child .igny8-system-step-connector { - display: none; -} - -.igny8-system-step.completed + .igny8-system-step .igny8-system-step-connector { - background: var(--green); -} - -.igny8-system-step.in_progress + .igny8-system-step .igny8-system-step-connector { - background: var(--amber); -} - -/* System workflow responsive adjustments */ -@media (max-width: 1200px) { - .igny8-system-step { - min-width: 140px; - max-width: 160px; - } -} - -@media (max-width: 768px) { - .igny8-system-step { - min-width: 120px; - max-width: 140px; - padding: 12px; - } - - .igny8-system-steps-container { - gap: 8px; - } - - .igny8-system-step-number { - width: 24px; - height: 24px; - font-size: 11px; - } - - .igny8-system-step-title { - font-size: 12px; - } -} -.workflow-steps { - display: flex; -} -/* === WORDPRESS ADMIN STYLES === */ -/* Ensure WordPress admin styles are available for cron pages */ -.wp-list-table { - border: 1px solid #c3c4c7; - border-spacing: 0; - width: 100%; - clear: both; - margin: 0; -} - -.wp-list-table.widefat { - border-collapse: collapse; -} - -.wp-list-table.fixed { - table-layout: fixed; -} - -.wp-list-table.striped tbody tr:nth-child(odd) { - background-color: #f6f7f7; -} - -.wp-list-table.striped tbody tr:nth-child(even) { - background-color: #fff; -} - -.wp-list-table th, -.wp-list-table td { - border-bottom: 1px solid #c3c4c7; - padding: 8px 10px; - text-align: left; - vertical-align: top; -} - -.wp-list-table th { - background-color: #f1f1f1; - font-weight: 600; - color: #1d2327; -} - -.wp-list-table tbody tr:hover { - background-color: #f0f6fc; -} - - - - - - -/* WordPress admin wrap styles */ -.wrap { - margin: 0 20px 0 2px; -} - -.wrap h1 { - margin: 0 0 20px; - padding: 0; - font-size: 23px; - font-weight: 400; - line-height: 1.3; - color: #1d2327; -} - - - - - - -/* WordPress admin notice styles */ -.notice { - background: #fff; - border-left: 4px solid #fff; - box-shadow: 0 1px 1px 0 rgba(0,0,0,.1); - margin: 5px 15px 2px; - padding: 1px 12px; -} - -.notice.notice-success { - border-left-color: #00a32a; -} - -.notice p { - margin: .5em 0; - padding: 2px; -} - -/* WordPress admin submit styles */ -.submit { - padding: 0; - margin: 0; -} - - -.ai-integration, .new-content-status { - border-right: 3px solid #ccc; - margin-right: 25px; - padding-right: 25px; -} -#igny8-ai-integration-form .igny8-form-group h4 {margin-bottom: 35px;} -.new-content-status .igny8-form-group h4 {margin-bottom: 20px;} - - -.igny8-flex-row { - display: flex; - align-items: center; - align-content: center; - -} - -/* Workflow section styling */ -.igny8-workflow-section { - margin-top: 30px; -} - -.igny8-step-card { - border-left: 4px solid #e5e7eb; - transition: all 0.3s ease; -} - -.igny8-step-card.completed { - border-left-color: #10b981; - background: linear-gradient(135deg, #f0fdf4 0%, #ecfdf5 100%); -} - -.igny8-step-card.current { - border-left-color: #3b82f6; - background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%); -} - -.igny8-step-card.pending { - border-left-color: #f59e0b; - background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%); -} - -.igny8-step-header { - display: flex; - align-items: center; - gap: 15px; - margin-bottom: 15px; -} - -.igny8-step-number { - background: #6b7280; - color: white; - width: 35px; - height: 35px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-weight: bold; - font-size: 16px; - flex-shrink: 0; -} - -.igny8-step-card.completed .igny8-step-number { - background: #10b981; -} - -.igny8-step-card.current .igny8-step-number { - background: #3b82f6; -} - -.igny8-step-card.pending .igny8-step-number { - background: #f59e0b; -} - -.igny8-step-title { - font-size: 18px; - font-weight: 600; - color: #1f2937; - margin: 0; -} - -.igny8-step-status { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 10px; -} - -.igny8-step-status-icon { - font-size: 16px; -} - -.igny8-step-status-text { - font-size: 14px; - font-weight: 500; -} - -.igny8-step-card.completed .igny8-step-status-text { - color: #10b981; -} - -.igny8-step-card.current .igny8-step-status-text { - color: #3b82f6; -} - -.igny8-step-card.pending .igny8-step-status-text { - color: #f59e0b; -} - -.igny8-step-data { - color: #6b7280; - font-size: 14px; - margin-bottom: 15px; -} - -.igny8-step-action { - margin-top: 15px; -} - -.igny8-grid-4 { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 20px; -} - -/* Card layout optimization for settings cards */ -.igny8-flex-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 20px; - width: 100%; -} - -.igny8-card-header-content { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - gap: 20px; -} - -.igny8-card-title-text { - display: flex; - flex-direction: column; - align-items: flex-start; - flex: 1; - min-width: 0; -} - -.igny8-card-title-text h3 { - margin: 0 0 5px 0; - font-size: 18px; - font-weight: 600; - color: #1f2937; -} - -.igny8-card-title-text .igny8-card-subtitle { - margin: 0; - font-size: 14px; - color: #6b7280; -} - -.igny8-flex-row form { - display: flex; - align-items: flex-end; - gap: 20px; - flex: 1; - justify-content: flex-end; - flex-direction: column; - align-content: flex-end; -} - -.igny8-form-group {display: flex;align-items: flex-start;gap: 5px;flex: 1;flex-direction: column;} - -/* Editor Type Selection Styles */ -.igny8-editor-option { - display: block; - margin-bottom: 15px; - padding: 15px; - border: 2px solid #e1e5e9; - border-radius: 8px; - cursor: pointer; - transition: all 0.3s ease; - background: #fff; -} - -.igny8-editor-option:hover { - border-color: #0073aa; - background-color: #f8f9fa; -} - -.igny8-editor-option.selected { - border-color: #0073aa; - background-color: #f0f8ff; - box-shadow: 0 2px 8px rgba(0, 115, 170, 0.1); -} - -.igny8-editor-option input[type="radio"] { - margin-right: 10px; - transform: scale(1.2); -} - -.igny8-editor-option-content { - display: inline-block; - vertical-align: top; - width: calc(100% - 30px); -} - -.igny8-editor-option-title { - font-size: 16px; - font-weight: 600; - color: #333; - margin: 0 0 5px 0; -} - -.igny8-editor-option-description { - margin: 5px 0 0 0; - color: #666; - font-size: 14px; - line-height: 1.4; -} - -.igny8-form-actions { - display: flex; - align-items: center; - flex-shrink: 0; - justify-content: flex-end; -} - -.igny8-card-body { - padding: 20px; -} - -.igny8-mode-toggle-label, -.igny8-radio-group { - display: flex; - align-items: center; - gap: 10px; - white-space: nowrap; -} - -.igny8-mode-toggle-label { - gap: 15px; -} - - - -/* Responsive adjustments */ -@media (max-width: 768px) { - .igny8-grid-4 { - grid-template-columns: 1fr; - } - - .igny8-step-header { - flex-direction: column; - text-align: center; - gap: 10px; - } - - .igny8-flex-row { - flex-direction: column; - align-items: stretch; - gap: 15px; - } - - .igny8-card-header-content { - flex-direction: column; - align-items: stretch; - } - - .igny8-flex-row form { - flex-direction: column; - align-items: stretch; - } - - .igny8-form-group { - justify-content: flex-start; - } -} - -#igny8-new-content-form .igny8-flex-row .igny8-form-actions, #igny8-ai-integration-form .igny8-flex-row .igny8-form-actions {margin-top: 0;} - - -.igny8-card .igny8-standard-header .igny8-card-title-text h3 { - font-size: 20px !important; -} - -.igny8-error-log { - width: 800px; -} -.igny8-form-group select{min-width: 200px;} -.igny8-form-group textarea {width: 80%;} - -/* Title with Badge Layout */ -.igny8-title-with-badge { - display: flex; - align-items: center; - gap: 8px; -} - -.igny8-title-actions { - display: flex; - align-items: center; - gap: 4px; - margin-left: auto; -} - -.igny8-title-text { - flex: 1; -} - -.igny8-menu-toggle { - padding: 8px; - border: none; - background: transparent; - cursor: pointer; - transition: all 0.2s ease; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 4px; -} - -.igny8-menu-toggle:hover { - background: rgba(0, 0, 0, 0.05); - transform: scale(1.1); -} - -.igny8-hamburger { - display: flex; - flex-direction: column; - gap: 3px; - width: 16px; - height: 14px; -} - -.igny8-hamburger span { - display: block; - width: 100%; - height: 2px; - background: var(--blue); - border-radius: 1px; - transition: all 0.2s ease; -} - -.igny8-menu-toggle:hover .igny8-hamburger span { - background: var(--blue-dark); -} - -/* Expandable Description Row */ -.igny8-description-row { - display: none; - background: var(--panel-2); - border-top: 1px solid var(--border); -} - -.igny8-description-row.expanded { - display: table-row; -} - -.igny8-description-content-cell { - padding: 16px; - color: var(--text); - line-height: 1.5; -} - -.igny8-description-content { - background: var(--panel-1); - border-radius: 6px; - padding: 12px; - border: 1px solid var(--border); -} - -.igny8-description-content p { - margin: 0 0 8px 0; -} - -.igny8-description-content p:last-child { - margin-bottom: 0; -} - -/* Description Section Styling */ -.description-section { - margin-bottom: 16px; - padding: 12px; - background: var(--panel-2); - border-radius: 6px; - border-left: 4px solid var(--blue); -} - -.description-section:last-child { - margin-bottom: 0; -} - -.section-heading { - margin: 0 0 8px 0; - color: var(--blue); - font-size: 14px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.section-content { - position: relative; -} - -.content-type-badge { - display: inline-block; - background: var(--blue); - color: white; - padding: 2px 8px; - border-radius: 12px; - font-size: 10px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - margin-bottom: 8px; -} - -.content-details { - color: var(--text); - line-height: 1.5; - font-size: 13px; -} - -.description-item { - margin-bottom: 8px; - padding: 8px; - background: var(--panel-2); - border-radius: 4px; - border-left: 3px solid var(--blue); -} - -.description-item:last-child { - margin-bottom: 0; -} - -.description-item strong { - color: var(--blue); - display: block; - margin-bottom: 4px; - font-size: 12px; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.description-text { - color: var(--text); - line-height: 1.5; - white-space: pre-wrap; -} - -/* Image Prompts Toggle Styles */ -.igny8-image-prompts-display { - display: flex; - align-items: center; - gap: 8px; -} - -.igny8-image-icon { - font-size: 16px; - display: inline-block; - width: 16px; - height: 16px; - line-height: 1; -} - -.igny8-image-prompts-toggle { - background: none; - border: none; - padding: 8px; - margin-bottom: 3px; - cursor: pointer; - border-radius: 4px; - transition: all 0.2s ease; - -} - -.igny8-image-prompts-toggle:hover { - background: rgba(0, 0, 0, 0.05); - transform: scale(1.1); -} - -/* Expandable Image Prompts Row */ -.igny8-image-prompts-row { - display: none; - background: var(--panel-2); - border-top: 1px solid var(--border); -} - -.igny8-image-prompts-row.expanded { - display: table-row; -} - -.igny8-image-prompts-content-cell { - padding: 16px; - color: var(--text); - line-height: 1.5; -} - -.igny8-image-prompts-content { - background: var(--panel-1); - border-radius: 6px; - padding: 12px; - border: 1px solid var(--border); -} - -.igny8-image-prompts-content .prompt-item { - margin-bottom: 8px; - padding: 8px; - background: var(--panel-2); - border-radius: 4px; - border-left: 3px solid var(--blue); -} - -.igny8-image-prompts-content .prompt-item:last-child { - margin-bottom: 0; -} - -.igny8-image-prompts-content .prompt-item strong { - color: var(--blue); - display: block; - margin-bottom: 4px; - font-size: 12px; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.igny8-image-prompts-content .prompt-item:not(:last-child) { - margin-bottom: 12px; -} - -/* Ensure dashicons are properly styled */ -.igny8-image-icon.dashicons { - font-family: dashicons; - font-size: 16px; - color: var(--blue); - vertical-align: middle; -} - -.igny8-image-icon.dashicons:hover { - color: var(--blue-dark); -} - -/* Status with Badge Layout */ -.igny8-status-with-badge { - display: flex; - align-items: center; - gap: 8px; - flex-wrap: wrap; -} - -.igny8-status-text { - flex: 1; - min-width: 0; -} - -.igny8-status-with-badge .igny8-badge { - font-size: 10px; - padding: 2px 6px; - border-radius: 8px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -/* Static image size options styling */ -.igny8-size-options-static { - display: flex; - gap: 10px; - margin-bottom: 10px; -} - -.igny8-size-static { - flex: 1; - padding: 12px 8px; - border: 2px solid var(--border-light); - border-radius: 8px; - text-align: center; - min-height: 60px; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - opacity: 0.7; -} - -.igny8-size-static .size-label { - font-weight: 600; - font-size: 14px; - margin-bottom: 4px; -} - -.igny8-size-static .size-dimensions { - font-size: 12px; - opacity: 0.8; -} - -/* Different colors for each size option */ -/* DALL-E sizes */ -.igny8-size-square { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - border-color: #667eea; -} - -.igny8-size-portrait { - background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); - color: white; - border-color: #f093fb; -} - -.igny8-size-landscape { - background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); - color: white; - border-color: #4facfe; -} - -/* Runware sizes */ -.igny8-size-featured { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - border-color: #667eea; -} - -.igny8-size-desktop { - background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); - color: white; - border-color: #f093fb; -} - -.igny8-size-mobile { - background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); - color: white; - border-color: #4facfe; -} - -/* Image provider styling */ -.igny8-provider-info { - margin-bottom: 10px; -} - -.igny8-provider-badge { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 12px 16px; - background: var(--bg-light); - border: 1px solid var(--border-light); - border-radius: 8px; - font-weight: 500; - color: var(--text-primary); -} - -.igny8-provider-badge .dashicons { - color: var(--blue); - font-size: 16px; -} -.igny8-card.igny8-prompt-section { - display: flex; - flex-direction: row; -} - -.igny8-card.igny8-prompt-section .igny8-dashboard-section { - width: 100%; -} - -/* Image Size Checkbox and Quantity Input Styling */ -.igny8-size-checkbox-container { - display: flex; - flex-direction: column; - gap: 15px; - margin-top: 10px; -} - -.igny8-size-option { - display: flex; - align-items: center; - padding: 15px; - background: #f8fafc; - border: 1px solid #e2e8f0; - border-radius: 8px; - transition: all 0.2s ease; - justify-content: space-between; -} - -.igny8-size-option:hover { - background: #f1f5f9; - border-color: #cbd5e1; -} - -.igny8-checkbox-label { - display: flex; - align-items: center; - cursor: pointer; - font-weight: 500; - color: #374151; - margin-right: 15px; -} - -.igny8-checkbox-label input[type="checkbox"] { - margin-right: 8px; - width: 16px; - height: 16px; - accent-color: var(--blue); -} - -.igny8-checkbox-text { - font-size: 14px; - font-weight: 500; -} - -.igny8-quantity-input { - display: flex; - align-items: center; - gap: 5px; -} - -.igny8-quantity-input label { - font-size: 12px; - color: #6b7280; - font-weight: 500; -} - -.igny8-quantity-input input[type="number"] { - padding: 4px 8px; - border: 1px solid #d1d5db; - border-radius: 4px; - font-size: 12px; - text-align: center; -} - -.igny8-quantity-input input[type="number"]:disabled { - background-color: #f3f4f6; - color: #9ca3af; - cursor: not-allowed; -} - -.igny8-size-info { - - font-size: 12px; - color: #6b7280; - background: #e5e7eb; - padding: 4px 8px; - border-radius: 4px; - font-weight: 500; -} - -/* Featured Image Row Styling */ -.igny8-featured-image-row { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - border-color: #667eea; -} - -.igny8-featured-image-row .igny8-size-info { - background: rgba(255, 255, 255, 0.2); - color: white; - font-weight: 600; -} - -.igny8-featured-image-row .igny8-size-info:first-child { - font-size: 14px; - font-weight: 700; -} \ No newline at end of file diff --git a/igny8-wp-plugin-for-reference-olny/assets/css/image-injection.css b/igny8-wp-plugin-for-reference-olny/assets/css/image-injection.css deleted file mode 100644 index 1c1e3d3a..00000000 --- a/igny8-wp-plugin-for-reference-olny/assets/css/image-injection.css +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Igny8 Image Injection CSS - * - * Responsive image display styles for marker-based image injection - * - * @package Igny8 - * @version 1.0.0 - */ - -/* Desktop and larger screens (769px+) */ -@media (min-width: 769px) { - .igny8-article-image-desktop { - display: block !important; - } - .igny8-article-image-mobile { - display: none !important; - } -} - -/* Mobile and smaller screens (768px and below) */ -@media (max-width: 768px) { - .igny8-article-image-desktop { - display: none !important; - } - .igny8-article-image-mobile { - display: block !important; - } -} - -/* Image wrapper styling */ -.igny8-image-wrapper { - margin: 20px 0; - text-align: center; -} - -.igny8-image-wrapper img { - max-width: 100%; - height: auto; - border-radius: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); -} - -/* Loading state */ -.igny8-image-wrapper img[loading="lazy"] { - opacity: 0; - transition: opacity 0.3s ease; -} - -.igny8-image-wrapper img[loading="lazy"].loaded { - opacity: 1; -} diff --git a/igny8-wp-plugin-for-reference-olny/assets/js/core.js b/igny8-wp-plugin-for-reference-olny/assets/js/core.js deleted file mode 100644 index aab49bea..00000000 --- a/igny8-wp-plugin-for-reference-olny/assets/js/core.js +++ /dev/null @@ -1,4961 +0,0 @@ -/** - * IGNY8 UNIFIED JAVASCRIPT - PRODUCTION READY - * ============================================ - * - * This file contains ALL JavaScript functionality for the Igny8 plugin. - * Cleaned and optimized for production use. - * - * @package Igny8Compact - * @since 1.0.0 - */ - -/* ========================================= - Planner Settings - Sector Selection - ========================================= */ - -window.initializePlannerSettings = function() { - // Only initialize if we're on the planner home page - if (!window.IGNY8_PAGE || window.IGNY8_PAGE.module !== 'planner' || window.IGNY8_PAGE.submodule !== 'home') { - return; - } - - const parentSelect = document.getElementById('igny8-parent-sector'); - const lockButton = document.getElementById('igny8-lock-parent'); - const childSelection = document.getElementById('igny8-child-selection'); - const childCheckboxes = document.getElementById('igny8-child-checkboxes'); - const saveButton = document.getElementById('igny8-save-selection'); - const finalSelection = document.getElementById('igny8-final-selection'); - const selectedDisplay = document.getElementById('igny8-selected-sectors-display'); - const editButton = document.getElementById('igny8-edit-selection'); - - if (!parentSelect || !lockButton || !childSelection || !childCheckboxes || !saveButton || !finalSelection || !selectedDisplay || !editButton) { - return; - } - - let selectedParent = null; - let selectedChildren = []; - - // Load parent sectors - loadParentSectors(); - - // Load saved selection if exists - loadSavedSelection(); - - // Sample data creation button - const createSampleButton = document.getElementById('igny8-create-sample-sectors'); - if (createSampleButton) { - createSampleButton.addEventListener('click', createSampleSectors); - } - - // Parent sector change handler - parentSelect.addEventListener('change', function() { - if (this.value) { - lockButton.style.display = 'inline-block'; - } else { - lockButton.style.display = 'none'; - } - }); - - // Lock parent selection - lockButton.addEventListener('click', function() { - selectedParent = parentSelect.value; - if (selectedParent) { - parentSelect.disabled = true; - this.style.display = 'none'; - loadChildSectors(selectedParent); - childSelection.style.display = 'block'; - } - }); - - // Save selection - saveButton.addEventListener('click', function() { - const checkedBoxes = childCheckboxes.querySelectorAll('input[type="checkbox"]:checked'); - selectedChildren = Array.from(checkedBoxes).map(cb => { - const label = document.querySelector(`label[for="${cb.id}"]`); - return { - id: cb.value, - name: label ? label.textContent.trim() : cb.value - }; - }); - - console.log('Selected children:', selectedChildren); // Debug log - - if (selectedChildren.length === 0) { - alert('Please select at least one child sector.'); - return; - } - - saveSectorSelection(selectedParent, selectedChildren); - }); - - // Edit selection - editButton.addEventListener('click', function() { - resetSelection(); - }); - - function loadParentSectors() { - const formData = new FormData(); - formData.append('action', 'igny8_get_parent_sectors'); - formData.append('nonce', window.IGNY8_PAGE.nonce); - - fetch(window.IGNY8_PAGE.ajaxUrl, { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - parentSelect.innerHTML = ''; - data.data.forEach(sector => { - const option = document.createElement('option'); - option.value = sector.id; - option.textContent = sector.name; - parentSelect.appendChild(option); - }); - } - }) - .catch(error => console.error('Error loading parent sectors:', error)); - } - - function loadChildSectors(parentId) { - const formData = new FormData(); - formData.append('action', 'igny8_get_child_sectors'); - formData.append('nonce', window.IGNY8_PAGE.nonce); - formData.append('parent_id', parentId); - - fetch(window.IGNY8_PAGE.ajaxUrl, { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - childCheckboxes.innerHTML = ''; - data.data.forEach(sector => { - const checkboxContainer = document.createElement('div'); - checkboxContainer.className = 'igny8-checkbox-item'; - - const checkbox = document.createElement('input'); - checkbox.type = 'checkbox'; - checkbox.id = 'child-sector-' + sector.id; - checkbox.value = sector.id; - - const label = document.createElement('label'); - label.htmlFor = 'child-sector-' + sector.id; - label.textContent = sector.name; - - checkboxContainer.appendChild(checkbox); - checkboxContainer.appendChild(label); - childCheckboxes.appendChild(checkboxContainer); - }); - } - }) - .catch(error => console.error('Error loading child sectors:', error)); - } - - function saveSectorSelection(parentId, children) { - console.log('Saving sector selection:', { parentId, children }); // Debug log - - const formData = new FormData(); - formData.append('action', 'igny8_save_sector_selection'); - formData.append('nonce', window.IGNY8_PAGE.nonce); - formData.append('parent_id', parentId); - formData.append('children_count', children.length); - - // Add each child as separate form fields - children.forEach((child, index) => { - formData.append(`child_${index}_id`, child.id); - formData.append(`child_${index}_name`, child.name); - }); - - fetch(window.IGNY8_PAGE.ajaxUrl, { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - displayFinalSelection(data.data.parent, data.data.children); - } else { - let errorMessage = 'Unknown error'; - if (data.data) { - if (typeof data.data === 'string') { - errorMessage = data.data; - } else if (data.data.message) { - errorMessage = data.data.message; - } else { - errorMessage = JSON.stringify(data.data); - } - } - alert('Error saving selection: ' + errorMessage); - } - }) - .catch(error => { - console.error('Error saving selection:', error); - alert('Error saving selection. Please try again.'); - }); - } - - function loadSavedSelection() { - const formData = new FormData(); - formData.append('action', 'igny8_get_saved_sector_selection'); - formData.append('nonce', window.IGNY8_PAGE.nonce); - - fetch(window.IGNY8_PAGE.ajaxUrl, { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - if (data.success && data.data) { - displayFinalSelection(data.data.parent, data.data.children); - } - }) - .catch(error => console.error('Error loading saved selection:', error)); - } - - function displayFinalSelection(parent, children) { - selectedParent = parent.id; - selectedChildren = children; - - // Hide all selection steps - document.getElementById('igny8-parent-selection').style.display = 'none'; - childSelection.style.display = 'none'; - - // Show final selection - selectedDisplay.innerHTML = ` -
- Parent Sector: ${parent.name} --- Child Sectors: -- `; - finalSelection.style.display = 'block'; - } - - function resetSelection() { - selectedParent = null; - selectedChildren = []; - - // Show parent selection, hide others - document.getElementById('igny8-parent-selection').style.display = 'block'; - childSelection.style.display = 'none'; - finalSelection.style.display = 'none'; - - // Reset form - parentSelect.disabled = false; - parentSelect.value = ''; - lockButton.style.display = 'none'; - childCheckboxes.innerHTML = ''; - } - - function createSampleSectors() { - const button = document.getElementById('igny8-create-sample-sectors'); - if (!button) return; - - const originalText = button.textContent; - button.textContent = 'Creating...'; - button.disabled = true; - - const formData = new FormData(); - formData.append('action', 'igny8_create_sample_sectors'); - formData.append('nonce', window.IGNY8_PAGE.nonce); - - fetch(window.IGNY8_PAGE.ajaxUrl, { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - alert('Sample sectors created successfully! Refreshing the page...'); - location.reload(); - } else { - alert('Error: ' + (data.data || 'Unknown error')); - } - }) - .catch(error => { - console.error('Error creating sample sectors:', error); - alert('Error creating sample sectors. Please try again.'); - }) - .finally(() => { - button.textContent = originalText; - button.disabled = false; - }); - } -}; - -/* ========================================= - AI Integration Form - ========================================= */ - -window.initializeAIIntegrationForm = function() { - // Only initialize if we're on the planner home page - if (!window.IGNY8_PAGE || window.IGNY8_PAGE.module !== 'planner' || window.IGNY8_PAGE.submodule !== 'home') { - return; - } - - const form = document.getElementById('igny8-ai-integration-form'); - if (!form) { - return; - } - - // Handle mode change to show/hide AI features and prompts section - const modeRadios = form.querySelectorAll('input[name="igny8_planner_mode"]'); - const aiFeatures = document.getElementById('igny8-ai-features'); - const promptsSection = document.querySelector('.igny8-planner-prompts'); - - modeRadios.forEach(radio => { - radio.addEventListener('change', function() { - if (this.value === 'ai') { - aiFeatures.style.display = ''; - if (promptsSection) { - promptsSection.style.display = ''; - } - } else { - aiFeatures.style.display = 'none'; - if (promptsSection) { - promptsSection.style.display = 'none'; - } - } - }); - }); - - form.addEventListener('submit', function(e) { - e.preventDefault(); - - const formData = new FormData(form); - - // Determine the correct action based on the current page - const currentPage = window.location.href; - if (currentPage.includes('writer')) { - formData.append('action', 'igny8_save_writer_ai_settings'); - formData.append('nonce', window.IGNY8_PAGE.nonce); - } else { - formData.append('action', 'igny8_save_ai_integration_settings'); - formData.append('nonce', window.IGNY8_PAGE.nonce); - } - - // Show loading state - const submitBtn = form.querySelector('button[type="submit"]'); - const originalText = submitBtn.textContent; - submitBtn.textContent = 'Saving...'; - submitBtn.disabled = true; - - fetch(window.IGNY8_PAGE.ajaxUrl, { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - // Show success message - igny8GlobalNotification('AI Integration settings saved successfully!', 'success'); - // Reload page to update AI buttons - setTimeout(() => { - window.location.reload(); - }, 1500); - } else { - // Show error message - const errorMsg = data.data?.message || 'Error saving settings'; - igny8GlobalNotification(errorMsg, 'error'); - } - }) - .catch(error => { - console.error('Error saving AI integration settings:', error); - igny8GlobalNotification('Error saving settings. Please try again.', 'error'); - }) - .finally(() => { - // Reset button state - submitBtn.textContent = originalText; - submitBtn.disabled = false; - }); - }); -} - -// Initialize AI action buttons for tables -window.initializeAIActionButtons = function() { - // Only initialize if we're on a planner or writer submodule page - if (!window.IGNY8_PAGE || !window.IGNY8_PAGE.submodule) { - return; - } - - // Only initialize for planner and writer modules - if (window.IGNY8_PAGE.module !== 'planner' && window.IGNY8_PAGE.module !== 'writer') { - return; - } - - const tableId = window.IGNY8_PAGE.tableId; - if (!tableId) return; - - // AI Clustering button - const clusterBtn = document.getElementById(`${tableId}_ai_cluster_btn`); - if (clusterBtn) { - clusterBtn.addEventListener('click', function() { - const selectedIds = getSelectedRowIds(tableId); - if (selectedIds.length === 0) { - igny8GlobalNotification('Please select keywords to cluster', 'error'); - return; - } - - if (selectedIds.length > 20) { - igny8GlobalNotification('Maximum 20 keywords allowed for clustering', 'error'); - return; - } - - // Check if sector is selected before clustering - checkSectorSelectionBeforeClustering(selectedIds); - }); - } - - // AI Ideas button - const ideasBtn = document.getElementById(`${tableId}_ai_ideas_btn`); - if (ideasBtn) { - ideasBtn.addEventListener('click', function() { - const selectedIds = getSelectedRowIds(tableId); - if (selectedIds.length === 0) { - igny8GlobalNotification('Please select clusters to generate ideas', 'error'); - return; - } - - if (selectedIds.length > 5) { - igny8GlobalNotification('Maximum 5 clusters allowed for idea generation', 'error'); - return; - } - - processAIIdeas(selectedIds); - }); - } - - // AI Generate Images button - const generateImagesBtn = document.getElementById(`${tableId}_generate_images_btn`); - if (generateImagesBtn) { - console.log('Igny8: Generate Images button found for table:', tableId); - generateImagesBtn.addEventListener('click', function(e) { - console.log('Igny8: Generate Images button clicked'); - e.preventDefault(); - e.stopPropagation(); - - const selectedIds = getSelectedRowIds(tableId); - console.log('Igny8: Selected IDs:', selectedIds); - console.log('Igny8: Post IDs being sent for image generation:', selectedIds); - - if (selectedIds.length === 0) { - igny8GlobalNotification('Please select posts to generate images', 'error'); - return; - } - - if (selectedIds.length > 10) { - igny8GlobalNotification('Maximum 10 posts allowed for image generation', 'error'); - return; - } - - // Only for drafts table - console.log('Igny8: Calling processAIImageGenerationDrafts with', selectedIds.length, 'posts'); - processAIImageGenerationDrafts(selectedIds); - }); - } else { - console.log('Igny8: Generate Images button NOT found for table:', tableId); - } - - // Queue to Writer button is now handled by the bulk selection system - // No need for separate event listener as it's integrated into updateStates() -} - -// Get selected row IDs from table -function getSelectedRowIds(tableId) { - const checkboxes = document.querySelectorAll(`#table-${tableId}-body input[type="checkbox"]:checked`); - const ids = []; - console.log(`Igny8: Found ${checkboxes.length} checked checkboxes for table ${tableId}`); - - checkboxes.forEach((checkbox, index) => { - const row = checkbox.closest('tr'); - const rowId = row.getAttribute('data-id'); - console.log(`Igny8: Checkbox ${index + 1} - Row ID: ${rowId}`); - - if (rowId && !isNaN(rowId)) { - ids.push(parseInt(rowId)); - console.log(`Igny8: Added Post ID ${rowId} to selection`); - } - }); - - console.log(`Igny8: Final selected Post IDs: ${ids.join(', ')}`); - return ids; -} - -// Process AI Clustering -function processAIClustering(keywordIds) { - const formData = new FormData(); - formData.append('action', 'igny8_ai_cluster_keywords'); - formData.append('nonce', window.IGNY8_PAGE.nonce); - formData.append('keyword_ids', JSON.stringify(keywordIds)); - - // Show progress modal - showProgressModal('Auto Clustering', keywordIds.length); - - // Log client-side event start - console.log('Igny8 AI: Starting clustering process for', keywordIds.length, 'keywords'); - - fetch(window.IGNY8_PAGE.ajaxUrl, { - method: 'POST', - body: formData - }) - .then(response => { - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - return response.json(); - }) - .then(data => { - if (data.success) { - console.log('Igny8 AI: Clustering completed successfully', data.data); - - // Show success modal - showSuccessModal('Auto Clustering Complete', data.data.clusters_created || keywordIds.length, data.data.message); - - // Reload table data - try { - if (window.loadTableData && window.IGNY8_PAGE?.tableId) { - window.loadTableData(window.IGNY8_PAGE.tableId); - } - } catch (error) { - console.error('Error reloading table data:', error); - // Don't show error to user since clustering was successful - } - } else { - console.error('Igny8 AI: Clustering failed', data.data); - // Close progress modal and show error - if (currentProgressModal) { - currentProgressModal.remove(); - currentProgressModal = null; - } - igny8GlobalNotification(data.data?.message || 'AI clustering failed', 'error'); - } - }) - .catch(error => { - console.error('Error processing AI clustering:', error); - // Close progress modal and show error - if (currentProgressModal) { - currentProgressModal.remove(); - currentProgressModal = null; - } - igny8GlobalNotification('Error processing AI clustering', 'error'); - }); -} - -// Process AI Ideas Generation -function processAIIdeas(clusterIds) { - const formData = new FormData(); - formData.append('action', 'igny8_ai_generate_ideas'); - formData.append('nonce', window.IGNY8_PAGE.nonce); - formData.append('cluster_ids', JSON.stringify(clusterIds)); - - // Show progress modal - showProgressModal('Generate Ideas', clusterIds.length); - - // Log client-side event start - console.log('Igny8 AI: Starting ideas generation process for', clusterIds.length, 'clusters'); - - fetch(window.IGNY8_PAGE.ajaxUrl, { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - console.log('Igny8 AI: Ideas generation completed successfully', data.data); - - // Show success modal - showSuccessModal('Ideas Generated', data.data.ideas_created || clusterIds.length, data.data.message); - - // Reload table data - try { - if (window.loadTableData && window.IGNY8_PAGE?.tableId) { - window.loadTableData(window.IGNY8_PAGE.tableId); - } - } catch (error) { - console.error('Error reloading table data:', error); - // Don't show error to user since ideas generation was successful - } - } else { - console.error('Igny8 AI: Ideas generation failed', data.data); - // Close progress modal and show error - if (currentProgressModal) { - currentProgressModal.remove(); - currentProgressModal = null; - } - igny8GlobalNotification(data.data?.message || 'AI ideas generation failed', 'error'); - } - }) - .catch(error => { - console.error('Error processing AI ideas:', error); - // Close progress modal and show error - if (currentProgressModal) { - currentProgressModal.remove(); - currentProgressModal = null; - } - igny8GlobalNotification('Error processing AI ideas', 'error'); - }); -} - -// Process AI Image Generation for Drafts (Sequential Image Processing) -function processAIImageGenerationDrafts(postIds) { - console.log('Igny8: processAIImageGenerationDrafts called with postIds:', postIds); - - // Get image generation settings from saved options (passed via wp_localize_script) - const desktopEnabled = window.IGNY8_PAGE?.imageSettings?.desktop_enabled || false; - const mobileEnabled = window.IGNY8_PAGE?.imageSettings?.mobile_enabled || false; - const maxInArticleImages = window.IGNY8_PAGE?.imageSettings?.max_in_article_images || 1; - - // Calculate total images per post - let imagesPerPost = 1; // Featured image (always 1) - let imageTypes = ['featured']; - - if (desktopEnabled) { - imagesPerPost += maxInArticleImages; - for (let i = 1; i <= maxInArticleImages; i++) { - imageTypes.push('desktop'); - } - } - - if (mobileEnabled) { - imagesPerPost += maxInArticleImages; - for (let i = 1; i <= maxInArticleImages; i++) { - imageTypes.push('mobile'); - } - } - - const totalImages = postIds.length * imagesPerPost; - - console.log('Igny8: Image generation settings:', { - desktopEnabled, - mobileEnabled, - maxInArticleImages, - imagesPerPost, - totalImages, - imageTypes - }); - - // Show progress modal with calculated total - showProgressModal('Generate Images', totalImages, 'images'); - updateProgressModal(0, totalImages, 'processing', 'Preparing to generate images...'); - - let totalImagesGenerated = 0; - let totalImagesFailed = 0; - const allResults = { - generated: [], - failed: [] - }; - - // Process each post sequentially - function processNextPost(postIndex) { - if (postIndex >= postIds.length) { - // All posts processed - show final results - console.log('Igny8: All posts processed', allResults); - - if (allResults.generated.length > 0) { - updateProgressModal(totalImages, totalImages, 'completed'); - - setTimeout(() => { - showSuccessModal( - 'Images Generated', - allResults.generated.length, - `Successfully generated ${allResults.generated.length} images for ${postIds.length} posts` - ); - - // Reload table - if (window.loadTableData && window.IGNY8_PAGE?.tableId) { - window.loadTableData(window.IGNY8_PAGE.tableId); - } - }, 500); - } else { - if (currentProgressModal) { - currentProgressModal.remove(); - currentProgressModal = null; - } - igny8GlobalNotification('No images were generated', 'error'); - } - return; - } - - const postId = postIds[postIndex]; - - // Update progress for current post - const currentProgress = postIndex * imagesPerPost; - updateProgressModal(currentProgress, totalImages, 'processing', `Post ${postIndex + 1} of ${postIds.length}`); - - // Generate all images for this post (one at a time) - generateAllImagesForPost(postId, postIndex + 1, function(postResults) { - // Post complete - add results - allResults.generated.push(...postResults.generated); - allResults.failed.push(...postResults.failed); - totalImagesGenerated += postResults.generated.length; - totalImagesFailed += postResults.failed.length; - - console.log(`β Post ${postId} complete: ${postResults.generated.length} images generated, ${postResults.failed.length} failed`); - - // Move to next post - setTimeout(() => processNextPost(postIndex + 1), 100); - }); - } - - // Generate all images for a single post (featured + in-article) - function generateAllImagesForPost(postId, postNumber, callback) { - // Use the existing action but with single post - const formData = new FormData(); - formData.append('action', 'igny8_ai_generate_images_drafts'); - formData.append('nonce', window.IGNY8_PAGE.nonce); - formData.append('post_ids', JSON.stringify([postId])); - formData.append('desktop_enabled', desktopEnabled ? '1' : '0'); - formData.append('mobile_enabled', mobileEnabled ? '1' : '0'); - formData.append('max_in_article_images', maxInArticleImages); - - console.log('Igny8: Sending AJAX request to:', window.IGNY8_PAGE.ajaxUrl); - console.log('Igny8: AJAX request data:', { - action: 'igny8_ai_generate_images_drafts', - post_ids: JSON.stringify([postId]), - desktop_enabled: desktopEnabled ? '1' : '0', - mobile_enabled: mobileEnabled ? '1' : '0', - max_in_article_images: maxInArticleImages, - nonce: window.IGNY8_PAGE.nonce - }); - - fetch(window.IGNY8_PAGE.ajaxUrl, { - method: 'POST', - body: formData - }) - .then(response => { - console.log('Igny8: AJAX response received:', response.status, response.statusText); - return response.json(); - }) - .then(data => { - if (data.success) { - callback({ - generated: data.data.generated_images || [], - failed: data.data.failed_images || [] - }); - } else { - callback({ - generated: [], - failed: [{ - post_id: postId, - error: data.data?.message || 'Unknown error' - }] - }); - } - }) - .catch(error => { - console.error(`β Post ${postId} - Exception:`, error); - callback({ - generated: [], - failed: [{ - post_id: postId, - error: error.message - }] - }); - }); - } - - // Start processing first post - processNextPost(0); -} - -// Process AI Content Generation -function processAIContentGeneration(taskId) { - const formData = new FormData(); - formData.append('action', 'igny8_ai_generate_content'); - formData.append('nonce', window.IGNY8_PAGE.nonce); - formData.append('task_id', taskId); - - // Show progress modal - showProgressModal('Generate Draft', 1); - - // Log client-side event start - console.log('Igny8 AI: Starting content generation for task', taskId); - - fetch(window.IGNY8_PAGE.ajaxUrl, { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - console.log('Igny8 AI: Content generation completed successfully', data.data); - - // Show success modal - showSuccessModal('Draft Generated', 1, data.data.message); - - // Reload table data - try { - if (window.loadTableData && window.IGNY8_PAGE?.tableId) { - window.loadTableData(window.IGNY8_PAGE.tableId); - } - } catch (error) { - console.error('Error reloading table data:', error); - // Don't show error to user since content generation was successful - } - } else { - console.error('Igny8 AI: Content generation failed', data.data); - // Close progress modal and show error - if (currentProgressModal) { - currentProgressModal.remove(); - currentProgressModal = null; - } - igny8GlobalNotification(data.data?.message || 'AI content generation failed', 'error'); - } - }) - .catch(error => { - console.error('Error processing AI content generation:', error); - // Close progress modal and show error - if (currentProgressModal) { - currentProgressModal.remove(); - currentProgressModal = null; - } - igny8GlobalNotification('Error processing AI content generation', 'error'); - }); -} - - -// Unified Global Notification System -function igny8GlobalNotification(message, type = 'info') { - // Debug logging - console.log('igny8GlobalNotification called:', message, type, new Date().toLocaleTimeString()); - - // Get or create global notification container - let container = document.getElementById('igny8-global-notification'); - if (!container) { - container = document.createElement('div'); - container.id = 'igny8-global-notification'; - document.body.appendChild(container); - } - - // Clear any existing timeouts to prevent conflicts - if (container._hideTimeout) { - clearTimeout(container._hideTimeout); - console.log('Cleared existing hide timeout'); - } - if (container._removeTimeout) { - clearTimeout(container._removeTimeout); - console.log('Cleared existing remove timeout'); - } - - // Clear any existing classes and content - container.className = ''; - container.textContent = message; - container.style.display = 'block'; - - // Add type class and show - container.classList.add(type); - container.classList.add('show'); - - console.log('Notification shown, will hide in 4 seconds'); - - // Auto-hide after 4 seconds (4000ms) - container._hideTimeout = setTimeout(() => { - console.log('Starting hide animation'); - container.classList.remove('show'); - container.classList.add('hide'); - - // Remove from DOM after animation completes - container._removeTimeout = setTimeout(() => { - console.log('Removing notification from DOM'); - container.classList.remove('hide'); - container.style.display = 'none'; - }, 300); - }, 4000); -} - -// Legacy function for backward compatibility - -/* ========================================= - Debug Toggle Functionality - ========================================= */ - -window.initializeDebugToggle = function() { - const saveButton = document.getElementById('save-debug-setting'); - const enabledRadio = document.getElementById('debug-enabled'); - const disabledRadio = document.getElementById('debug-disabled'); - - if (!saveButton || !enabledRadio || !disabledRadio) return; - - saveButton.addEventListener('click', function() { - const isEnabled = enabledRadio.checked; - - console.log('DEBUG: Save button clicked, isEnabled:', isEnabled); - - // Show loading state - const originalText = this.innerHTML; - this.innerHTML = ' Saving...'; - this.disabled = true; - - // Send AJAX request to update the setting - const formData = new FormData(); - formData.append('action', 'igny8_toggle_debug_monitoring'); - formData.append('nonce', igny8_ajax.nonce); - formData.append('is_enabled', isEnabled ? '1' : '0'); - - console.log('DEBUG: Sending AJAX with is_enabled:', isEnabled ? '1' : '0'); - - fetch(igny8_ajax.ajax_url, { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - // Show success notification - if (typeof igny8ShowNotification === 'function') { - igny8ShowNotification( - isEnabled ? 'Debug monitoring enabled' : 'Debug monitoring disabled', - 'success' - ); - } - - // Update module debug visibility on submodule pages - updateModuleDebugVisibility(isEnabled); - - // Update button to show success - this.innerHTML = ' Saved'; - setTimeout(() => { - this.innerHTML = originalText; - }, 2000); - } else { - // Show error notification - if (typeof igny8ShowNotification === 'function') { - igny8ShowNotification( - data.data?.message || 'Failed to update debug monitoring setting', - 'error' - ); - } - } - }) - .catch(error => { - // Show error notification - if (typeof igny8ShowNotification === 'function') { - igny8ShowNotification('Network error occurred', 'error'); - } - }) - .finally(() => { - // Restore button state - this.disabled = false; - if (this.innerHTML.includes('Saving...')) { - this.innerHTML = originalText; - } - }); - }); -}; - -window.updateModuleDebugVisibility = function(isEnabled) { - // Only update on submodule pages (pages with module debug container) - const moduleDebugContainer = document.getElementById('igny8-module-debug-container'); - if (!moduleDebugContainer) return; - - if (isEnabled) { - moduleDebugContainer.style.display = 'block'; - } else { - moduleDebugContainer.style.display = 'none'; - } -}; - -// Initialize module debug visibility on page load -window.initializeModuleDebugVisibility = function() { - const enabledRadio = document.getElementById('debug-enabled'); - const moduleDebugContainer = document.getElementById('igny8-module-debug-container'); - - if (enabledRadio && moduleDebugContainer) { - const isEnabled = enabledRadio.checked; - updateModuleDebugVisibility(isEnabled); - } -}; - -/* ========================================= - Igny8 Core Initialization & Dropdowns - ========================================= */ - -jQuery(document).ready(function ($) { - initializeDropdowns($); - initializeDebugSystem(); - initializeDebugToggle(); - initializeModuleDebugVisibility(); - // Removed initializeDelegatedEvents() - handled in DOMContentLoaded -}); - -/* ========================================= - Dropdown Functionality - ========================================= */ - -window.initializeDropdowns = function ($) { - // Toggle dropdown - $(document).off('click.dropdown').on('click.dropdown', '.select-btn', function (e) { - e.preventDefault(); - e.stopPropagation(); - - const $select = $(this).closest('.select'); - if (!$select.find('.select-list').length) return; - - $('.select').not($select).removeClass('open'); - $select.toggleClass('open'); - }); - - // Select item - $(document).off('click.dropdown-item').on('click.dropdown-item', '.select-item', function (e) { - e.stopPropagation(); - - const $item = $(this); - const $select = $item.closest('.select'); - const $btn = $select.find('.select-btn'); - const value = $item.data('value'); - const text = $item.text(); - - $btn.attr('data-value', value).data('value', value) - .find('.select-text').text(text); - - $select.removeClass('open'); - $select[0].dispatchEvent(new CustomEvent('change', { detail: { value, text } })); - }); - - // Close on outside click - $(document).off('click.dropdown-outside').on('click.dropdown-outside', function (e) { - if (!$(e.target).closest('.select').length) $('.select').removeClass('open'); - }); -}; - -/* ========================================= - Igny8 Debug System β Toggle & Monitoring - ========================================= */ - -function initializeDebugSystem() { - const toggle = document.getElementById('igny8-debug-toggle'); - const statusText = document.getElementById('igny8-module-debug-status'); - const container = document.getElementById('igny8-module-debug-container'); - const widgets = document.getElementById('igny8-module-debug-widgets'); - - // ---- Main Debug Toggle ---- - if (toggle) { - setDebugUI(toggle.checked); - toggle.addEventListener('change', () => { - const enabled = toggle.checked; - setDebugUI(enabled); - saveDebugState(enabled); - }); - } - - // ---- Module Debug Widget Toggle ---- - window.igny8ToggleModuleDebug = () => { - if (!widgets) return; - const hidden = widgets.classList.contains('hidden') || widgets.style.display === 'none'; - widgets.style.display = hidden ? 'block' : 'none'; - widgets.classList.toggle('hidden', !hidden); - }; - - // ---- Auto-show Debug Panel on DOM Ready ---- - document.addEventListener('DOMContentLoaded', () => { - if (container && widgets) { - container.style.display = 'block'; - widgets.style.display = 'block'; - widgets.classList.remove('hidden'); - } - }); - - // ---- Helper: UI Update ---- - function setDebugUI(enabled) { - if (container) container.style.display = enabled ? 'block' : 'none'; - if (statusText) statusText.textContent = enabled ? 'Enabled' : 'Disabled'; - } - - // ---- Helper: Save State via AJAX ---- - function saveDebugState(enabled) { - if (!igny8_ajax?.ajax_url) return; - fetch(igny8_ajax.ajax_url, { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: new URLSearchParams({ - action: 'igny8_toggle_debug', - nonce: igny8_ajax.nonce, - enabled: enabled ? '1' : '0' - }) - }) - .then(r => r.json()) - .then(d => { if (!d.success) {} }) - .catch(err => {}); - } -} - - - -/* ========================================= - Igny8 Utility β Number, Currency, Date - ========================================= */ - -window.igny8FormatNumber = (num, decimals = 0) => - new Intl.NumberFormat(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals }).format(num); - -window.igny8FormatCurrency = (amount, currency = 'USD') => - new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount); - -window.igny8FormatDate = (date, options = {}) => { - const base = { year: 'numeric', month: 'short', day: 'numeric' }; - return new Intl.DateTimeFormat('en-US', { ...base, ...options }).format(new Date(date)); -}; - - -// ---- Update Debug Grid Cell State ---- -window.igny8DebugState = function (stage, ok, msg = '') { - const cell = document.querySelector(`[data-debug-stage="${stage}"]`); - if (!cell) return; - - cell.classList.toggle('ok', !!ok); - cell.classList.toggle('fail', !ok); - cell.classList.toggle('neutral', !ok && !ok); - if (msg) cell.title = msg; -}; - -// ---- Centralized Event Logger ---- -window.igny8LogEvent = function (eventType, status, details = '') { - const timestamp = new Date().toLocaleTimeString(); - const logEntry = `[${timestamp}] ${eventType}: ${status}${details ? ' - ' + details : ''}`; - - const colorMap = { - SUCCESS: '#28a745', FOUND: '#28a745', EXISTS: '#28a745', WORKING: '#28a745', - MISSING: '#dc3545', BROKEN: '#dc3545', ERROR: '#dc3545', NOT_: '#dc3545', - WARNING: '#ffc107', PENDING: '#ffc107' - }; - const color = colorMap[status] || '#6c757d'; - const method = (status === 'MISSING' || status === 'BROKEN' || status === 'ERROR' || status === 'NOT_') ? 'error' : - (status === 'WARNING' || status === 'PENDING') ? 'warn' : 'log'; - console[method](`%c${logEntry}`, `color: ${color}; font-weight: bold;`); - - window.igny8EventLog = window.igny8EventLog || []; - window.igny8EventLog.push(logEntry); - if (window.igny8EventLog.length > 100) window.igny8EventLog = window.igny8EventLog.slice(-100); -}; - - -/* ========================================= - Igny8 Filters β Inputs, Dropdowns, Ranges - ========================================= */ - -// ---- Initialize All Filter Listeners ---- -function initializeFilters() { - const filters = document.querySelectorAll('.igny8-filters [data-filter]'); - let boundCount = 0; - - filters.forEach(el => { - if (el.tagName === 'INPUT' && el.type === 'text') { - // Debounced search input - let timer; - el.addEventListener('input', () => { - clearTimeout(timer); - timer = setTimeout(() => { - const tableId = el.closest('.igny8-filters')?.dataset.table; - if (tableId) handleFilterChange(tableId); - }, 400); - }); - boundCount++; - - } else if (el.classList.contains('select')) { - // Dropdown select - el.querySelectorAll('.select-item').forEach(item => { - item.addEventListener('click', () => { - const val = item.dataset.value || ''; - const txt = item.textContent.trim(); - const btn = el.querySelector('.select-btn'); - const label = el.querySelector('.select-text'); - if (btn && label) { - btn.dataset.value = val; - label.textContent = txt; - } - el.classList.remove('open'); - }); - }); - boundCount++; - } - }); - - // ---- Range OK & Clear ---- - document.querySelectorAll('[id$="_ok"]').forEach(btn => { - btn.addEventListener('click', () => { - const range = btn.closest('.select'); - if (!range) return; - - const min = range.querySelector('input[id$="_min"]')?.value.trim(); - const max = range.querySelector('input[id$="_max"]')?.value.trim(); - const label = range.querySelector('.select-text'); - - if (label) { - label.textContent = min && max ? `${min} - ${max}` : - min ? `${min} - β` : - max ? `0 - ${max}` : 'Volume'; - } - range.classList.remove('open'); - }); - boundCount++; - }); - - document.querySelectorAll('[id$="_clear"]').forEach(btn => { - btn.addEventListener('click', () => { - const range = btn.closest('.select'); - if (!range) return; - - range.querySelectorAll('input[type="number"]').forEach(i => i.value = ''); - const label = range.querySelector('.select-text'); - if (label) label.textContent = 'Volume'; - range.classList.remove('open'); - }); - boundCount++; - }); - - // ---- Apply & Reset ---- - document.querySelectorAll('[id$="_filter_apply_btn"]').forEach(btn => { - btn.addEventListener('click', () => applyFilters(btn.id.replace('_filter_apply_btn', ''))); - boundCount++; - }); - - document.querySelectorAll('[id$="_filter_reset_btn"]').forEach(btn => { - btn.addEventListener('click', () => resetFilters(btn.id.replace('_filter_reset_btn', ''))); - boundCount++; - }); -} - -// ---- Collect Filter Values ---- -function collectFilters(tableId) { - const container = document.querySelector(`[data-table="${tableId}"].igny8-filters`); - if (!container) return {}; - - const payload = {}; - - container.querySelectorAll('input[data-filter]').forEach(i => { - const val = i.value.trim(); - if (val) payload[i.dataset.filter] = val; - }); - - container.querySelectorAll('.select[data-filter]').forEach(s => { - const val = s.querySelector('.select-btn')?.dataset.value; - if (val) payload[s.dataset.filter] = val; - }); - - container.querySelectorAll('input[type="number"]').forEach(i => { - const val = i.value.trim(); - if (val) { - if (i.id.includes('search_volume_min')) payload['search_volume-min'] = val; - if (i.id.includes('search_volume_max')) payload['search_volume-max'] = val; - } - }); - - return payload; -} - -// ---- Trigger Filter Change ---- -function handleFilterChange(tableId) { - const payload = collectFilters(tableId); - loadTableData(tableId, payload, 1); -} - -// ---- Apply Filters ---- -function applyFilters(tableId) { - const payload = collectFilters(tableId); - const perPage = getSessionPerPage(tableId) || getDefaultPerPage(); - loadTableData(tableId, payload, 1, perPage); -} - -// ---- Reset Filters ---- -function resetFilters(tableId) { - const container = document.querySelector(`[data-table="${tableId}"].igny8-filters`); - if (!container) return; - - container.querySelectorAll('input[type="text"], input[type="number"]').forEach(i => i.value = ''); - - container.querySelectorAll('.select').forEach(select => { - const btn = select.querySelector('.select-btn'); - const label = select.querySelector('.select-text'); - if (btn && label) { - btn.dataset.value = ''; - const field = select.dataset.filter; - label.textContent = field ? getFilterLabel(tableId, field) : 'All'; - } - select.classList.remove('open'); - }); - - const perPage = getSessionPerPage(tableId) || getDefaultPerPage(); - loadTableData(tableId, {}, 1, perPage); -} - -// ---- Retrieve Original Filter Label ---- -function getFilterLabel(tableId, field) { - if (window.IGNY8_PAGE?.filtersConfig?.[tableId]?.[field]) { - return IGNY8_PAGE.filtersConfig[tableId][field].label || field; - } - - const el = document.querySelector(`[data-table="${tableId}"].igny8-filters [data-filter="${field}"]`); - const allText = el?.querySelector('.select-item[data-value=""]')?.textContent.trim(); - const match = allText?.match(/^All\s+(.+)$/); - return match ? match[1] : field.charAt(0).toUpperCase() + field.slice(1); -} - -/* ========================================= - Igny8 Table Actions β Bulk & Row Controls - ========================================= */ - -function initializeTableActions(tableId) { - const exportBtn = document.getElementById(`${tableId}_export_btn`); - const deleteBtn = document.getElementById(`${tableId}_delete_btn`); - const publishBtn = document.getElementById(`${tableId}_publish_btn`); - const importBtn = document.getElementById(`${tableId}_import_btn`); - const addBtn = document.getElementById(`${tableId}_add_btn`); - const countSpan = document.getElementById(`${tableId}_count`); - - // ---- Button Handlers ---- - exportBtn?.addEventListener('click', () => { - const selectedIds = getSelectedRows(tableId); - if (selectedIds.length) { - igny8ShowExportModal(tableId, selectedIds); - } else { - igny8ShowNotification('Please select records to export', 'warning'); - } - }); - - deleteBtn?.addEventListener('click', () => { - const rows = getSelectedRows(tableId); - if (rows.length) deleteSelectedData(tableId, rows); - else igny8ShowNotification('Please select records to delete', 'warning'); - }); - - // Publish button handler (for drafts) - publishBtn?.addEventListener('click', () => { - const rows = getSelectedRows(tableId); - if (rows.length) { - handleBulkPublishDrafts(rows, tableId); - } else { - igny8ShowNotification('Please select drafts to publish', 'warning'); - } - }); - - importBtn?.addEventListener('click', () => igny8ShowImportModal(tableId)); - addBtn?.addEventListener('click', () => openAddNewModal?.(tableId)); - - // ---- Selection Count & Button State ---- - const updateStates = () => { - const count = getSelectedRows(tableId).length; - if (countSpan) { - countSpan.textContent = `${count} selected`; - countSpan.classList.toggle('igny8-count-hidden', count === 0); - } - if (exportBtn) exportBtn.disabled = !count; - if (deleteBtn) deleteBtn.disabled = !count; - if (publishBtn) publishBtn.disabled = !count; - - // Update AI action buttons - const clusterBtn = document.getElementById(`${tableId}_ai_cluster_btn`); - const ideasBtn = document.getElementById(`${tableId}_ai_ideas_btn`); - const mappingBtn = document.getElementById(`${tableId}_ai_mapping_btn`); - const queueWriterBtn = document.getElementById(`${tableId}_queue_writer_btn`); - const generateContentBtn = document.getElementById(`${tableId}_generate_content_btn`); - const generateImagesBtn = document.getElementById(`${tableId}_generate_images_btn`); - - if (clusterBtn) clusterBtn.disabled = !count; - if (ideasBtn) ideasBtn.disabled = !count; - if (mappingBtn) mappingBtn.disabled = !count; - if (queueWriterBtn) queueWriterBtn.disabled = !count; - if (generateContentBtn) generateContentBtn.disabled = !count; - if (generateImagesBtn) generateImagesBtn.disabled = !count; - }; - - document.addEventListener('rowSelectionChanged', e => { - if (e.detail.tableId === tableId) updateStates(); - }); - - // Initial state update - updateStates(); -} - -// ---- Helpers ---- -function getSelectedRows(tableId) { - return [...document.querySelectorAll(`#table-${tableId}-body input[type="checkbox"]:checked`)] - .map(cb => { - const row = cb.closest('tr'); - return row ? row.getAttribute('data-id') : null; - }) - .filter(id => id !== null); -} - -function exportSelectedData(tableId, rows) { - loadTableData(tableId, {}, 1); -} - -function deleteSelectedData(tableId, rows) { - if (!rows.length) return igny8ShowNotification('No records selected for deletion', 'warning'); - - // Get the actual record data for the selected rows - const data = rows.map(rowId => { - const row = document.querySelector(`#table-${tableId}-body tr[data-id="${rowId}"]`); - if (!row) return null; - - // Extract record data from the row - const cells = row.querySelectorAll('td'); - const record = { - id: rowId, - table_id: tableId // CRITICAL FIX: Include table_id in record data - }; - - // Get the first text cell as the title/name - if (cells.length > 1) { - const titleCell = cells[1]; // Skip checkbox column - record.name = titleCell.textContent.trim(); - record.keyword = titleCell.textContent.trim(); // For keywords - record.idea_title = titleCell.textContent.trim(); // For ideas - } - - return record; - }).filter(record => record !== null); - - igny8ShowDeleteDialog('bulk', data); -} - -function deleteRow(rowId) { - const row = document.querySelector(`tr[data-id="${rowId}"]`); - const tableId = row?.closest('table')?.id.replace('_table', ''); - if (!tableId) return; - - const rowData = getUniversalRowData(tableId, rowId); - if (rowData) igny8ShowDeleteDialog('single', [rowData]); -} - -/* ========================================= - Igny8 Add/Edit Row Handlers - ========================================= */ - -function openAddNewRow(tableId) { - document.querySelector('tr.igny8-inline-form-row')?.remove(); - - const formData = new FormData(); - formData.append('action', 'igny8_render_form_row'); - formData.append('nonce', igny8_ajax.nonce); - formData.append('table_id', tableId); - formData.append('mode', 'add'); - formData.append('record_data', '{}'); - - fetch(ajaxurl, { method: 'POST', body: formData }) - .then(r => r.json()) - .then(result => { - if (!result.success) throw new Error(result.data); - const tableBody = document.querySelector(`#table-${tableId}-body`); - if (tableBody) tableBody.insertAdjacentHTML('afterbegin', result.data); - }) - .catch(err => igny8ShowNotification(`Add form error: ${err.message}`, 'error')); -} - -function editRow(rowId, tableId) { - document.querySelector('tr.igny8-inline-form-row')?.remove(); - - const formData = new FormData(); - formData.append('action', 'igny8_get_row_data'); - formData.append('nonce', igny8_ajax.nonce); - formData.append('table_id', tableId); - formData.append('row_id', rowId); - - fetch(ajaxurl, { method: 'POST', body: formData }) - .then(r => r.json()) - .then(result => { - if (!result.success) throw new Error(result.data); - - const editFormData = new FormData(); - editFormData.append('action', 'igny8_render_form_row'); - editFormData.append('nonce', igny8_ajax.nonce); - editFormData.append('table_id', tableId); - editFormData.append('mode', 'edit'); - editFormData.append('record_data', JSON.stringify(result.data)); - - return fetch(ajaxurl, { method: 'POST', body: editFormData }); - }) - .then(r => r.json()) - .then(formResult => { - if (!formResult.success) throw new Error(formResult.data); - const row = document.querySelector(`tr[data-id="${rowId}"]`); - if (row) row.outerHTML = formResult.data; - else igny8ShowNotification('Target row not found for editing', 'error'); - }) - .catch(err => igny8ShowNotification(`Edit form error: ${err.message}`, 'error')); -} - -/* ========================================= - Igny8 Delegated Event Handlers - ========================================= */ - -function initializeDelegatedEvents() { - // Single delegated click handler to prevent conflicts - document.addEventListener('click', e => { - // Prevent multiple event handlers from interfering - if (e.defaultPrevented) return; - - const addBtn = e.target.closest('[data-action="addRow"]'); - const editBtn = e.target.closest('[data-action="editRow"]'); - const queueBtn = e.target.closest('[data-action="queueToWriter"]'); - const bulkQueueBtn = e.target.closest('[data-action="bulkQueueToWriter"]'); - const deleteBtn = e.target.closest('[data-action="deleteRow"]'); - const personalizeBtn = e.target.closest('#igny8-launch'); - const personalizeForm = e.target.closest('#igny8-form'); - const saveBtn = e.target.closest('.igny8-save-btn'); - const cronBtn = e.target.closest('button[data-action]'); - const descriptionToggle = e.target.closest('.igny8-description-toggle'); - const imagePromptsToggle = e.target.closest('.igny8-image-prompts-toggle'); - - if (addBtn) { - e.preventDefault(); - e.stopPropagation(); - openAddNewRow(addBtn.dataset.tableId); - return; - } - - if (editBtn) { - e.preventDefault(); - e.stopPropagation(); - editRow(editBtn.dataset.rowId, editBtn.dataset.tableId); - return; - } - - if (queueBtn) { - e.preventDefault(); - e.stopPropagation(); - const ideaId = queueBtn.dataset.ideaId; - if (ideaId) { - igny8QueueIdeaToWriter(ideaId); - } - return; - } - - if (bulkQueueBtn) { - e.preventDefault(); - e.stopPropagation(); - const tableId = bulkQueueBtn.closest('[data-table]')?.dataset?.table; - if (tableId) { - const selectedRows = getSelectedRows(tableId); - if (selectedRows.length > 0) { - igny8BulkQueueIdeasToWriter(selectedRows); - } else { - igny8ShowNotification('Please select ideas to queue to Writer', 'warning'); - } - } - return; - } - - if (deleteBtn) { - e.preventDefault(); - e.stopPropagation(); - const tableId = deleteBtn.closest('[data-table]').dataset.table; - const rowId = deleteBtn.dataset.rowId; - const rowData = getUniversalRowData(tableId, rowId); - if (rowData) igny8ShowDeleteDialog('single', [rowData]); - return; - } - - // Handle Queue to Writer button click (for Ideas table) - const queueWriterBtn = e.target.closest(`[id$="_queue_writer_btn"]`); - if (queueWriterBtn) { - e.preventDefault(); - e.stopPropagation(); - const tableId = queueWriterBtn.closest('[data-table]')?.dataset?.table; - if (tableId) { - const selectedRows = getSelectedRows(tableId); - if (selectedRows.length > 0) { - if (selectedRows.length > 50) { - igny8ShowNotification('Maximum 50 ideas allowed for queuing', 'error'); - return; - } - igny8BulkQueueIdeasToWriter(selectedRows); - } else { - igny8ShowNotification('Please select ideas to queue to Writer', 'warning'); - } - } - } - - // Handle Generate Content button click (for Writer Queue table) - const generateContentBtn = e.target.closest(`[id$="_generate_content_btn"]`); - if (generateContentBtn) { - e.preventDefault(); - e.stopPropagation(); - const tableId = generateContentBtn.closest('[data-table]')?.dataset?.table; - if (tableId) { - const selectedRows = getSelectedRows(tableId); - if (selectedRows.length > 0) { - if (selectedRows.length > 5) { - igny8ShowNotification('Maximum 5 tasks allowed for content generation', 'error'); - return; - } - igny8BulkGenerateContent(selectedRows); - } else { - igny8ShowNotification('Please select tasks to generate content', 'warning'); - } - } - return; - } - - // Handle personalization button clicks - if (personalizeBtn) { - e.preventDefault(); - e.stopPropagation(); - handlePersonalizeClick(personalizeBtn); - return; - } - - // Handle personalization form submissions - if (personalizeForm) { - e.preventDefault(); - e.stopPropagation(); - handlePersonalizeFormSubmit(personalizeForm); - return; - } - - // Handle save content button - if (saveBtn) { - e.preventDefault(); - e.stopPropagation(); - handleSaveContent(saveBtn); - return; - } - - // Handle cron job buttons - if (cronBtn) { - e.preventDefault(); - e.stopPropagation(); - const action = cronBtn.getAttribute('data-action'); - const hook = cronBtn.getAttribute('data-hook'); - - if (action === 'runNow') { - handleIconRunNow(cronBtn, hook); - } else if (action === 'openInNewWindow') { - handleOpenInNewWindow(cronBtn, hook); - } - return; - } - - // Handle description toggle clicks - if (descriptionToggle) { - e.preventDefault(); - e.stopPropagation(); - // Description toggle logic would go here - return; - } - - // Handle image prompts toggle clicks - if (imagePromptsToggle) { - e.preventDefault(); - e.stopPropagation(); - // Image prompts toggle logic would go here - return; - } - }); -} - -// === Planner β Writer Bridge Functions === - -/** - * Queue a single idea to Writer - */ -function igny8QueueIdeaToWriter(ideaId) { - const data = new FormData(); - data.append('action', 'igny8_create_task_from_idea'); - data.append('nonce', igny8_ajax.nonce); - data.append('idea_id', ideaId); - - fetch(igny8_ajax.ajax_url, { - method: 'POST', - body: data - }) - .then(r => r.json()) - .then(resp => { - if (resp.success) { - igny8ShowNotification(resp.message || 'Task created', 'success'); - } else { - igny8ShowNotification(resp.message || 'Failed to create task', 'error'); - } - // Reload both tables to show updated data - if (typeof igny8ReloadTable === 'function') { - igny8ReloadTable('planner_ideas'); - igny8ReloadTable('writer_drafts'); - } - }) - .catch(error => { - console.error('Queue to Writer error:', error); - igny8ShowNotification('Failed to queue idea to Writer', 'error'); - }); -} - -/** - * Bulk generate content for tasks - */ -function igny8BulkGenerateContent(selectedIds) { - let completed = 0; - let failed = 0; - const total = selectedIds.length; - - igny8ShowNotification(`Starting content generation for ${total} tasks...`, 'info'); - - selectedIds.forEach((taskId, index) => { - // Add a small delay between requests to avoid overwhelming the API - setTimeout(() => { - const data = new FormData(); - data.append('action', 'igny8_ai_generate_content'); - data.append('nonce', window.IGNY8_PAGE?.nonce || igny8_ajax.nonce); - data.append('task_id', taskId); - - fetch(window.IGNY8_PAGE?.ajaxUrl || igny8_ajax.ajax_url, { - method: 'POST', - body: data - }) - .then(r => { - if (!r.ok) { - throw new Error(`HTTP error! status: ${r.status}`); - } - return r.text().then(text => { - try { - return JSON.parse(text); - } catch (e) { - console.error('Invalid JSON response:', text); - throw new Error('Invalid JSON response from server'); - } - }); - }) - .then(resp => { - completed++; - if (resp.success) { - const data = resp.data; - console.log(`Content generated for task ${taskId}:`, data); - - // Show detailed success message - if (data.post_id) { - console.log(`β WordPress post created: Post ID ${data.post_id}`); - if (data.post_edit_url) { - console.log(`π Edit post: ${data.post_edit_url}`); - } - } - if (data.task_status) { - console.log(`π Task status updated to: ${data.task_status}`); - } - } else { - failed++; - console.error(`Failed to generate content for task ${taskId}:`, resp.data); - } - - // Check if all requests are complete - if (completed + failed === total) { - if (failed === 0) { - igny8ShowNotification(`Content generated successfully for all ${total} tasks. Check WordPress Posts for drafts.`, 'success'); - } else { - igny8ShowNotification(`Content generation completed: ${completed} successful, ${failed} failed`, 'warning'); - } - - // Reload tables to show updated data - if (typeof igny8ReloadTable === 'function') { - igny8ReloadTable('writer_queue'); - igny8ReloadTable('writer_drafts'); - } - } - }) - .catch(error => { - failed++; - console.error(`Content generation error for task ${taskId}:`, error); - - // Check if all requests are complete - if (completed + failed === total) { - igny8ShowNotification(`Content generation completed: ${completed} successful, ${failed} failed`, 'warning'); - - // Reload tables to show updated data - if (typeof igny8ReloadTable === 'function') { - igny8ReloadTable('writer_queue'); - igny8ReloadTable('writer_drafts'); - } - } - }); - }, index * 1000); // 1 second delay between requests - }); -} - -/** - * Bulk queue ideas to Writer - */ -function igny8BulkQueueIdeasToWriter(selectedIds) { - const data = new FormData(); - data.append('action', 'igny8_bulk_create_tasks_from_ideas'); - data.append('nonce', igny8_ajax.nonce); - selectedIds.forEach(id => data.append('idea_ids[]', id)); - - // Show progress modal - showProgressModal('Queue to Writer', selectedIds.length); - - fetch(igny8_ajax.ajax_url, { - method: 'POST', - body: data - }) - .then(r => { - if (!r.ok) { - throw new Error(`HTTP error! status: ${r.status}`); - } - return r.text().then(text => { - try { - return JSON.parse(text); - } catch (e) { - console.error('Invalid JSON response:', text); - throw new Error('Invalid JSON response from server'); - } - }); - }) - .then(resp => { - if (resp.success) { - // Show success modal - showSuccessModal('Queue to Writer Complete', selectedIds.length, resp.data?.message || 'Ideas queued to Writer successfully'); - } else { - // Close progress modal and show error - if (currentProgressModal) { - currentProgressModal.remove(); - currentProgressModal = null; - } - igny8ShowNotification(resp.data?.message || 'Failed to queue ideas to Writer', 'error'); - } - // Reload both tables to show updated data - if (typeof igny8ReloadTable === 'function') { - igny8ReloadTable('planner_ideas'); - igny8ReloadTable('writer_queue'); - } - }) - .catch(error => { - console.error('Bulk queue to Writer error:', error); - // Close progress modal and show error - if (currentProgressModal) { - currentProgressModal.remove(); - currentProgressModal = null; - } - igny8ShowNotification('Failed to bulk queue ideas to Writer: ' + error.message, 'error'); - }); -} - -// === Bulk & Row Actions === - -/** - * Handle bulk actions (delete, map, unmap) - */ -function handleBulkAction(action, btn) { - const tableId = btn.closest('[data-table]')?.dataset?.table; - if (!tableId) { - igny8ShowNotification('Table not found', 'error'); - return; - } - - // Get selected row IDs - const selectedRows = getSelectedRows(tableId); - if (selectedRows.length === 0) { - igny8ShowNotification('Please select records to perform this action', 'warning'); - return; - } - - // Handle different bulk actions - if (action === 'bulk_delete_keywords') { - handleBulkDelete(selectedRows, tableId); - } else if (action === 'bulk_map_keywords') { - handleBulkMap(selectedRows, tableId); - } else if (action === 'bulk_unmap_keywords') { - handleBulkUnmap(selectedRows, tableId); - } else if (action === 'bulk_publish_drafts') { - handleBulkPublishDrafts(selectedRows, tableId); - } -} - -/** - * Handle row actions (view, map, create draft) - */ -function handleRowAction(action, btn) { - const rowId = btn.dataset.rowId; - const tableId = btn.closest('[data-table]')?.dataset?.table; - - if (!rowId || !tableId) { - igny8ShowNotification('Row or table not found', 'error'); - return; - } - - // Handle different row actions - if (action === 'view_cluster_keywords') { - handleViewClusterKeywords(rowId, tableId); - } else if (action === 'map_cluster_to_keywords') { - handleMapClusterToKeywords(rowId, tableId); - } else if (action === 'create_draft_from_idea') { - handleCreateDraftFromIdea(rowId, tableId); - } -} - -/** - * Handle bulk delete - */ -function handleBulkDelete(keywordIds, tableId) { - if (!confirm(`Are you sure you want to delete ${keywordIds.length} selected keywords?`)) { - return; - } - - sendBulkAction('igny8_bulk_delete_keywords', { keyword_ids: keywordIds }, tableId); -} - -/** - * Handle bulk map to cluster - */ -function handleBulkMap(keywordIds, tableId) { - // Get cluster selection (could be from a dropdown or modal) - const clusterSelect = document.querySelector(`[data-table="${tableId}"] .cluster-select`); - if (!clusterSelect) { - igny8ShowNotification('No cluster selection found', 'error'); - return; - } - - const clusterId = clusterSelect.value; - if (!clusterId) { - igny8ShowNotification('Please select a cluster to map keywords to', 'warning'); - return; - } - - sendBulkAction('igny8_bulk_map_keywords', { keyword_ids: keywordIds, cluster_id: clusterId }, tableId); -} - -/** - * Handle bulk unmap from clusters - */ -function handleBulkUnmap(keywordIds, tableId) { - if (!confirm(`Are you sure you want to unmap ${keywordIds.length} selected keywords from their clusters?`)) { - return; - } - - sendBulkAction('igny8_bulk_unmap_keywords', { keyword_ids: keywordIds }, tableId); -} - -/** - * Handle view cluster keywords (modal) - */ -function handleViewClusterKeywords(clusterId, tableId) { - sendRowAction('igny8_view_cluster_keywords', { cluster_id: clusterId }, (response) => { - if (response.success) { - showClusterKeywordsModal(response.cluster_name, response.keywords); - } - }); -} - -/** - * Handle map cluster to keywords - */ -function handleMapClusterToKeywords(clusterId, tableId) { - // Get selected keyword IDs from checkboxes - const selectedRows = getSelectedRows(tableId); - if (selectedRows.length === 0) { - igny8ShowNotification('Please select keywords to map to this cluster', 'warning'); - return; - } - - sendRowAction('igny8_map_cluster_to_keywords', { - cluster_id: clusterId, - keyword_ids: selectedRows - }, tableId); -} - -/** - * Handle create draft from idea - */ -function handleCreateDraftFromIdea(ideaId, tableId) { - sendRowAction('igny8_create_draft_from_idea', { idea_id: ideaId }, (response) => { - if (response.success) { - igny8ShowNotification(`Draft created successfully (ID: ${response.draft_id})`, 'success'); - // Optionally refresh the table to show the new draft - loadTableData(tableId, {}, 1); - } - }); -} - -/** - * Handle bulk publish drafts action - */ -function handleBulkPublishDrafts(selectedRows, tableId) { - if (selectedRows.length === 0) { - igny8ShowNotification('Please select drafts to publish', 'warning'); - return; - } - - // Show confirmation dialog - if (!confirm(`Are you sure you want to publish ${selectedRows.length} draft(s)? This will make them live on your website.`)) { - return; - } - - const formData = new FormData(); - formData.append('action', 'igny8_bulk_publish_drafts'); - formData.append('nonce', window.IGNY8_PAGE?.nonce || igny8_ajax.nonce); - - // Add task IDs - selectedRows.forEach(id => { - formData.append('task_ids[]', id); - }); - - // Show progress notification - igny8ShowNotification('Publishing drafts...', 'info'); - - fetch(window.IGNY8_PAGE?.ajaxUrl || igny8_ajax.ajax_url, { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - igny8ShowNotification(data.data.message, 'success'); - // Refresh table to show updated data - if (window.loadTableData && tableId) { - window.loadTableData(tableId); - } - } else { - igny8ShowNotification(data.data?.message || 'Failed to publish drafts', 'error'); - } - }) - .catch(error => { - console.error('Bulk publish error:', error); - igny8ShowNotification('Network error occurred while publishing', 'error'); - }); -} - -/** - * Send bulk action AJAX request - */ -function sendBulkAction(action, data, tableId) { - const formData = new FormData(); - formData.append('action', action); - formData.append('nonce', igny8_ajax.nonce); - - // Add data fields - Object.keys(data).forEach(key => { - if (Array.isArray(data[key])) { - data[key].forEach(value => { - formData.append(`${key}[]`, value); - }); - } else { - formData.append(key, data[key]); - } - }); - - fetch(igny8_ajax.ajax_url, { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - igny8ShowNotification(data.data.message, 'success'); - - // Show workflow automation results if available - if (data.data.workflow_message) { - igny8ShowNotification(data.data.workflow_message, 'info'); - } - - // Refresh table to show updated data - loadTableData(tableId, {}, 1); - } else { - igny8ShowNotification(data.data || 'Action failed', 'error'); - } - }) - .catch(error => { - igny8ShowNotification('Network error occurred', 'error'); - }); -} - -/** - * Send row action AJAX request - */ -function sendRowAction(action, data, tableId, callback) { - const formData = new FormData(); - formData.append('action', action); - formData.append('nonce', igny8_ajax.nonce); - - // Add data fields - Object.keys(data).forEach(key => { - formData.append(key, data[key]); - }); - - fetch(igny8_ajax.ajax_url, { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - if (callback) { - callback(data); - } else { - igny8ShowNotification(data.data.message, 'success'); - - // Show workflow automation results if available - if (data.data.workflow_message) { - igny8ShowNotification(data.data.workflow_message, 'info'); - } - - // Refresh table to show updated data - if (tableId) { - loadTableData(tableId, {}, 1); - } - } - } else { - igny8ShowNotification(data.data || 'Action failed', 'error'); - } - }) - .catch(error => { - igny8ShowNotification('Network error occurred', 'error'); - }); -} - -/** - * Show cluster keywords modal - */ -function showClusterKeywordsModal(clusterName, keywords) { - // Remove existing modal if present - document.getElementById('cluster-keywords-modal')?.remove(); - - const modal = document.createElement('div'); - modal.id = 'cluster-keywords-modal'; - modal.className = 'igny8-modal'; - modal.innerHTML = ` - - `; - - document.body.appendChild(modal); - modal.classList.add('open'); -} - -/* ========================================= - Igny8 Pagination Component - ========================================= */ - -function initializePagination(tableId) { - const container = document.querySelector(`[data-table="${tableId}"].igny8-pagination`); - if (!container) { - return; - } - - // --- Page Navigation --- - container.addEventListener('click', e => { - const btn = e.target.closest('.igny8-page-btn'); - if (!btn) return; - - const page = parseInt(btn.dataset.page); - if (!page || page === getCurrentPage(tableId)) return; - - const filters = collectFilters(tableId); - const perPage = getSessionPerPage(tableId) || getDefaultPerPage(); - loadTableData(tableId, filters, page, perPage); - }); - - // --- Per-Page Selection --- - const perPageSelect = document.querySelector(`#${tableId} .igny8-per-page-select`); - if (perPageSelect) { - perPageSelect.addEventListener('change', () => { - const perPage = parseInt(perPageSelect.value); - const filters = collectFilters(tableId); - filters.per_page = perPage; - loadTableData(tableId, filters, 1); - }); - } -} - -function getCurrentPage(tableId) { - return parseInt(document - .querySelector(`[data-table="${tableId}"].igny8-pagination`) - ?.dataset.currentPage || 1); -} - -function loadPage(tableId, page, perPage = null) { - const filters = collectFilters(tableId); - filters.per_page = perPage || getCurrentPerPage(tableId); - loadTableData(tableId, filters, page); -} - -function getCurrentPerPage(tableId) { - return parseInt(document.querySelector(`#${tableId} .igny8-per-page-select`)?.value || 10); -} - -/** - * Update pagination controls - * @param {string} tableId - * @param {Object} p - Pagination data - */ -function updatePagination(tableId, p) { - const container = document.querySelector(`[data-table="${tableId}"].igny8-pagination`); - if (!container) return; - - container.dataset.currentPage = p.current_page || 1; - container.dataset.totalItems = p.total_items || 0; - container.dataset.perPage = p.per_page || 10; - - container.innerHTML = ''; - - if (!p.total_pages) return; - - const { current_page, total_pages, total_items, per_page } = p; - - // --- Prev Button --- - if (current_page > 1) addPageBtn(container, 'βΉ Previous', current_page - 1); - - // --- First Pages --- - for (let i = 1; i <= Math.min(2, total_pages); i++) - addPageBtn(container, i, i, i === current_page); - - // --- Ellipsis before middle --- - if (total_pages > 4 && current_page > 3) addEllipsis(container); - - // --- Middle Page --- - if (current_page > 2 && current_page < total_pages - 1) - addPageBtn(container, current_page, current_page, true); - - // --- Ellipsis before last pages --- - if (total_pages > 4 && current_page < total_pages - 2) addEllipsis(container); - - // --- Last Pages --- - for (let i = Math.max(3, total_pages - 1); i <= total_pages; i++) - if (i > 2) addPageBtn(container, i, i, i === current_page); - - // --- Next Button --- - if (current_page < total_pages) addPageBtn(container, 'Next βΊ', current_page + 1); - - // --- Info Text --- - const start = (current_page - 1) * per_page + 1; - const end = Math.min(current_page * per_page, total_items); - const info = document.createElement('span'); - info.textContent = `Showing ${start}-${end} of ${total_items} items`; - Object.assign(info.style, { marginLeft: '12px', fontSize: '12px', color: '#666' }); - container.appendChild(info); -} - -// --- Helpers --- -function addPageBtn(container, label, page, isActive = false) { - const btn = document.createElement('button'); - btn.textContent = label; - btn.dataset.page = page; - btn.className = `igny8-btn igny8-btn-sm igny8-page-btn ${isActive ? 'igny8-btn-primary' : 'igny8-btn-outline'}`; - container.appendChild(btn); -} - -function addEllipsis(container) { - const span = document.createElement('span'); - span.textContent = '...'; - Object.assign(span.style, { margin: '0 8px', color: '#666' }); - container.appendChild(span); -} - -/* ========================================= - Igny8 Delete Dialog & Notifications - ========================================= */ - -function igny8ShowDeleteDialog(type, records) { - // Remove existing modal if present - document.getElementById('igny8-delete-modal')?.remove(); - - const modal = document.createElement('div'); - modal.id = 'igny8-delete-modal'; - modal.className = 'igny8-modal'; - - const headerTitle = type === 'single' ? 'Delete Record' : 'Delete Multiple Records'; - const bodyHTML = type === 'single' - ? getSingleDeleteBody(records[0]) - : getBulkDeleteBody(records); - - modal.innerHTML = ` - `; - - document.body.appendChild(modal); - modal.classList.add('open'); - - // Store delete context - window.igny8DeleteRecords = records; - window.igny8DeleteType = type; -} - -// ---- Helper to build single-record body ---- -function getSingleDeleteBody(record) { - const title = record.keyword || record.name || record.idea_title || 'Unknown'; - return ` -- ${children.map(child => `
-- ${child.name}
`).join('')} -Are you sure you want to delete this record?
-${title}
-This action cannot be undone.
`; -} - -// ---- Helper to build bulk delete body ---- -function getBulkDeleteBody(records) { - const total = records.length; - const previewCount = Math.min(5, total); - const remaining = total - previewCount; - - const previewItems = records.slice(0, previewCount) - .map(r => `- ${r.keyword || r.name || r.idea_title || 'Unknown'}
`) - .join(''); - - const moreText = remaining > 0 ? `- ... and ${remaining} more records
` : ''; - - return ` -Are you sure you want to delete ${total} records?
-${previewItems}${moreText}
-This action cannot be undone.
`; -} - -// ---- Confirm Deletion ---- -async function igny8ConfirmDelete() { - const { igny8DeleteRecords: records, igny8DeleteType: type } = window; - if (!records || !type) return; - - const tableId = records[0].table_id || 'planner_keywords'; - - try { - const formData = new FormData(); - formData.append('nonce', igny8_ajax.nonce); - formData.append('table_id', tableId); - - if (type === 'single') { - formData.append('action', 'igny8_delete_single_record'); - formData.append('record_id', records[0].id); - } else { - formData.append('action', 'igny8_delete_bulk_records'); - records.forEach(r => formData.append('record_ids[]', r.id)); - } - - const res = await fetch(igny8_ajax.ajax_url, { method: 'POST', body: formData }); - const data = await res.json(); - - if (data.success) { - igny8ShowNotification(data.data.message || 'Record(s) deleted successfully', 'success'); - igny8CancelDelete(); - loadTableData(tableId, {}, 1); - if (type === 'bulk') updateBulkActionStates(tableId); - } else { - igny8ShowNotification(data.data || 'Delete failed', 'error'); - } - } catch (err) { - igny8ShowNotification('Delete failed due to a server error.', 'error'); - } -} - -// ---- Cancel & Close Modal ---- -function igny8CancelDelete() { - document.getElementById('igny8-delete-modal')?.remove(); - window.igny8DeleteRecords = null; - window.igny8DeleteType = null; -} - -// ---- Universal Bulk Action State Update ---- -function updateBulkActionStates(tableId) { - document.querySelectorAll(`[data-table="${tableId}"] .igny8-checkbox`) - .forEach(cb => cb.checked = false); - - const selectAll = document.querySelector(`[data-table="${tableId}"] .igny8-select-all`); - if (selectAll) { - selectAll.checked = false; - selectAll.indeterminate = false; - } - - const deleteBtn = document.getElementById(`${tableId}_delete_btn`); - const exportBtn = document.getElementById(`${tableId}_export_btn`); - if (deleteBtn) deleteBtn.disabled = true; - if (exportBtn) exportBtn.disabled = true; - - const countDisplay = document.querySelector(`[data-table="${tableId}"] .igny8-selected-count`); - if (countDisplay) countDisplay.textContent = '0 selected'; -} - -// ---- Unified Notification System ---- -function igny8ShowNotification(message, type = 'info', tableId = null) { - // Use the unified global notification system - igny8GlobalNotification(message, type); -} - -// =================================================================== -// TABLE COMPONENT -// =================================================================== - -/* ---------------------------- - Table Body Update ------------------------------ */ -function updateTableBody(tableId, tableBodyHtml) { - const tbody = document.querySelector(`#table-${tableId}-body`); - if (!tbody) return; - - tbody.innerHTML = ''; - - if (tableBodyHtml) { - const template = document.createElement('template'); - template.innerHTML = tableBodyHtml; - - template.content.querySelectorAll('tr').forEach(row => { - tbody.appendChild(row.cloneNode(true)); - }); - } -} - -/* ---------------------------- - Loading State ------------------------------ */ -function showTableLoadingState(tableId) { - const tbody = document.querySelector(`#table-${tableId}-body`); - if (!tbody) return; - - tbody.innerHTML = ''; - const loadingRow = document.createElement('tr'); - loadingRow.innerHTML = `Loading... `; - tbody.appendChild(loadingRow); -} - -/* ---------------------------- - Detect Current Table ------------------------------ */ -function detectCurrentTableId() { - return ( - document.querySelector('.igny8-table[data-table]')?.dataset.table || - document.querySelector('.igny8-filters[data-table]')?.dataset.table || - document.querySelector('.igny8-pagination[data-table]')?.dataset.table || - null - ); -} - -/* ---------------------------- - Unified AJAX Loader ------------------------------ */ -async function loadTableData(tableId, filters = {}, page = 1, perPage = null) { - try { - showTableLoadingState(tableId); - - const recordsPerPage = perPage || getSessionPerPage(tableId) || getDefaultPerPage(); - const body = new URLSearchParams({ - action: 'igny8_get_table_data', - nonce: igny8_ajax.nonce, - table: tableId, - filters: JSON.stringify(filters), - page, - per_page: recordsPerPage - }); - - const response = await fetch(igny8_ajax.ajax_url, { method: 'POST', body }); - if (!response.ok) throw new Error(`HTTP ${response.status}`); - - const data = await response.json(); - if (!data.success) throw new Error(data.data || 'Unknown error'); - - // Update DOM - if (data.data.table_body_html) updateTableBody(tableId, data.data.table_body_html); - if (data.data.pagination) updatePagination(tableId, data.data.pagination); - } catch (err) { - igny8ShowNotification('Failed to load table data', 'error'); - } -} - -/* ---------------------------- - Universal Table Initialization ------------------------------ */ -function initializeTableWithAJAX(tableId, module, submodule) { - initializeFilters(); - initializeTableActions(tableId); - initializePagination(tableId); - initializeTableSelection(tableId); - loadTableData(tableId, {}, 1); -} - -/* ---------------------------- - Row Selection Handling ------------------------------ */ -function initializeTableSelection(tableId) { - const table = document.getElementById(tableId); - if (!table) return; - - const selectAll = table.querySelector('thead input[type="checkbox"]'); - - if (selectAll) { - selectAll.addEventListener('change', () => { - document.querySelectorAll(`#table-${tableId}-body input[type="checkbox"]`).forEach(cb => { - cb.checked = selectAll.checked; - }); - dispatchSelectionChange(tableId); - }); - } - - table.addEventListener('change', e => { - if (e.target.matches('#table-' + tableId + '-body input[type="checkbox"]')) { - const all = document.querySelectorAll(`#table-${tableId}-body input[type="checkbox"]`); - const checked = document.querySelectorAll(`#table-${tableId}-body input[type="checkbox"]:checked`); - - if (selectAll) { - selectAll.checked = checked.length === all.length; - selectAll.indeterminate = checked.length > 0 && checked.length < all.length; - } - dispatchSelectionChange(tableId); - } - }); -} - -function dispatchSelectionChange(tableId) { - document.dispatchEvent(new CustomEvent('rowSelectionChanged', { detail: { tableId } })); -} - -/* ---------------------------- - Universal Row Data Extraction ------------------------------ */ -function getUniversalRowData(tableId, rowId) { - const row = document.querySelector(`[data-table="${tableId}"] tr[data-id="${rowId}"]`); - if (!row) return null; - - const headers = row.closest('table').querySelectorAll('thead th'); - const cells = row.querySelectorAll('td'); - const record = { id: rowId, table_id: tableId }; - - // Map headers to cell values (skip checkbox + actions) - for (let i = 1; i < headers.length - 1; i++) { - const field = headers[i].textContent.trim().toLowerCase().replace(/\s+/g, '_'); - record[field] = cells[i]?.textContent.trim() || ''; - } - - if (tableId === 'planner_keywords') { - const map = { volume: 'search_volume', cluster: 'cluster_id' }; - return Object.keys(record).reduce((acc, key) => { - acc[map[key] || key] = record[key]; - return acc; - }, { id: rowId, table_id: tableId }); - } - return record; -} - -/* ---------------------------- - Per-Page Handling ------------------------------ */ -function initializePerPageSelectors() { - const defaultPP = getDefaultPerPage(); - - document.querySelectorAll('.igny8-per-page-select').forEach(select => { - const tableId = select.dataset.table; - select.value = getSessionPerPage(tableId) || defaultPP; - - select.addEventListener('change', e => { - const perPage = parseInt(e.target.value); - setSessionPerPage(tableId, perPage); - loadTableData(tableId, {}, 1, perPage); - }); - }); -} - -const getDefaultPerPage = () => 20; -const getSessionPerPage = id => sessionStorage.getItem(`igny8_per_page_${id}`); -const setSessionPerPage = (id, val) => sessionStorage.setItem(`igny8_per_page_${id}`, val); - -/* ---------------------------- - Prompts Functionality ------------------------------ */ -window.initializePromptsFunctionality = function() { - // Only initialize if we're on the planner home page - if (!window.IGNY8_PAGE || window.IGNY8_PAGE.module !== 'planner' || window.IGNY8_PAGE.submodule !== 'home') { - return; - } - - const savePromptsBtn = document.getElementById('igny8-save-prompts'); - const resetPromptsBtn = document.getElementById('igny8-reset-prompts'); - - if (savePromptsBtn) { - savePromptsBtn.addEventListener('click', function() { - const formData = new FormData(); - formData.append('action', 'igny8_save_ai_prompts'); - formData.append('nonce', window.IGNY8_PAGE.nonce); - - // Get prompt values - const clusteringPrompt = document.querySelector('textarea[name="igny8_clustering_prompt"]').value; - const ideasPrompt = document.querySelector('textarea[name="igny8_ideas_prompt"]').value; - - formData.append('igny8_clustering_prompt', clusteringPrompt); - formData.append('igny8_ideas_prompt', ideasPrompt); - - // Show loading state - const originalText = savePromptsBtn.textContent; - savePromptsBtn.textContent = 'Saving...'; - savePromptsBtn.disabled = true; - - fetch(window.IGNY8_PAGE.ajaxUrl, { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - igny8GlobalNotification('Prompts saved successfully!', 'success'); - } else { - const errorMsg = data.data?.message || 'Error saving prompts'; - igny8GlobalNotification(errorMsg, 'error'); - } - }) - .catch(error => { - console.error('Error saving prompts:', error); - igny8GlobalNotification('Error saving prompts. Please try again.', 'error'); - }) - .finally(() => { - // Reset button state - savePromptsBtn.textContent = originalText; - savePromptsBtn.disabled = false; - }); - }); - } - - if (resetPromptsBtn) { - resetPromptsBtn.addEventListener('click', function() { - if (confirm('Are you sure you want to reset all prompts to their default values? This action cannot be undone.')) { - const formData = new FormData(); - formData.append('action', 'igny8_reset_ai_prompts'); - formData.append('nonce', window.IGNY8_PAGE.nonce); - - // Show loading state - const originalText = resetPromptsBtn.textContent; - resetPromptsBtn.textContent = 'Resetting...'; - resetPromptsBtn.disabled = true; - - fetch(window.IGNY8_PAGE.ajaxUrl, { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - // Reload the page to show default prompts - window.location.reload(); - } else { - const errorMsg = data.data?.message || 'Error resetting prompts'; - igny8GlobalNotification(errorMsg, 'error'); - } - }) - .catch(error => { - console.error('Error resetting prompts:', error); - igny8GlobalNotification('Error resetting prompts. Please try again.', 'error'); - }) - .finally(() => { - // Reset button state - resetPromptsBtn.textContent = originalText; - resetPromptsBtn.disabled = false; - }); - } - }); - } -} - -/* ---------------------------- - DOM Ready ------------------------------ */ -document.addEventListener('DOMContentLoaded', () => { - initializePerPageSelectors(); - - // Initialize planner settings - initializePlannerSettings(); - - // Initialize AI integration form - initializeAIIntegrationForm(); - - // Initialize AI action buttons - initializeAIActionButtons(); - - // Initialize prompts functionality - initializePromptsFunctionality(); - - // Initialize Writer AI settings - initializeWriterAISettings(); - - // Only initialize table functionality on submodule pages that have tableId - if (typeof IGNY8_PAGE !== 'undefined' && IGNY8_PAGE.submodule && IGNY8_PAGE.tableId) { - initializeTableWithAJAX(IGNY8_PAGE.tableId, IGNY8_PAGE.module, IGNY8_PAGE.submodule); - } - // No fallback initialization - tables should only be initialized on submodule pages - - // Initialize all delegated events in one place to prevent conflicts - initializeDelegatedEvents(); - - // Initialize personalization functionality - initializePersonalization(); -}); - -// =================================================================== -// Form functionality -// =================================================================== - -/** - * Client-side validation function for form data - * Performs lightweight validation before AJAX submission - * - * @param {HTMLElement} formRow The form row element - * @param {string} tableId The table ID for validation rules - * @returns {Object} Validation result with valid boolean and error message - */ -function igny8ValidateFormData(formRow, tableId) { - // Define validation rules for each table - const validationRules = { - 'planner_keywords': { - 'keyword': { required: true, maxLength: 255, noHtml: true }, - 'search_volume': { required: false, type: 'numeric', min: 0 }, - 'difficulty': { required: false, type: 'numeric_or_text', min: 0, max: 100, textOptions: ['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' } - }, - 'planner_clusters': { - 'cluster_name': { required: true, maxLength: 255, noHtml: true }, - 'sector_id': { required: false, type: 'integer' }, - 'status': { required: true, enum: ['active', 'inactive', 'archived'] } - }, - 'planner_ideas': { - 'idea_title': { required: true, maxLength: 255, noHtml: true }, - 'idea_description': { required: false, noHtml: 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' }, - 'status': { required: true, enum: ['new', 'scheduled', 'published'] }, - 'estimated_word_count': { required: false, type: 'integer', min: 0 }, - 'target_keywords': { required: false, type: 'text', noHtml: false }, - 'mapped_post_id': { required: false, type: 'integer' } - }, - 'writer_tasks': { - 'title': { required: true, maxLength: 255, noHtml: true }, - 'description': { required: false, noHtml: true }, - 'status': { required: true, enum: ['pending', 'in_progress', 'completed', 'cancelled', 'draft', 'queued', 'review', 'published'] }, - 'priority': { required: true, enum: ['high', 'medium', 'low', 'urgent'] }, - 'content_type': { required: false, enum: ['blog_post', 'landing_page', 'product_page', 'guide_tutorial', 'news_article', 'review', 'comparison', 'email', 'social_media'] }, - 'cluster_id': { required: false, type: 'integer' }, - 'keywords': { required: false, type: 'text', noHtml: false }, - 'word_count': { required: false, type: 'integer', min: 0 }, - 'idea_id': { required: false, type: 'integer' }, - 'due_date': { required: false, type: 'datetime' }, - 'schedule_at': { required: false, type: 'datetime' }, - 'assigned_post_id': { required: false, type: 'integer' }, - 'ai_writer': { required: false, enum: ['ai', 'human'] } - }, - }; - - const rules = validationRules[tableId]; - if (!rules) { - return { valid: true }; // No validation rules defined, allow submission - } - - // Get form inputs - const inputs = formRow.querySelectorAll('input, textarea'); - const selects = formRow.querySelectorAll('.select-btn'); - - // Validate each field - for (const [fieldName, fieldRules] of Object.entries(rules)) { - let value = ''; - - // Get value from input or select - const input = Array.from(inputs).find(i => i.name === fieldName); - if (input) { - value = input.value.trim(); - } else { - const select = Array.from(selects).find(s => s.name === fieldName); - if (select) { - value = select.getAttribute('data-value') || ''; - } - } - - // Required field validation - if (fieldRules.required && (!value || value === '')) { - return { valid: false, error: `${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)} is required` }; - } - - // Skip further validation if field is empty and not required - if (!value || value === '') { - continue; - } - - // Length validation - if (fieldRules.maxLength && value.length > fieldRules.maxLength) { - return { valid: false, error: `${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)} cannot exceed ${fieldRules.maxLength} characters` }; - } - - // HTML content validation - if (fieldRules.noHtml && value !== value.replace(/<[^>]*>/g, '')) { - return { valid: false, error: `${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)} cannot contain HTML` }; - } - - // Numeric validation - if (fieldRules.type === 'numeric' || fieldRules.type === 'integer' || fieldRules.type === 'decimal' || fieldRules.type === 'numeric_or_text') { - let numValue; - - // Handle numeric_or_text type (like difficulty) - if (fieldRules.type === 'numeric_or_text') { - if (fieldRules.textOptions && fieldRules.textOptions.includes(value)) { - // Valid text option, convert to numeric for range validation - const difficultyMap = { - 'Very Easy': 10, - 'Easy': 30, - 'Medium': 50, - 'Hard': 70, - 'Very Hard': 90 - }; - numValue = difficultyMap[value] || 0; - } else if (isNaN(value) || value === '') { - return { valid: false, error: `${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)} must be a number or valid difficulty level` }; - } else { - numValue = parseFloat(value); - } - } else { - if (isNaN(value) || value === '') { - return { valid: false, error: `${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)} must be a number` }; - } - numValue = parseFloat(value); - } - - // Range validation - if (fieldRules.min !== undefined && numValue < fieldRules.min) { - return { valid: false, error: `${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)} must be at least ${fieldRules.min}` }; - } - - if (fieldRules.max !== undefined && numValue > fieldRules.max) { - return { valid: false, error: `${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)} must be at most ${fieldRules.max}` }; - } - - // Integer validation - if ((fieldRules.type === 'integer') && !Number.isInteger(numValue)) { - return { valid: false, error: `${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)} must be a whole number` }; - } - } - - // Enum validation - if (fieldRules.enum && !fieldRules.enum.includes(value)) { - return { valid: false, error: `${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)} must be one of: ${fieldRules.enum.join(', ')}` }; - } - } - - return { valid: true }; -} - -document.addEventListener('click', function (e) { - const saveBtn = e.target.closest('.igny8-form-save'); - const cancelBtn = e.target.closest('.igny8-form-cancel'); - - // Save action - if (saveBtn) { - const formRow = saveBtn.closest('tr.igny8-inline-form-row'); - if (!formRow) return; - - const tableId = saveBtn.dataset.tableId; - const nonce = saveBtn.dataset.nonce; - const mode = formRow.dataset.mode; - const recordId = formRow.dataset.id || ''; - - // Client-side validation before AJAX submit - const validationResult = igny8ValidateFormData(formRow, tableId); - if (!validationResult.valid) { - if (typeof igny8ShowNotification === 'function') { - igny8ShowNotification(validationResult.error, 'error'); - } - return; - } - - const formData = new FormData(); - formData.append('action', 'igny8_save_form_record'); - formData.append('nonce', nonce); - formData.append('table_id', tableId); - formData.append('action_type', mode); - if (recordId) formData.append('record_id', recordId); - - formRow.querySelectorAll('input, textarea').forEach(input => { - if (input.name && input.name !== 'record_id') { - formData.append(input.name, input.value); - } - }); - - formRow.querySelectorAll('.select-btn').forEach(btn => { - if (btn.name) formData.append(btn.name, btn.getAttribute('data-value') || ''); - }); - - fetch(ajaxurl, { method: 'POST', body: formData }) - .then(res => res.json()) - .then(result => { - if (result.success) { - if (typeof igny8ShowNotification === 'function') { - igny8ShowNotification('Record saved successfully!', 'success', tableId); - - // Show workflow automation results if available - if (result.data.workflow_message) { - igny8ShowNotification(result.data.workflow_message, 'info', tableId); - } - } - formRow.style.transition = 'all 0.3s ease-out'; - formRow.style.opacity = '0'; - formRow.style.transform = 'translateX(-20px)'; - setTimeout(() => { - formRow.remove(); - if (typeof loadTableData === 'function') { - loadTableData(tableId, {}, 1); - } else { - location.reload(); - } - }, 300); - } else { - igny8ShowNotification(`Error saving record: ${result.data?.message || result.data || 'Unknown error'}`, 'error', tableId); - } - }) - .catch(err => { - igny8ShowNotification(`Error saving record: ${err.message}`, 'error', tableId); - }); - } - - // Cancel action - if (cancelBtn) { - const formRow = cancelBtn.closest('tr.igny8-inline-form-row'); - if (!formRow) return; - formRow.style.transition = 'all 0.3s ease-out'; - formRow.style.opacity = '0'; - formRow.style.transform = 'translateX(20px)'; - setTimeout(() => formRow.remove(), 300); - } -}); - -/* ========================================= - Personalization Module Functionality - ========================================= */ - -/** - * Initialize personalization functionality - */ -function initializePersonalization() { - // Personalization click handlers moved to main delegated events handler - - // Handle auto mode initialization - const autoContainer = document.getElementById('igny8-auto-content'); - if (autoContainer) { - initializeAutoMode(autoContainer); - } - - // Handle inline mode initialization - const inlineContainer = document.getElementById('igny8-inline-form'); - if (inlineContainer) { - initializeInlineMode(inlineContainer); - } -} - -/** - * Handle personalization button click - */ -function handlePersonalizeClick(button) { - const ajaxUrl = button.dataset.ajaxUrl; - const postId = button.dataset.postId; - const formFields = button.dataset.formFields || ''; - - // Get all data attributes for context - const contextData = {}; - for (const [key, value] of Object.entries(button.dataset)) { - if (key !== 'ajaxUrl' && key !== 'postId' && key !== 'formFields') { - contextData[key] = value; - } - } - - // Build URL with context data - let url = `${ajaxUrl}?action=igny8_get_fields&post_id=${postId}`; - if (formFields) { - url += `&form_fields=${encodeURIComponent(formFields)}`; - } - - // Add nonce for security - if (window.igny8_ajax?.nonce) { - url += `&nonce=${encodeURIComponent(window.igny8_ajax.nonce)}`; - } - - // Add context data as query parameters - for (const [key, value] of Object.entries(contextData)) { - url += `&${key}=${encodeURIComponent(value)}`; - } - - // Show loading state - const originalContent = button.parentElement.innerHTML; - button.parentElement.innerHTML = 'Loading personalization form...'; - - // Load form fields - fetch(url, { - method: 'GET', - headers: { - 'X-Requested-With': 'XMLHttpRequest' - } - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - button.parentElement.innerHTML = data.data; - // Re-initialize form handlers for the new content - initializePersonalization(); - } else { - throw new Error(data.data || 'Failed to load form'); - } - }) - .catch(error => { - console.error('Error loading personalization form:', error); - button.parentElement.innerHTML = originalContent; - igny8ShowNotification('Error loading personalization form: ' + error.message, 'error'); - }); -} - -/** - * Handle personalization form submission - */ -function handlePersonalizeFormSubmit(form) { - const ajaxUrl = form.closest('[data-ajax-url]')?.dataset.ajaxUrl || - document.querySelector('#igny8-launch')?.dataset.ajaxUrl || - window.igny8_ajax?.ajax_url; - const postId = form.closest('[data-post-id]')?.dataset.postId || - document.querySelector('#igny8-launch')?.dataset.postId; - - if (!ajaxUrl || !postId) { - igny8ShowNotification('Missing configuration for personalization', 'error'); - return; - } - - // Collect form data - const formData = new FormData(); - formData.append('action', 'igny8_generate_custom'); - formData.append('nonce', window.igny8_ajax?.nonce || ''); - formData.append('post_id', postId); - - // Add all form fields - const inputs = form.querySelectorAll('input, select, textarea'); - inputs.forEach(input => { - if (input.name && input.name !== 'submit') { - formData.append(input.name, input.value); - } - }); - - // Show loading state - const outputContainer = document.getElementById('igny8-generated-content') || form.parentElement; - if (outputContainer) { - outputContainer.innerHTML = 'Generating personalized content...'; - } - - // Submit form - fetch(ajaxUrl, { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - if (outputContainer) { - outputContainer.innerHTML = ` --- `; - } - igny8ShowNotification('Content personalized successfully!', 'success'); - } else { - throw new Error(data.data || 'Failed to generate content'); - } - }) - .catch(error => { - console.error('Error generating content:', error); - if (outputContainer) { - outputContainer.innerHTML = 'Your Personalized Content
-- ${data.data} --- --Error generating personalized content: ' + error.message + ''; - } - igny8ShowNotification('Error generating personalized content', 'error'); - }); -} - -/** - * Handle save content button - */ -function handleSaveContent(button) { - const contentContainer = button.closest('.igny8-content-container'); - if (!contentContainer) { - igny8ShowNotification('No content to save', 'error'); - return; - } - - const content = contentContainer.querySelector('.igny8-final-content')?.innerHTML; - const postId = document.querySelector('#igny8-launch')?.dataset.postId || - document.querySelector('[data-post-id]')?.dataset.postId; - - if (!content || !postId) { - igny8ShowNotification('Missing content or post ID', 'error'); - return; - } - - // Get field inputs from the form - const form = document.getElementById('igny8-form'); - const fieldInputs = {}; - if (form) { - const inputs = form.querySelectorAll('input, select, textarea'); - inputs.forEach(input => { - if (input.name && input.name !== 'submit' && input.name !== 'PageContent') { - fieldInputs[input.name] = input.value; - } - }); - } - - // Show loading state - const originalText = button.innerHTML; - button.innerHTML = ' Saving...'; - button.disabled = true; - - // Save content - const formData = new FormData(); - formData.append('action', 'igny8_save_content_manual'); - formData.append('nonce', window.igny8_ajax?.nonce || ''); - formData.append('content', content); - formData.append('post_id', postId); - formData.append('field_inputs', JSON.stringify(fieldInputs)); - - fetch(window.igny8_ajax?.ajax_url || '/wp-admin/admin-ajax.php', { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - igny8ShowNotification(data.data?.message || 'Content saved successfully!', 'success'); - // Update the content container with saved status - const statusElement = contentContainer.querySelector('.igny8-content-status'); - if (statusElement) { - statusElement.textContent = 'β Content saved'; - } - } else { - igny8ShowNotification(data.data?.message || 'Error saving content', 'error'); - } - }) - .catch(error => { - console.error('Error saving content:', error); - igny8ShowNotification('Error saving content', 'error'); - }) - .finally(() => { - button.innerHTML = originalText; - button.disabled = false; - }); -} - -/** - * Initialize auto mode - */ -function initializeAutoMode(container) { - const ajaxUrl = container.dataset.ajaxUrl; - const postId = container.dataset.postId; - const formFields = container.dataset.formFields || ''; - - // Get all data attributes for context - const contextData = {}; - for (const [key, value] of Object.entries(container.dataset)) { - if (key !== 'ajaxUrl' && key !== 'postId' && key !== 'formFields') { - contextData[key] = value; - } - } - - // Build URL with context data - let url = `${ajaxUrl}?action=igny8_get_fields&post_id=${postId}`; - if (formFields) { - url += `&form_fields=${encodeURIComponent(formFields)}`; - } - - // Add context data as query parameters - for (const [key, value] of Object.entries(contextData)) { - url += `&${key}=${encodeURIComponent(value)}`; - } - - // Load form and auto-submit - fetch(url) - .then(response => response.text()) - .then(html => { - const formContainer = document.createElement('div'); - formContainer.innerHTML = html; - const form = formContainer.querySelector('#igny8-form'); - - if (form) { - // Auto-submit the form - setTimeout(() => { - form.dispatchEvent(new Event('submit')); - }, 1000); - } - }) - .catch(error => { - console.error('Error in auto mode:', error); - container.querySelector('.igny8-loading').textContent = 'Error loading personalization form'; - }); -} - -/** - * Initialize inline mode - */ -function initializeInlineMode(container) { - const ajaxUrl = document.querySelector('#igny8-launch')?.dataset.ajaxUrl || - window.igny8_ajax?.ajax_url; - const postId = document.querySelector('#igny8-launch')?.dataset.postId || - document.querySelector('[data-post-id]')?.dataset.postId; - const formFields = document.querySelector('#igny8-launch')?.dataset.formFields || ''; - - if (!ajaxUrl || !postId) { - console.error('Missing AJAX URL or post ID for inline mode'); - return; - } - - // Get all data attributes for context - const launchButton = document.querySelector('#igny8-launch'); - const contextData = {}; - if (launchButton) { - for (const [key, value] of Object.entries(launchButton.dataset)) { - if (key !== 'ajaxUrl' && key !== 'postId' && key !== 'formFields') { - contextData[key] = value; - } - } - } - - // Build URL with context data - let url = `${ajaxUrl}?action=igny8_get_fields&post_id=${postId}`; - if (formFields) { - url += `&form_fields=${encodeURIComponent(formFields)}`; - } - - // Add context data as query parameters - for (const [key, value] of Object.entries(contextData)) { - url += `&${key}=${encodeURIComponent(value)}`; - } - - // Load form fields - const formContainer = container.querySelector('#igny8-form-container'); - if (formContainer) { - fetch(url) - .then(response => response.text()) - .then(html => { - formContainer.innerHTML = html; - // Re-initialize form handlers for the new content - initializePersonalization(); - }) - .catch(error => { - console.error('Error loading inline form:', error); - formContainer.innerHTML = 'Error loading form fields.
'; - }); - } -} - -/** - * Global function for manual save (called from onclick) - */ -window.igny8_save_content_manual = function(button) { - handleSaveContent(button); -}; - -/** - * Initialize Writer AI Settings - */ -function initializeWriterAISettings() { - // Only initialize if we're on the writer home page - if (!window.IGNY8_PAGE || window.IGNY8_PAGE.module !== 'writer' || window.IGNY8_PAGE.submodule !== 'home') { - return; - } - - // Writer Mode Toggle - const writerModeRadios = document.querySelectorAll('input[name="igny8_writer_mode"]'); - writerModeRadios.forEach(radio => { - radio.addEventListener('change', function() { - const aiFeatures = document.getElementById('igny8-writer-ai-features'); - if (aiFeatures) { - aiFeatures.style.display = this.value === 'ai' ? 'block' : 'none'; - } - }); - }); - - // Save Writer AI Settings - const saveWriterAIBtn = document.getElementById('igny8-save-writer-ai-settings'); - if (saveWriterAIBtn) { - saveWriterAIBtn.addEventListener('click', function() { - saveWriterAISettings(); - }); - } - - // Save Content Prompt - const saveContentPromptBtn = document.getElementById('igny8-save-content-prompt'); - if (saveContentPromptBtn) { - saveContentPromptBtn.addEventListener('click', function() { - saveContentPrompt(); - }); - } - - // Reset Content Prompt - const resetContentPromptBtn = document.getElementById('igny8-reset-content-prompt'); - if (resetContentPromptBtn) { - resetContentPromptBtn.addEventListener('click', function() { - resetContentPrompt(); - }); - } - - // Save Content Decision button - const saveContentDecisionBtn = document.getElementById('igny8-save-content-decision'); - console.log('Save Content Decision button found:', saveContentDecisionBtn); - if (saveContentDecisionBtn) { - console.log('Adding click event listener to Save Content Decision button'); - saveContentDecisionBtn.addEventListener('click', function() { - console.log('Save Content Decision button clicked!'); - saveContentDecision(); - }); - } else { - console.log('Save Content Decision button NOT found!'); - } -} - -/** - * Save Content Decision - */ -function saveContentDecision() { - console.log('saveContentDecision function called'); - - const formData = new FormData(); - formData.append('action', 'igny8_save_new_content_decision'); - formData.append('nonce', window.IGNY8_PAGE.nonce); - - const newContentAction = document.querySelector('input[name="new_content_action"]:checked')?.value || 'draft'; - console.log('Selected content action:', newContentAction); - console.log('All radio buttons:', document.querySelectorAll('input[name="new_content_action"]')); - formData.append('new_content_action', newContentAction); - - console.log('Sending AJAX request...'); - fetch(window.IGNY8_PAGE.ajaxUrl, { - method: 'POST', - body: formData - }) - .then(response => { - console.log('Response received:', response); - return response.json(); - }) - .then(data => { - console.log('Response data:', data); - if (data.success) { - igny8ShowNotification('New content decision saved successfully', 'success'); - } else { - igny8ShowNotification(data.data?.message || 'Failed to save content decision', 'error'); - } - }) - .catch(error => { - console.error('Error saving content decision:', error); - igny8ShowNotification('Error saving content decision', 'error'); - }); -} - -/** - * Save Writer AI Settings - */ -function saveWriterAISettings() { - const formData = new FormData(); - formData.append('action', 'igny8_save_writer_ai_settings'); - formData.append('nonce', window.IGNY8_PAGE.nonce); - - const writerMode = document.querySelector('input[name="igny8_writer_mode"]:checked')?.value || 'manual'; - const contentGeneration = document.querySelector('input[name="igny8_content_generation"]:checked')?.value || 'enabled'; - - formData.append('igny8_writer_mode', writerMode); - formData.append('igny8_content_generation', contentGeneration); - - fetch(window.IGNY8_PAGE.ajaxUrl, { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - igny8GlobalNotification(data.data.message, 'success'); - } else { - igny8GlobalNotification(data.data?.message || 'Failed to save Writer AI settings', 'error'); - } - }) - .catch(error => { - console.error('Error saving Writer AI settings:', error); - igny8GlobalNotification('Error saving Writer AI settings', 'error'); - }); -} - -/** - * Save Content Generation Prompt - */ -function saveContentPrompt() { - const promptTextarea = document.querySelector('textarea[name="igny8_content_generation_prompt"]'); - if (!promptTextarea) return; - - const formData = new FormData(); - formData.append('action', 'igny8_save_content_prompt'); - formData.append('nonce', window.IGNY8_PAGE.nonce); - formData.append('igny8_content_generation_prompt', promptTextarea.value); - - fetch(window.IGNY8_PAGE.ajaxUrl, { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - igny8GlobalNotification(data.data.message, 'success'); - } else { - igny8GlobalNotification(data.data?.message || 'Failed to save content prompt', 'error'); - } - }) - .catch(error => { - console.error('Error saving content prompt:', error); - igny8GlobalNotification('Error saving content prompt', 'error'); - }); -} - -/** - * Reset Content Generation Prompt - */ -function resetContentPrompt() { - if (confirm('Are you sure you want to reset the content generation prompt to default?')) { - // This would need to be implemented with a default prompt endpoint - igny8GlobalNotification('Reset to default functionality coming soon', 'info'); - } -} - -// Personalization initialization moved to main DOMContentLoaded handler - -// ========================================= -// Import/Export Functionality -// ========================================= - -window.initializeImportExport = function() { - console.log('Initializing Import/Export functionality...'); - - // Only initialize if we're on the import-export page - if (!window.IGNY8_IMPORT_EXPORT) { - console.log('IGNY8_IMPORT_EXPORT not found, skipping initialization'); - return; - } - - console.log('IGNY8_IMPORT_EXPORT found:', window.IGNY8_IMPORT_EXPORT); - - const importForm = document.getElementById('igny8-import-form'); - const exportForm = document.getElementById('igny8-export-form'); - const settingsForm = document.getElementById('igny8-settings-form'); - const downloadTemplateBtns = document.querySelectorAll('.download-template'); - - console.log('Forms found:', { - importForm: !!importForm, - exportForm: !!exportForm, - settingsForm: !!settingsForm, - downloadTemplateBtns: downloadTemplateBtns.length - }); - - // Template download handlers - downloadTemplateBtns.forEach(btn => { - btn.addEventListener('click', function() { - const templateType = this.getAttribute('data-type'); - downloadTemplate(templateType); - }); - }); - - // Import form handler - if (importForm) { - importForm.addEventListener('submit', function(e) { - if (!runImport()) { - e.preventDefault(); - } - }); - } - - // Export form handler - if (exportForm) { - exportForm.addEventListener('submit', function(e) { - if (!runExport()) { - e.preventDefault(); - } - }); - } - - // Settings form handler - if (settingsForm) { - settingsForm.addEventListener('submit', function(e) { - if (!saveImportExportSettings()) { - e.preventDefault(); - } - }); - } -}; - -// Download CSV template -function downloadTemplate(templateType) { - console.log('Downloading template:', templateType); - - // Create a form to submit to the AJAX handler - const form = document.createElement('form'); - form.method = 'POST'; - form.action = window.IGNY8_IMPORT_EXPORT.ajaxUrl; - form.style.display = 'none'; - - // Add form fields - const actionInput = document.createElement('input'); - actionInput.type = 'hidden'; - actionInput.name = 'action'; - actionInput.value = 'igny8_download_template'; - form.appendChild(actionInput); - - const nonceInput = document.createElement('input'); - nonceInput.type = 'hidden'; - nonceInput.name = 'nonce'; - nonceInput.value = window.IGNY8_IMPORT_EXPORT.nonce; - form.appendChild(nonceInput); - - const tableIdInput = document.createElement('input'); - tableIdInput.type = 'hidden'; - tableIdInput.name = 'table_id'; - tableIdInput.value = 'planner_' + templateType; - form.appendChild(tableIdInput); - - // Add form to document and submit - document.body.appendChild(form); - form.submit(); - document.body.removeChild(form); - - console.log('Template download initiated'); - igny8GlobalNotification('Downloading template...', 'info'); -} - -// Run CSV import -function runImport() { - console.log('Starting import process...'); - - const importForm = document.getElementById('igny8-import-form'); - const importFile = document.getElementById('import-file'); - const importType = document.getElementById('import-type'); - const autoCluster = document.getElementById('auto-cluster-import'); - const resultsDiv = document.getElementById('import-results'); - const submitBtn = importForm.querySelector('button[type="submit"]'); - - console.log('Import form elements found:', { - importForm: !!importForm, - importFile: !!importFile, - importType: !!importType, - autoCluster: !!autoCluster, - resultsDiv: !!resultsDiv, - submitBtn: !!submitBtn - }); - - if (!importFile.files.length) { - console.log('No file selected'); - igny8GlobalNotification('Please select a CSV file to import', 'error'); - return false; - } - - if (!importType.value) { - console.log('No import type selected'); - igny8GlobalNotification('Please select an import type', 'error'); - return false; - } - - console.log('Import validation passed, submitting form...'); - igny8GlobalNotification('Starting import process...', 'info'); - - // Let the form submit naturally - return true; - -} - -// Run CSV export -function runExport() { - console.log('Starting export process...'); - - const exportForm = document.getElementById('igny8-export-form'); - const exportType = document.getElementById('export-type'); - const includeMetrics = document.getElementById('include-metrics'); - const includeRelationships = document.getElementById('include-relationships'); - const includeTimestamps = document.getElementById('include-timestamps'); - const submitBtn = exportForm.querySelector('button[type="submit"]'); - - console.log('Export form elements found:', { - exportForm: !!exportForm, - exportType: !!exportType, - includeMetrics: !!includeMetrics, - includeRelationships: !!includeRelationships, - includeTimestamps: !!includeTimestamps, - submitBtn: !!submitBtn - }); - - if (!exportType.value) { - console.log('No export type selected'); - igny8GlobalNotification('Please select an export type', 'error'); - return false; - } - - console.log('Export validation passed, submitting form...'); - igny8GlobalNotification('Starting export process...', 'info'); - - // Let the form submit naturally - return true; -} - -// Save import/export settings -function saveImportExportSettings() { - console.log('Saving import/export settings...'); - igny8GlobalNotification('Saving settings...', 'info'); - - // Let the form submit naturally - return true; -} - -// Display import results -function displayImportResults(data, resultsDiv, success = true) { - if (!resultsDiv) return; - - let html = ''; - - html += ''; - - resultsDiv.innerHTML = html; - resultsDiv.style.display = 'block'; -} - -// Display export results -function displayExportResults(exportType, success = true) { - const resultsDiv = document.getElementById('export-results'); - if (!resultsDiv) return; - - let html = 'Import Results
'; - html += `Status: ${data.message}
`; - - if (data.imported !== undefined) { - html += `Imported: ${data.imported} records
`; - } - - if (data.skipped !== undefined) { - html += `Skipped: ${data.skipped} records
`; - } - - if (data.details) { - html += `Details: ${data.details}
`; - } - - html += ''; - - html += ''; - - resultsDiv.innerHTML = html; - resultsDiv.style.display = 'block'; -} - -// =================================================================== -// IMPORT/EXPORT MODAL FUNCTIONALITY -// =================================================================== - -/** - * Show Import Modal - * - * @param {string} tableId The table ID for configuration - */ -function igny8ShowImportModal(tableId) { - // Remove existing modal if present - const existingModal = document.getElementById('igny8-import-export-modal'); - if (existingModal) { - existingModal.remove(); - } - - // Call PHP function to get modal HTML - const formData = new FormData(); - formData.append('action', 'igny8_get_import_modal'); - formData.append('nonce', window.igny8_ajax?.nonce || ''); - formData.append('table_id', tableId); - - fetch(window.igny8_ajax?.ajax_url || '/wp-admin/admin-ajax.php', { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(result => { - if (result.success) { - document.body.insertAdjacentHTML('beforeend', result.data); - const modal = document.getElementById('igny8-import-export-modal'); - - // Set the nonce in the form after modal is created - const nonceInput = modal.querySelector('input[name="nonce"]'); - if (nonceInput && window.igny8_ajax?.nonce) { - nonceInput.value = window.igny8_ajax.nonce; - } - - modal.classList.add('open'); - } else { - igny8ShowNotification('Failed to load import modal', 'error'); - } - }) - .catch(error => { - igny8ShowNotification('Error loading import modal', 'error'); - }); -} - -/** - * Show Export Modal - * - * @param {string} tableId The table ID for configuration - * @param {Array} selectedIds Array of selected row IDs (for export selected) - */ -function igny8ShowExportModal(tableId, selectedIds = []) { - // Remove existing modal if present - const existingModal = document.getElementById('igny8-import-export-modal'); - if (existingModal) { - existingModal.remove(); - } - - // Call PHP function to get modal HTML - const formData = new FormData(); - formData.append('action', 'igny8_get_export_modal'); - formData.append('nonce', window.igny8_ajax?.nonce || ''); - formData.append('table_id', tableId); - formData.append('selected_ids', JSON.stringify(selectedIds)); - - fetch(window.igny8_ajax?.ajax_url || '/wp-admin/admin-ajax.php', { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(result => { - if (result.success) { - document.body.insertAdjacentHTML('beforeend', result.data); - const modal = document.getElementById('igny8-import-export-modal'); - - // Set the nonce in the form after modal is created - const nonceInput = modal.querySelector('input[name="nonce"]'); - if (nonceInput && window.igny8_ajax?.nonce) { - nonceInput.value = window.igny8_ajax.nonce; - } - - modal.classList.add('open'); - } else { - igny8ShowNotification('Failed to load export modal', 'error'); - } - }) - .catch(error => { - igny8ShowNotification('Error loading export modal', 'error'); - }); -} - -/** - * Close Import/Export Modal - */ -function igny8CloseImportExportModal() { - const modal = document.getElementById('igny8-import-export-modal'); - if (modal) { - modal.remove(); - } -} - - -/** - * Submit Import Form - */ -async function igny8SubmitImportForm() { - const form = document.getElementById('igny8-modal-import-form'); - const fileInput = document.getElementById('import-file'); - - if (!fileInput.files.length) { - igny8ShowNotification('Please select a CSV file', 'error'); - return; - } - - const formData = new FormData(form); - formData.append('import_file', fileInput.files[0]); - - // Debug logging - console.log('Igny8 Import Debug - Nonce being sent:', window.igny8_ajax?.nonce); - console.log('Igny8 Import Debug - AJAX URL:', window.igny8_ajax?.ajax_url); - console.log('Igny8 Import Debug - Form data:', Object.fromEntries(formData.entries())); - - try { - const response = await fetch(window.igny8_ajax?.ajax_url || '/wp-admin/admin-ajax.php', { - method: 'POST', - body: formData - }); - - const result = await response.json(); - - if (result.success) { - igny8ShowNotification(result.data.message || 'Import completed successfully', 'success'); - igny8CloseImportExportModal(); - - // Get current table ID from the modal context or page - let currentTableId = window.igny8_current_table_id; - - // If not set globally, try to get from the modal - if (!currentTableId) { - const modal = document.getElementById('igny8-import-export-modal'); - if (modal) { - // Try to extract table ID from modal data attributes or other context - currentTableId = modal.dataset.tableId || 'planner_keywords'; - } else { - currentTableId = 'planner_keywords'; // fallback - } - } - - // Reload table data - if (typeof loadTableData === 'function') { - loadTableData(currentTableId, {}, 1); - } else if (typeof igny8LoadTableData === 'function') { - igny8LoadTableData(currentTableId, {}, 1); - } else { - // Fallback: reload the page - location.reload(); - } - } else { - const errorMessage = typeof result.data === 'object' ? - (result.data.message || JSON.stringify(result.data)) : - (result.data || 'Import failed'); - igny8ShowNotification(errorMessage, 'error'); - } - } catch (error) { - igny8ShowNotification('Import failed due to server error', 'error'); - } -} - -/** - * Submit Export Form - */ -async function igny8SubmitExportForm() { - const form = document.getElementById('igny8-modal-export-form'); - const includeMetrics = document.getElementById('include-metrics')?.checked || false; - const includeRelationships = document.getElementById('include-relationships')?.checked || false; - const includeTimestamps = document.getElementById('include-timestamps')?.checked || false; - - const formData = new FormData(form); - formData.append('include_metrics', includeMetrics ? '1' : '0'); - formData.append('include_relationships', includeRelationships ? '1' : '0'); - formData.append('include_timestamps', includeTimestamps ? '1' : '0'); - - // Debug logging - console.log('Igny8 Export Debug - Form data:', Object.fromEntries(formData.entries())); - console.log('Igny8 Export Debug - Action:', formData.get('action')); - console.log('Igny8 Export Debug - Export type:', formData.get('export_type')); - console.log('Igny8 Export Debug - Selected IDs:', formData.get('selected_ids')); - - try { - const response = await fetch(window.igny8_ajax?.ajax_url || '/wp-admin/admin-ajax.php', { - method: 'POST', - body: formData - }); - - const result = await response.json(); - - if (result.success) { - // Download the CSV file - const blob = new Blob([result.data.csv_content], { type: 'text/csv' }); - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = result.data.filename || 'export.csv'; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - window.URL.revokeObjectURL(url); - - igny8ShowNotification(`Exported ${result.data.count} records successfully`, 'success'); - igny8CloseImportExportModal(); - } else { - const errorMessage = typeof result.data === 'object' ? - (result.data.message || JSON.stringify(result.data)) : - (result.data || 'Export failed'); - igny8ShowNotification(errorMessage, 'error'); - } - } catch (error) { - igny8ShowNotification('Export failed due to server error', 'error'); - } -} - -/** - * Download Template - */ -function igny8DownloadTemplate(tableId) { - // Create form for template download - const form = document.createElement('form'); - form.method = 'POST'; - form.action = window.igny8_ajax?.ajax_url || '/wp-admin/admin-ajax.php'; - form.style.display = 'none'; - - const actionInput = document.createElement('input'); - actionInput.type = 'hidden'; - actionInput.name = 'action'; - actionInput.value = 'igny8_download_template'; - form.appendChild(actionInput); - - const nonceInput = document.createElement('input'); - nonceInput.type = 'hidden'; - nonceInput.name = 'nonce'; - nonceInput.value = window.igny8_ajax?.nonce || ''; - form.appendChild(nonceInput); - - const typeInput = document.createElement('input'); - typeInput.type = 'hidden'; - typeInput.name = 'table_id'; - typeInput.value = tableId; - form.appendChild(typeInput); - - document.body.appendChild(form); - form.submit(); - document.body.removeChild(form); - - igny8ShowNotification('Template downloaded', 'success'); -} - - -// =================================================================== -// PROGRESS MODAL SYSTEM -// =================================================================== - -// Global progress modal instance -let currentProgressModal = null; - -// Show progress modal for AI operations -function showProgressModal(title, totalItems, itemType = 'items') { - // Remove existing modal if present - if (currentProgressModal) { - currentProgressModal.remove(); - } - - currentProgressModal = document.createElement('div'); - currentProgressModal.id = 'igny8-progress-modal'; - currentProgressModal.className = 'igny8-modal'; - currentProgressModal.setAttribute('data-item-type', itemType); - currentProgressModal.setAttribute('data-total', totalItems); - currentProgressModal.innerHTML = ` - - `; - - document.body.appendChild(currentProgressModal); - currentProgressModal.classList.add('open'); - - return currentProgressModal; -} - -// Update progress modal with live stats -function updateProgressModal(current, total, status = 'processing', currentItem = '') { - if (!currentProgressModal) return; - - const itemType = currentProgressModal.getAttribute('data-item-type') || 'items'; - const progressText = currentProgressModal.querySelector('#progress-text'); - const progressSubtext = currentProgressModal.querySelector('#progress-subtext'); - const progressBar = currentProgressModal.querySelector('#progress-bar'); - const progressPercentage = currentProgressModal.querySelector('#progress-percentage'); - const completedCount = currentProgressModal.querySelector('#completed-count'); - const processingCount = currentProgressModal.querySelector('#processing-count'); - const remainingCount = currentProgressModal.querySelector('#remaining-count'); - const progressIcon = currentProgressModal.querySelector('#progress-icon'); - - const percentage = Math.round((current / total) * 100); - const remaining = Math.max(0, total - current); - - // Update main text - if (progressText) { - if (status === 'completed') { - progressText.textContent = `β Completed ${current} of ${total} ${itemType}`; - if (progressIcon) progressIcon.textContent = 'β '; - } else { - progressText.textContent = `Processing ${current} of ${total} ${itemType}`; - } - } - - // Update subtext - if (progressSubtext) { - if (currentItem) { - progressSubtext.textContent = `Current: ${currentItem}`; - } else if (status === 'completed') { - progressSubtext.textContent = `All ${itemType} processed successfully!`; - } else { - progressSubtext.textContent = `Working on ${itemType}...`; - } - } - - // Update progress bar - if (progressBar) { - progressBar.style.width = percentage + '%'; - } - - if (progressPercentage) { - progressPercentage.textContent = percentage + '%'; - } - - // Update stats - if (completedCount) completedCount.textContent = current; - if (processingCount) processingCount.textContent = status === 'processing' ? '1' : '0'; - if (remainingCount) remainingCount.textContent = remaining; -} - -// Show success modal -function showSuccessModal(title, completedCount, message = '') { - // Remove progress modal - if (currentProgressModal) { - currentProgressModal.remove(); - currentProgressModal = null; - } - - const modal = document.createElement('div'); - modal.id = 'igny8-success-modal'; - modal.className = 'igny8-modal'; - modal.innerHTML = ` - - `; - - document.body.appendChild(modal); - modal.classList.add('open'); -} - -// Close success modal -function closeSuccessModal() { - const modal = document.getElementById('igny8-success-modal'); - if (modal) { - modal.classList.remove('open'); - setTimeout(() => modal.remove(), 300); - } -} - -// =================================================================== -// SECTOR SELECTION ENFORCEMENT -// =================================================================== - -// Check sector selection before clustering -function checkSectorSelectionBeforeClustering(keywordIds) { - // Use existing AJAX call to get sector options - const formData = new FormData(); - formData.append('action', 'igny8_get_saved_sector_selection'); - formData.append('nonce', window.IGNY8_PAGE.nonce); - - fetch(window.IGNY8_PAGE.ajaxUrl, { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - if (data.success && data.data && data.data.children && data.data.children.length > 0) { - // Sector is selected, proceed with clustering - processAIClustering(keywordIds); - } else { - // No sector selected, show modal using existing modal system - showSectorRequiredModal(); - } - }) - .catch(error => { - console.error('Error checking sector selection:', error); - igny8GlobalNotification('Error checking sector selection', 'error'); - }); -} - -// Show sector required modal using existing modal system -function showSectorRequiredModal() { - const modal = document.createElement('div'); - modal.className = 'igny8-modal'; - modal.innerHTML = ` - - `; - - document.body.appendChild(modal); - modal.classList.add('open'); -} - -// Close sector required modal -function closeSectorRequiredModal() { - const modal = document.querySelector('.igny8-modal'); - if (modal) { - modal.classList.remove('open'); - setTimeout(() => modal.remove(), 300); - } -} - -// Go to planner settings -function goToPlannerSettings() { - closeSectorRequiredModal(); - window.location.href = window.location.origin + window.location.pathname + '?page=igny8-planner'; -} - -// =================================================================== -// CRON SCHEDULE SETTINGS MODAL -// =================================================================== - -// REMOVED: showCronScheduleModal() - Now handled by Smart Automation System -function showCronScheduleModal_DEPRECATED() { - const modal = document.createElement('div'); - modal.id = 'igny8-cron-schedule-modal'; - modal.className = 'igny8-modal'; - modal.innerHTML = ` - - `; - - document.body.appendChild(modal); - modal.classList.add('open'); - - // Populate URLs after modal is created to ensure key is available - setTimeout(() => { - const autoClusterUrl = getCronUrl('igny8_auto_cluster_cron'); - const autoIdeasUrl = getCronUrl('igny8_auto_generate_ideas_cron'); - const autoQueueUrl = getCronUrl('igny8_auto_queue_cron'); - - document.getElementById('cron-url-auto-cluster').textContent = autoClusterUrl; - document.getElementById('cron-url-auto-ideas').textContent = autoIdeasUrl; - document.getElementById('cron-url-auto-queue').textContent = autoQueueUrl; - - // Update copy button onclick handlers with actual URLs - const clusterCopyBtn = document.querySelector('#cron-url-auto-cluster').nextElementSibling; - const ideasCopyBtn = document.querySelector('#cron-url-auto-ideas').nextElementSibling; - const queueCopyBtn = document.querySelector('#cron-url-auto-queue').nextElementSibling; - - clusterCopyBtn.onclick = () => copyToClipboard(autoClusterUrl); - ideasCopyBtn.onclick = () => copyToClipboard(autoIdeasUrl); - queueCopyBtn.onclick = () => copyToClipboard(autoQueueUrl); - }, 100); -} - -// Close cron schedule modal -function closeCronScheduleModal() { - const modal = document.getElementById('igny8-cron-schedule-modal'); - if (modal) { - modal.classList.remove('open'); - setTimeout(() => modal.remove(), 300); - } -} - -// Get automation status -function getAutomationStatus(setting) { - const enabled = document.querySelector(`input[name="igny8_${setting}"]`)?.checked; - return enabled ? 'β Enabled' : 'β Disabled'; -} - -// Get cron URL - Updated for wp-load.php endpoint structure (v3.3.0) -function getCronUrl(action) { - const securityKey = getSecurityKey(); - - // Return null if no CRON key (page doesn't need CRON functionality) - if (!securityKey) { - return null; - } - - const baseUrl = window.location.origin; - const wpLoadPath = '/wp-load.php'; - - // Map internal action names to external action names - const actionMap = { - 'igny8_auto_cluster_cron': 'auto_cluster', - 'igny8_auto_generate_ideas_cron': 'auto_ideas', - 'igny8_auto_queue_cron': 'auto_queue', - 'igny8_auto_drafts_cron': 'auto_content', - 'igny8_auto_generate_content_cron': 'auto_content', - 'igny8_auto_publish_drafts_cron': 'auto_publish', - 'igny8_auto_optimizer_cron': 'auto_optimizer', - 'igny8_trigger_recalc': 'auto_recalc' - }; - - const externalAction = actionMap[action] || action; - const fullUrl = `${baseUrl}${wpLoadPath}?import_key=${securityKey}&import_id=igny8_cron&action=${externalAction}`; - - return fullUrl; -} - -// Get security key (retrieve from server) -function getSecurityKey() { - // Get the secure CRON key from server-side localization - const key = window.IGNY8_PAGE?.cronKey; - - // Return null if no CRON key (page doesn't need CRON functionality) - if (!key || key === null) { - return null; - } - - return key; -} - -// Copy to clipboard -function copyToClipboard(text) { - navigator.clipboard.writeText(text).then(() => { - igny8GlobalNotification('URL copied to clipboard', 'success'); - }).catch(() => { - igny8GlobalNotification('Failed to copy URL', 'error'); - }); -} - -// Regenerate CRON key -function regenerateCronKey() { - if (confirm('Are you sure you want to regenerate the CRON key? This will invalidate all existing CRON URLs.')) { - // Show loading state - const button = event.target.closest('button'); - const originalText = button.innerHTML; - button.innerHTML = ' Regenerating...'; - button.disabled = true; - - // Make AJAX request to regenerate key - fetch(window.IGNY8_PAGE.ajaxUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: new URLSearchParams({ - action: 'igny8_regenerate_cron_key', - nonce: window.IGNY8_PAGE.nonce - }) - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - // Update the key in the page data - window.IGNY8_PAGE.cronKey = data.data.new_key; - - // Update display elements - const keyDisplays = document.querySelectorAll('#cron-key-display, #writer-cron-key-display'); - keyDisplays.forEach(display => { - display.textContent = data.data.new_key; - }); - - // Update all CRON URLs in the modal - const urlElements = document.querySelectorAll('.igny8-cron-url code'); - urlElements.forEach(element => { - const currentUrl = element.textContent; - const newUrl = currentUrl.replace(/import_key=[^&]+/, `import_key=${data.data.new_key}`); - element.textContent = newUrl; - }); - - igny8GlobalNotification('CRON key regenerated successfully', 'success'); - } else { - igny8GlobalNotification('Failed to regenerate CRON key: ' + (data.data?.message || 'Unknown error'), 'error'); - } - }) - .catch(error => { - console.error('Error regenerating CRON key:', error); - igny8GlobalNotification('Failed to regenerate CRON key', 'error'); - }) - .finally(() => { - // Restore button state - button.innerHTML = originalText; - button.disabled = false; - }); - } -} - -// REMOVED: showWriterCronScheduleModal() - Now handled by Smart Automation System -function showWriterCronScheduleModal_DEPRECATED() { - const modal = document.createElement('div'); - modal.id = 'igny8-writer-cron-schedule-modal'; - modal.className = 'igny8-modal'; - modal.innerHTML = ` - - `; - - document.body.appendChild(modal); - modal.classList.add('open'); - - // Populate URLs after modal is created to ensure key is available - setTimeout(() => { - const autoContentUrl = getCronUrl('igny8_auto_generate_content_cron'); - const autoPublishUrl = getCronUrl('igny8_auto_publish_drafts_cron'); - - document.getElementById('writer-cron-url-auto-content').textContent = autoContentUrl; - document.getElementById('writer-cron-url-auto-publish').textContent = autoPublishUrl; - - // Update copy button onclick handlers with actual URLs - const contentCopyBtn = document.querySelector('#writer-cron-url-auto-content').nextElementSibling; - const publishCopyBtn = document.querySelector('#writer-cron-url-auto-publish').nextElementSibling; - - contentCopyBtn.onclick = () => copyToClipboard(autoContentUrl); - publishCopyBtn.onclick = () => copyToClipboard(autoPublishUrl); - }, 100); -} - -// Close Writer cron schedule modal -function closeWriterCronScheduleModal() { - const modal = document.getElementById('igny8-writer-cron-schedule-modal'); - if (modal) { - modal.classList.remove('open'); - setTimeout(() => modal.remove(), 300); - } -} - -// Get Writer automation status -function getWriterAutomationStatus(setting) { - const enabled = document.querySelector(`input[name="igny8_${setting}"]`)?.checked; - return enabled ? 'β Enabled' : 'β Disabled'; -} - -// =================================================================== -// SMART AUTOMATION - RUN NOW AJAX FUNCTIONALITY -// =================================================================== - -/** - * Handle Run Now button clicks for cron jobs - */ -function handleRunNowClick(event) { - event.preventDefault(); - - const button = event.target.closest('button[name="manual_run"]'); - if (!button) return; - - const form = button.closest('form'); - const hook = form.querySelector('input[name="hook"]').value; - const originalText = button.innerHTML; - - // Show loading state - button.disabled = true; - button.innerHTML = ' Running...'; - - // Make AJAX request - jQuery.ajax({ - url: ajaxurl, - type: 'POST', - data: { - action: 'igny8_cron_manual_run', - hook: hook, - nonce: jQuery('#_wpnonce').val() - }, - success: function(response) { - if (response.success) { - // Show success message - igny8GlobalNotification('Job executed successfully: ' + response.data.message, 'success'); - - // Refresh the page after a short delay to show updated status - setTimeout(function() { - location.reload(); - }, 1500); - } else { - igny8GlobalNotification('Error: ' + (response.data || 'Unknown error'), 'error'); - button.disabled = false; - button.innerHTML = originalText; - } - }, - error: function(xhr, status, error) { - igny8GlobalNotification('AJAX Error: ' + error, 'error'); - button.disabled = false; - button.innerHTML = originalText; - } - }); -} - -/** - * Show notification message - */ -function igny8GlobalNotification(message, type) { - const notification = document.createElement('div'); - notification.className = 'notice notice-' + (type === 'success' ? 'success' : 'error') + ' is-dismissible'; - notification.style.position = 'fixed'; - notification.style.top = '32px'; - notification.style.right = '20px'; - notification.style.zIndex = '9999'; - notification.style.maxWidth = '400px'; - notification.innerHTML = 'Export Results
'; - html += `Status: ${success ? 'Export completed successfully' : 'Export failed'}
`; - html += `Type: ${exportType}
`; - html += `File: igny8_export_${exportType}_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.csv
`; - - html += '' + message + '
'; - - document.body.appendChild(notification); - - // Auto-dismiss after 5 seconds - setTimeout(function() { - if (notification.parentNode) { - notification.parentNode.removeChild(notification); - } - }, 5000); - - // Handle manual dismiss - notification.querySelector('.notice-dismiss').addEventListener('click', function() { - if (notification.parentNode) { - notification.parentNode.removeChild(notification); - } - }); -} - -// Cron job click handlers moved to main delegated events handler - -/** - * Handle Run Now icon button click - */ - -/** - * Handle Open in New Window icon button click - */ -function handleOpenInNewWindow(button, hook) { - // Get the security key from the page - let securityKey = ''; - - // Try to find the security key in various locations on the page - const keyInput = document.querySelector('input[name="igny8_secure_cron_key"]'); - if (keyInput) { - securityKey = keyInput.value; - } else { - // Try to find it in a hidden field or data attribute - const keyElement = document.querySelector('[data-cron-key]'); - if (keyElement) { - securityKey = keyElement.getAttribute('data-cron-key'); - } else { - // Try to get it from the page content (if displayed) - const keyDisplay = document.querySelector('.igny8-cron-key-display'); - if (keyDisplay) { - securityKey = keyDisplay.textContent.trim(); - } - } - } - - // If still no key found, show error - if (!securityKey) { - igny8GlobalNotification('Security key not found. Please check the cron settings page.', 'error'); - return; - } - - // Map hook names to action names for the external URL - const actionMap = { - 'igny8_auto_cluster_cron': 'auto_cluster', - 'igny8_auto_generate_ideas_cron': 'auto_ideas', - 'igny8_auto_queue_cron': 'auto_queue', - 'igny8_auto_generate_content_cron': 'auto_content', - 'igny8_auto_generate_images_cron': 'auto_images', - 'igny8_auto_publish_drafts_cron': 'auto_publish', - 'igny8_auto_optimizer_cron': 'auto_optimizer', - 'igny8_auto_recalc_cron': 'auto_recalc', - 'igny8_health_check_cron': 'health_check' - }; - - const action = actionMap[hook] || 'master_scheduler'; - const baseUrl = window.location.origin; - const cronUrl = baseUrl + '/wp-load.php?import_key=' + encodeURIComponent(securityKey) + '&import_id=igny8_cron&action=' + action; - - // Open in new window - window.open(cronUrl, '_blank', 'width=800,height=600,scrollbars=yes,resizable=yes'); -} - -// Add CSS for spin animation -const style = document.createElement('style'); -style.textContent = ` - @keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } - } -`; -document.head.appendChild(style); - -// =================================================================== -// Dynamic Image Size Selector -// =================================================================== - -// Image size options for different providers -const imageSizeOptions = { - openai: [ - { value: '1024x768', label: 'Landscape β 1024 Γ 768', width: 1024, height: 768 }, - { value: '1024x1024', label: 'Square β 1024 Γ 1024', width: 1024, height: 1024 }, - { value: '720x1280', label: 'Social Portrait β 720 Γ 1280', width: 720, height: 1280 } - ], - runware: [ - { value: '1280x832', label: 'Landscape β 1280 Γ 832', width: 1280, height: 832 }, - { value: '1024x1024', label: 'Square β 1024 Γ 1024', width: 1024, height: 1024 }, - { value: '960x1280', label: 'Social Portrait β 960 Γ 1280', width: 960, height: 1280 } - ] -}; - -// Initialize image size selector when page loads -document.addEventListener('DOMContentLoaded', function() { - const providerSelect = document.getElementById('image_provider'); - const sizeSelect = document.getElementById('igny8_image_size_selector'); - const formatSelect = document.getElementById('igny8_image_format_selector'); - const dimensionsDisplay = document.getElementById('igny8-selected-dimensions'); - const formatDisplay = document.getElementById('igny8-selected-format'); - const widthInput = document.getElementById('image_width'); - const heightInput = document.getElementById('image_height'); - - if (providerSelect && sizeSelect && widthInput && heightInput) { - // Function to update size options based on provider - function updateSizeOptions() { - const selectedProvider = providerSelect.value; - const options = imageSizeOptions[selectedProvider] || imageSizeOptions.openai; - - // Clear existing options - sizeSelect.innerHTML = ''; - - // Add new options - options.forEach((option, index) => { - const optionElement = document.createElement('option'); - optionElement.value = option.value; - optionElement.textContent = option.label; - if (index === 0) optionElement.selected = true; // Select first option by default - sizeSelect.appendChild(optionElement); - }); - - // Update dimensions and hidden fields - updateDimensions(); - } - - // Function to update dimensions display and hidden fields - function updateDimensions() { - const selectedSize = sizeSelect.value; - const selectedProvider = providerSelect.value; - const options = imageSizeOptions[selectedProvider] || imageSizeOptions.openai; - const selectedOption = options.find(opt => opt.value === selectedSize); - - if (selectedOption) { - widthInput.value = selectedOption.width; - heightInput.value = selectedOption.height; - - if (dimensionsDisplay) { - dimensionsDisplay.textContent = `Selected: ${selectedOption.width} Γ ${selectedOption.height} (${selectedOption.label.split(' β ')[0]})`; - } - } - } - - // Function to update format display - function updateFormatDisplay() { - if (formatSelect && formatDisplay) { - const selectedFormat = formatSelect.value.toUpperCase(); - formatDisplay.textContent = `Selected format: ${selectedFormat}`; - } - } - - // Event listeners - providerSelect.addEventListener('change', updateSizeOptions); - sizeSelect.addEventListener('change', updateDimensions); - if (formatSelect) { - formatSelect.addEventListener('change', updateFormatDisplay); - } - - // Initialize on page load - updateSizeOptions(); - updateFormatDisplay(); - } -}); - -// =================================================================== -// Test Runware API Connection -// =================================================================== - -// Test Runware API Connection button handler -document.addEventListener('click', function(event) { - if (event.target && event.target.id === 'igny8-test-runware-btn') { - event.preventDefault(); - - const button = event.target; - const resultDiv = document.getElementById('igny8-runware-test-result'); - - // Disable button and show loading state - button.disabled = true; - button.textContent = 'Testing...'; - - // Clear previous results - if (resultDiv) { - resultDiv.innerHTML = ''; - } - - // Make AJAX request - fetch(igny8_ajax.ajax_url, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: new URLSearchParams({ - action: 'igny8_test_runware_connection', - nonce: igny8_ajax.nonce - }) - }) - .then(response => response.json()) - .then(data => { - // Re-enable button - button.disabled = false; - button.textContent = 'Test Runware Connection'; - - // Show result - if (resultDiv) { - if (data.success) { - resultDiv.innerHTML = ''; - } else { - resultDiv.innerHTML = '' + data.data.message + '
'; - } - } - }) - .catch(error => { - // Re-enable button - button.disabled = false; - button.textContent = 'Test Runware Connection'; - - // Show error - if (resultDiv) { - resultDiv.innerHTML = '' + data.data.message + '
'; - } - - console.error('Runware API test error:', error); - }); - } -}); - -// =================================================================== -// DESCRIPTION TOGGLE FUNCTIONALITY -// =================================================================== - -// Handle description toggle clicks -document.addEventListener('click', function(e) { - const toggleBtn = e.target.closest('.igny8-description-toggle'); - if (toggleBtn) { - e.preventDefault(); - e.stopPropagation(); - - const rowId = toggleBtn.dataset.rowId; - const description = toggleBtn.dataset.description; - const tableRow = toggleBtn.closest('tr'); - - // Check if description row already exists - let descriptionRow = document.querySelector(`tr.igny8-description-row[data-parent-id="${rowId}"]`); - - if (descriptionRow && descriptionRow.classList.contains('expanded')) { - // Close existing description row - descriptionRow.classList.remove('expanded'); - setTimeout(() => { - if (!descriptionRow.classList.contains('expanded')) { - descriptionRow.remove(); - } - }, 300); - } else { - // Remove any existing description rows for this table - const existingRows = document.querySelectorAll(`tr.igny8-description-row[data-parent-id="${rowId}"]`); - existingRows.forEach(row => row.remove()); - - // Parse and format description (handle both JSON and plain text) - let formattedDescription = ''; - try { - // Try to parse as JSON first - const descriptionData = JSON.parse(description); - if (descriptionData && typeof descriptionData === 'object') { - formattedDescription = 'β Connection failed: Network error
'; - - // Handle H2 sections if they exist - if (descriptionData.H2 && Array.isArray(descriptionData.H2)) { - descriptionData.H2.forEach((section, index) => { - if (section.heading && section.content_type && section.details) { - formattedDescription += ` -'; - } else { - formattedDescription = '-- `; - } - }); - } else { - // If it's JSON but not the expected format, show as structured data - Object.keys(descriptionData).forEach(key => { - if (descriptionData[key]) { - const label = key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); - formattedDescription += `${section.heading}
---${section.content_type.replace('_', ' ').toUpperCase()}-${section.details}-${label}: ${descriptionData[key]}`; - } - }); - } - formattedDescription += 'Invalid description format'; - } - } catch (error) { - // If JSON parsing fails, treat as plain text - formattedDescription = ` --- `; - } - - // Create new description row - const newRow = document.createElement('tr'); - newRow.className = 'igny8-description-row expanded'; - newRow.setAttribute('data-parent-id', rowId); - - const cellCount = tableRow.cells.length; - newRow.innerHTML = ` -${description}-- ${formattedDescription} - - `; - - // Insert after the current row - tableRow.parentNode.insertBefore(newRow, tableRow.nextSibling); - } - } -}); - -// Handle image prompts toggle clicks -document.addEventListener('click', function(e) { - const toggleBtn = e.target.closest('.igny8-image-prompts-toggle'); - if (toggleBtn) { - e.preventDefault(); - e.stopPropagation(); - - const rowId = toggleBtn.dataset.rowId; - const imagePrompts = toggleBtn.dataset.imagePrompts; - const tableRow = toggleBtn.closest('tr'); - - // Check if image prompts row already exists - let imagePromptsRow = document.querySelector(`tr.igny8-image-prompts-row[data-parent-id="${rowId}"]`); - - if (imagePromptsRow && imagePromptsRow.classList.contains('expanded')) { - // Close existing image prompts row - imagePromptsRow.classList.remove('expanded'); - setTimeout(() => { - if (!imagePromptsRow.classList.contains('expanded')) { - imagePromptsRow.remove(); - } - }, 300); - } else { - // Remove any existing image prompts rows for this table - const existingRows = document.querySelectorAll(`tr.igny8-image-prompts-row[data-parent-id="${rowId}"]`); - existingRows.forEach(row => row.remove()); - - // Parse and format image prompts - let formattedPrompts = ''; - try { - if (!imagePrompts || imagePrompts.trim() === '') { - formattedPrompts = 'No image prompts available'; - } else { - const prompts = JSON.parse(imagePrompts); - if (prompts && typeof prompts === 'object') { - formattedPrompts = ''; - const promptKeys = Object.keys(prompts); - - if (promptKeys.length === 0) { - formattedPrompts += ''; - } else { - formattedPrompts = 'No prompts found in data'; - } else { - promptKeys.forEach(key => { - if (prompts[key] && prompts[key].trim() !== '') { - const label = key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); - formattedPrompts += `${label}: ${prompts[key]}`; - } - }); - } - formattedPrompts += 'Invalid prompts data format'; - } - } - } catch (error) { - console.error('Error parsing image prompts:', error); - formattedPrompts = 'Error parsing image prompts: ' + error.message + ''; - } - - // Create new image prompts row - const newRow = document.createElement('tr'); - newRow.className = 'igny8-image-prompts-row expanded'; - newRow.setAttribute('data-parent-id', rowId); - - const cellCount = tableRow.cells.length; - newRow.innerHTML = ` -- ${formattedPrompts} - - `; - - // Insert after the current row - tableRow.parentNode.insertBefore(newRow, tableRow.nextSibling); - } - } -}); - -// Handle click outside to close description and image prompts rows -document.addEventListener('click', function(e) { - // Check if click is outside any description toggle button - if (!e.target.closest('.igny8-description-toggle') && !e.target.closest('.igny8-description-row')) { - // Close all expanded description rows - const expandedRows = document.querySelectorAll('.igny8-description-row.expanded'); - expandedRows.forEach(row => { - row.classList.remove('expanded'); - setTimeout(() => { - if (!row.classList.contains('expanded')) { - row.remove(); - } - }, 300); - }); - } - - // Check if click is outside any image prompts toggle button - if (!e.target.closest('.igny8-image-prompts-toggle') && !e.target.closest('.igny8-image-prompts-row')) { - // Close all expanded image prompts rows - const expandedImageRows = document.querySelectorAll('.igny8-image-prompts-row.expanded'); - expandedImageRows.forEach(row => { - row.classList.remove('expanded'); - setTimeout(() => { - if (!row.classList.contains('expanded')) { - row.remove(); - } - }, 300); - }); - } -}); - - -// =================================================================== -// END OF UNIFIED JAVASCRIPT -// =================================================================== diff --git a/igny8-wp-plugin-for-reference-olny/assets/js/image-queue-processor.js b/igny8-wp-plugin-for-reference-olny/assets/js/image-queue-processor.js deleted file mode 100644 index d3dae4ce..00000000 --- a/igny8-wp-plugin-for-reference-olny/assets/js/image-queue-processor.js +++ /dev/null @@ -1,436 +0,0 @@ -/** - * Igny8 Image Queue Processor - * Sequential image generation with individual progress tracking - */ - -// Process AI Image Generation for Drafts (Sequential Image Processing with Queue Modal) -function processAIImageGenerationDrafts(postIds) { - console.log('Igny8: processAIImageGenerationDrafts called with postIds:', postIds); - - // Event 1: Generate Images button clicked - if (window.addImageGenDebugLog) { - window.addImageGenDebugLog('INFO', 'Generate Images button clicked', { - postIds: postIds, - timestamp: new Date().toISOString() - }); - } - - // Get image generation settings from saved options (passed via wp_localize_script) - const desktopEnabled = window.IGNY8_PAGE?.imageSettings?.desktop_enabled || false; - const mobileEnabled = window.IGNY8_PAGE?.imageSettings?.mobile_enabled || false; - const maxInArticleImages = window.IGNY8_PAGE?.imageSettings?.max_in_article_images || 1; - - // Event 2: Settings retrieved - if (window.addImageGenDebugLog) { - window.addImageGenDebugLog('SUCCESS', 'Settings retrieved', { - desktop: desktopEnabled, - mobile: mobileEnabled, - maxImages: maxInArticleImages - }); - } - - // Build image queue based on settings - const imageQueue = []; - - postIds.forEach((postId, postIndex) => { - // Featured image (always) - imageQueue.push({ - post_id: postId, - post_number: postIndex + 1, - type: 'featured', - device: '', - label: 'Featured Image', - post_title: `Post ${postIndex + 1}` - }); - - // Desktop in-article images - if (desktopEnabled) { - for (let i = 1; i <= maxInArticleImages; i++) { - imageQueue.push({ - post_id: postId, - post_number: postIndex + 1, - type: 'article', - device: 'desktop', - index: i, - label: `desktop-${i}`, - section: i, - post_title: `Post ${postIndex + 1}` - }); - } - } - - // Mobile in-article images - if (mobileEnabled) { - for (let i = 1; i <= maxInArticleImages; i++) { - imageQueue.push({ - post_id: postId, - post_number: postIndex + 1, - type: 'article', - device: 'mobile', - index: i, - label: `mobile-${i}`, - section: i, - post_title: `Post ${postIndex + 1}` - }); - } - } - }); - - console.log('Igny8: Image queue built:', imageQueue); - - // Show queue modal - showImageQueueModal(imageQueue, imageQueue.length); - - // Start processing queue - processImageQueue(imageQueue, 0); -} - -// Show modal with image queue and individual progress bars -function showImageQueueModal(queue, totalImages) { - if (window.currentProgressModal) { - window.currentProgressModal.remove(); - } - - const modal = document.createElement('div'); - modal.id = 'igny8-image-queue-modal'; - modal.className = 'igny8-modal'; - - let queueHTML = ''; - queue.forEach((item, index) => { - const itemId = `queue-item-${index}`; - queueHTML += ` --- `; - }); - - modal.innerHTML = ` - - - `; - - document.body.appendChild(modal); - modal.classList.add('open'); - window.currentProgressModal = modal; -} - -// Process image queue sequentially with progressive loading -function processImageQueue(queue, currentIndex) { - if (currentIndex >= queue.length) { - // All done - console.log('Igny8: All images processed'); - - // Log to Image Generation Debug - if (window.addImageGenDebugLog) { - window.addImageGenDebugLog('SUCCESS', 'All images processed', { - total: queue.length, - timestamp: new Date().toISOString() - }); - } - - setTimeout(() => { - if (window.currentProgressModal) { - window.currentProgressModal.remove(); - window.currentProgressModal = null; - } - showNotification('Image generation complete!', 'success'); - - // Reload table - if (window.loadTableData && window.IGNY8_PAGE?.tableId) { - window.loadTableData(window.IGNY8_PAGE.tableId); - } - }, 2000); - return; - } - - const item = queue[currentIndex]; - const itemElement = document.getElementById(`queue-item-${currentIndex}`); - - if (!itemElement) { - console.error('Queue item element not found:', currentIndex); - - // Log to Image Generation Debug - if (window.addImageGenDebugLog) { - window.addImageGenDebugLog('ERROR', 'Queue item element not found', { - index: currentIndex, - itemId: `queue-item-${currentIndex}` - }); - } - - setTimeout(() => processImageQueue(queue, currentIndex + 1), 100); - return; - } - - // Update UI to processing - itemElement.setAttribute('data-status', 'processing'); - itemElement.querySelector('.queue-status').textContent = 'β³ Generating...'; - - const progressFill = itemElement.querySelector('.queue-progress-fill'); - const progressText = itemElement.querySelector('.queue-progress-text'); - - // Log to Image Generation Debug - if (window.addImageGenDebugLog) { - window.addImageGenDebugLog('INFO', `Processing ${item.label}`, { - postId: item.post_id, - type: item.type, - device: item.device || 'N/A', - index: item.index || 1, - queuePosition: `${currentIndex + 1}/${queue.length}` - }); - } - - // Progressive loading: 50% in 7s, 75% in next 5s, then 5% every second until 95% - let currentProgress = 0; - let phase = 1; - let phaseStartTime = Date.now(); - - const progressInterval = setInterval(() => { - const elapsed = Date.now() - phaseStartTime; - - if (phase === 1 && currentProgress < 50) { - // Phase 1: 0% to 50% in 7 seconds (7.14% per second) - currentProgress += 0.714; - if (currentProgress >= 50 || elapsed >= 7000) { - currentProgress = 50; - phase = 2; - phaseStartTime = Date.now(); - } - } else if (phase === 2 && currentProgress < 75) { - // Phase 2: 50% to 75% in 5 seconds (5% per second) - currentProgress += 0.5; - if (currentProgress >= 75 || elapsed >= 5000) { - currentProgress = 75; - phase = 3; - phaseStartTime = Date.now(); - } - } else if (phase === 3 && currentProgress < 95) { - // Phase 3: 75% to 95% - 5% every second - if (elapsed >= 1000) { - currentProgress = Math.min(95, currentProgress + 5); - phaseStartTime = Date.now(); - } - } - - progressFill.style.width = currentProgress + '%'; - progressText.textContent = Math.round(currentProgress) + '%'; - }, 100); - - // Generate single image - const formData = new FormData(); - formData.append('action', 'igny8_ai_generate_single_image'); - formData.append('nonce', window.IGNY8_PAGE.nonce); - formData.append('post_id', item.post_id); - formData.append('type', item.type); - formData.append('device', item.device || ''); - formData.append('index', item.index || 1); - // Add meta box integration fields - formData.append('image_label', item.label || ''); - formData.append('section', item.section || ''); - - fetch(window.IGNY8_PAGE.ajaxUrl, { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - // Stop progressive loading - clearInterval(progressInterval); - - if (data.success) { - // Success - complete to 100% - progressFill.style.width = '100%'; - progressText.textContent = '100%'; - itemElement.setAttribute('data-status', 'completed'); - itemElement.querySelector('.queue-status').textContent = 'β Complete'; - - // Display thumbnail if image URL is available - if (data.data?.image_url) { - const thumbnailDiv = itemElement.querySelector('.queue-thumbnail'); - if (thumbnailDiv) { - thumbnailDiv.innerHTML = `---- -- ${index + 1} - ${item.label} - ${item.post_title} - β³ Pending -- - -`; - } - } - - console.log(`β Image ${currentIndex + 1} generated successfully`); - - // Log to Image Generation Debug - if (window.addImageGenDebugLog) { - window.addImageGenDebugLog('SUCCESS', `${item.label} generated successfully`, { - postId: item.post_id, - attachmentId: data.data?.attachment_id, - provider: data.data?.provider, - queuePosition: `${currentIndex + 1}/${queue.length}` - }); - } - - // Process next image after short delay - setTimeout(() => processImageQueue(queue, currentIndex + 1), 500); - } else { - // Error - show at 90% - progressFill.style.width = '90%'; - progressText.textContent = 'Failed'; - itemElement.setAttribute('data-status', 'failed'); - itemElement.querySelector('.queue-status').textContent = 'β Failed'; - - const errorDiv = itemElement.querySelector('.queue-error'); - errorDiv.textContent = data.data?.message || 'Unknown error'; - errorDiv.style.display = 'block'; - - console.error(`β Image ${currentIndex + 1} failed:`, data.data?.message); - - // Log to Image Generation Debug - if (window.addImageGenDebugLog) { - window.addImageGenDebugLog('ERROR', `${item.label} generation failed`, { - postId: item.post_id, - error: data.data?.message || 'Unknown error', - queuePosition: `${currentIndex + 1}/${queue.length}` - }); - } - - // Continue to next image despite error - setTimeout(() => processImageQueue(queue, currentIndex + 1), 1000); - } - }) - .catch(error => { - // Exception - stop progressive loading - clearInterval(progressInterval); - - progressFill.style.width = '90%'; - progressText.textContent = 'Error'; - itemElement.setAttribute('data-status', 'failed'); - itemElement.querySelector('.queue-status').textContent = 'β Error'; - - const errorDiv = itemElement.querySelector('.queue-error'); - errorDiv.textContent = 'Exception: ' + error.message; - errorDiv.style.display = 'block'; - - console.error(`β Image ${currentIndex + 1} exception:`, error); - - // Log to Image Generation Debug - if (window.addImageGenDebugLog) { - window.addImageGenDebugLog('ERROR', `${item.label} request exception`, { - postId: item.post_id, - error: error.message, - queuePosition: `${currentIndex + 1}/${queue.length}` - }); - } - - // Continue to next image despite error - setTimeout(() => processImageQueue(queue, currentIndex + 1), 1000); - }); -} diff --git a/igny8-wp-plugin-for-reference-olny/assets/shortcodes/_README.php b/igny8-wp-plugin-for-reference-olny/assets/shortcodes/_README.php deleted file mode 100644 index f669a0fd..00000000 --- a/igny8-wp-plugin-for-reference-olny/assets/shortcodes/_README.php +++ /dev/null @@ -1,14 +0,0 @@ - ''], $atts); - $post_id = get_the_ID(); - - if (empty($post_id)) { - return ''; - } - - $images = get_post_meta($post_id, '_igny8_inarticle_images', true); - if (!is_array($images)) { - return ''; - } - - // Display specific image by ID - if (!empty($atts['id']) && isset($images[$atts['id']])) { - $image_data = $images[$atts['id']]; - - // Device detection - only show if device matches - $is_mobile = wp_is_mobile(); - $is_desktop = !$is_mobile; - - // Check if image should be displayed based on device - $should_display = false; - if (strpos($atts['id'], 'desktop-') === 0 && $is_desktop) { - $should_display = true; - } elseif (strpos($atts['id'], 'mobile-') === 0 && $is_mobile) { - $should_display = true; - } - - if (!$should_display) { - return ''; - } - - $attachment_id = intval($image_data['attachment_id']); - - if ($attachment_id > 0) { - return wp_get_attachment_image($attachment_id, 'large', false, [ - 'class' => 'igny8-inarticle-image', - 'data-image-id' => esc_attr($atts['id']), - 'data-device' => esc_attr($image_data['device']), - 'alt' => esc_attr($image_data['label']) - ]); - } - } - - return ''; -}); - -/** - * Display all in-article images - * - * Usage: [igny8-images] - * - * @param array $atts Shortcode attributes - * @return string HTML output - */ -add_shortcode('igny8-images', function($atts) { - $atts = shortcode_atts([ - 'device' => '', // Filter by device type (desktop/mobile) - 'size' => 'large', // Image size - 'class' => 'igny8-image-gallery' // CSS class - ], $atts); - - $post_id = get_the_ID(); - - if (empty($post_id)) { - return ''; - } - - $images = get_post_meta($post_id, '_igny8_inarticle_images', true); - if (!is_array($images) || empty($images)) { - return ''; - } - - $output = '
'; - $output .= ''; - - return $output; -}); - -/** - * Display desktop images only - * - * Usage: [igny8-desktop-images] - * - * @param array $atts Shortcode attributes - * @return string HTML output - */ -add_shortcode('igny8-desktop-images', function($atts) { - $atts = shortcode_atts([ - 'size' => 'large', - 'class' => 'igny8-desktop-gallery' - ], $atts); - - return do_shortcode('[igny8-images device="desktop" size="' . $atts['size'] . '" class="' . $atts['class'] . '"]'); -}); - -/** - * Display mobile images only - * - * Usage: [igny8-mobile-images] - * - * @param array $atts Shortcode attributes - * @return string HTML output - */ -add_shortcode('igny8-mobile-images', function($atts) { - $atts = shortcode_atts([ - 'size' => 'large', - 'class' => 'igny8-mobile-gallery' - ], $atts); - - return do_shortcode('[igny8-images device="mobile" size="' . $atts['size'] . '" class="' . $atts['class'] . '"]'); -}); - -/** - * Display image count - * - * Usage: [igny8-image-count] - * - * @param array $atts Shortcode attributes - * @return string HTML output - */ -add_shortcode('igny8-image-count', function($atts) { - $atts = shortcode_atts(['device' => ''], $atts); - - $post_id = get_the_ID(); - - if (empty($post_id)) { - return '0'; - } - - $images = get_post_meta($post_id, '_igny8_inarticle_images', true); - if (!is_array($images)) { - return '0'; - } - - if (!empty($atts['device'])) { - $count = 0; - foreach ($images as $image_data) { - if ($image_data['device'] === $atts['device']) { - $count++; - } - } - return (string) $count; - } - - return (string) count($images); -}); - -/** - * Display image gallery with responsive design - * - * Usage: [igny8-responsive-gallery] - * - * @param array $atts Shortcode attributes - * @return string HTML output - */ -add_shortcode('igny8-responsive-gallery', function($atts) { - $atts = shortcode_atts([ - 'desktop_size' => 'large', - 'mobile_size' => 'medium', - 'class' => 'igny8-responsive-gallery' - ], $atts); - - $post_id = get_the_ID(); - - if (empty($post_id)) { - return ''; - } - - $images = get_post_meta($post_id, '_igny8_inarticle_images', true); - if (!is_array($images) || empty($images)) { - return ''; - } - - $output = 'This is coming from shortcode
'; - - foreach ($images as $label => $image_data) { - // Filter by device if specified - if (!empty($atts['device']) && $image_data['device'] !== $atts['device']) { - continue; - } - - $attachment_id = intval($image_data['attachment_id']); - - if ($attachment_id > 0) { - $output .= wp_get_attachment_image($attachment_id, $atts['size'], false, [ - 'class' => 'igny8-inarticle-image', - 'data-image-id' => esc_attr($label), - 'data-device' => esc_attr($image_data['device']), - 'alt' => esc_attr($image_data['label']) - ]); - } - } - - $output .= ''; - - // Desktop images - $desktop_images = array_filter($images, function($img) { - return $img['device'] === 'desktop'; - }); - - if (!empty($desktop_images)) { - $output .= ''; - - // Add responsive CSS - $output .= ''; - - return $output; -}); diff --git a/igny8-wp-plugin-for-reference-olny/assets/templates/igny8_clusters_template.csv b/igny8-wp-plugin-for-reference-olny/assets/templates/igny8_clusters_template.csv deleted file mode 100644 index 5dbfffc3..00000000 --- a/igny8-wp-plugin-for-reference-olny/assets/templates/igny8_clusters_template.csv +++ /dev/null @@ -1,4 +0,0 @@ -cluster_name,sector_id,status,keyword_count,total_volume,avg_difficulty,mapped_pages_count -"Car Interior Accessories",1,"active",25,45000,42,0 -"Car Storage Solutions",1,"active",18,32000,38,0 -"Car Beverage Holders",1,"active",12,18000,35,0 diff --git a/igny8-wp-plugin-for-reference-olny/assets/templates/igny8_ideas_template.csv b/igny8-wp-plugin-for-reference-olny/assets/templates/igny8_ideas_template.csv deleted file mode 100644 index a165a451..00000000 --- a/igny8-wp-plugin-for-reference-olny/assets/templates/igny8_ideas_template.csv +++ /dev/null @@ -1,4 +0,0 @@ -idea_title,idea_description,content_structure,content_type,keyword_cluster_id,target_keywords,status,estimated_word_count -"Top 10 Car Interior Accessories for 2024","A comprehensive list of the best car interior accessories available this year, including reviews and recommendations.","review","post",1,"car accessories, car storage solutions, car interior accessories","new",1200 -"How to Organize Your Car Interior Like a Pro","Step-by-step guide to organizing your car interior for maximum efficiency and comfort.","guide_tutorial","post",2,"car organization, car storage tips, car interior organization","new",1500 -"DIY Car Storage Solutions That Actually Work","Creative and practical DIY storage solutions you can make at home for your car.","guide_tutorial","post",2,"DIY car storage, car storage solutions, car organization tips","new",800 diff --git a/igny8-wp-plugin-for-reference-olny/assets/templates/igny8_keywords_template.csv b/igny8-wp-plugin-for-reference-olny/assets/templates/igny8_keywords_template.csv deleted file mode 100644 index 7a5b750d..00000000 --- a/igny8-wp-plugin-for-reference-olny/assets/templates/igny8_keywords_template.csv +++ /dev/null @@ -1,4 +0,0 @@ -keyword,search_volume,difficulty,cpc,intent,status,sector_id,cluster_id -"car accessories",12000,45,2.50,"commercial","unmapped",1,0 -"car storage solutions",8500,38,1.80,"informational","unmapped",1,0 -"car interior accessories",15000,52,3.20,"commercial","unmapped",1,0 diff --git a/igny8-wp-plugin-for-reference-olny/core/_README.php b/igny8-wp-plugin-for-reference-olny/core/_README.php deleted file mode 100644 index 313ed5a2..00000000 --- a/igny8-wp-plugin-for-reference-olny/core/_README.php +++ /dev/null @@ -1,14 +0,0 @@ - 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 . "'; - foreach ($desktop_images as $label => $image_data) { - $attachment_id = intval($image_data['attachment_id']); - if ($attachment_id > 0) { - $output .= wp_get_attachment_image($attachment_id, $atts['desktop_size'], false, [ - 'class' => 'igny8-desktop-image', - 'data-image-id' => esc_attr($label) - ]); - } - } - $output .= ''; - } - - // Mobile images - $mobile_images = array_filter($images, function($img) { - return $img['device'] === 'mobile'; - }); - - if (!empty($mobile_images)) { - $output .= ''; - } - - $output .= '
"; - 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 .= "- "; - } - } else { - $html = '" . esc_html($log->created_at) . " -{$status_icon} " . esc_html($log->status) . " -" . esc_html($context['model'] ?? 'Unknown') . " -" . intval($context['input_tokens'] ?? 0) . " / " . intval($context['output_tokens'] ?? 0) . " -" . igny8_format_cost($context['total_cost'] ?? 0) . " -" . esc_html($log->api_id ? substr($log->api_id, 0, 12) . '...' : 'N/A') . " -'; - } - - wp_send_json_success(['html' => $html, 'total' => $total]); -} - -/** - * API Logs - Clear API request logs - */ -add_action('wp_ajax_igny8_clear_api_logs', 'igny8_ajax_clear_api_logs'); -function igny8_ajax_clear_api_logs() { - // Check user permissions - if (!current_user_can('manage_options')) { - wp_send_json_error(['message' => 'Insufficient permissions']); - } - - global $wpdb; - $result = $wpdb->delete( - $wpdb->prefix . 'igny8_logs', - ['source' => 'openai_api'], - ['%s'] - ); - - if ($result !== false) { - wp_send_json_success(['message' => 'API logs cleared successfully']); - } else { - wp_send_json_error(['message' => 'Failed to clear API logs']); - } -} - -/** - * Image Logs - Get image request logs - */ -add_action('wp_ajax_igny8_get_image_logs', 'igny8_ajax_get_image_logs'); -function igny8_ajax_get_image_logs() { - // Check user permissions - if (!current_user_can('manage_options')) { - wp_send_json_error(['message' => 'Insufficient permissions']); - } - - global $wpdb; - $logs = $wpdb->get_results(" - SELECT * FROM {$wpdb->prefix}igny8_logs - WHERE source = 'openai_image' - ORDER BY created_at DESC - LIMIT 20 - "); - - $html = ''; - $total = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_logs WHERE source = 'openai_image'"); - - if ($logs) { - foreach ($logs as $log) { - $context = json_decode($log->context, true); - $status_class = $log->status === 'success' ? 'success' : 'error'; - $status_icon = $log->status === 'success' ? 'β ' : 'β'; - - $html .= " No API logs found. - "; - } - } else { - $html = '" . esc_html($log->created_at) . " -{$status_icon} " . esc_html($log->status) . " -" . esc_html($context['model'] ?? 'dall-e-3') . " -" . intval($context['prompt_length'] ?? 0) . " chars -" . igny8_format_cost($context['total_cost'] ?? 0) . " -" . esc_html($context['image_size'] ?? '1024x1024') . " -" . esc_html($log->api_id ? substr($log->api_id, 0, 12) . '...' : 'N/A') . " -'; - } - - wp_send_json_success(['html' => $html, 'total' => $total]); -} - -/** - * Image Logs - Clear image request logs - */ -add_action('wp_ajax_igny8_clear_image_logs', 'igny8_ajax_clear_image_logs'); -function igny8_ajax_clear_image_logs() { - // Check user permissions - if (!current_user_can('manage_options')) { - wp_send_json_error(['message' => 'Insufficient permissions']); - } - - global $wpdb; - $result = $wpdb->delete( - $wpdb->prefix . 'igny8_logs', - ['source' => 'openai_image'], - ['%s'] - ); - - if ($result !== false) { - wp_send_json_success(['message' => 'Image logs cleared successfully']); - } else { - wp_send_json_error(['message' => 'Failed to clear image logs']); - } -} - -// =================================================================== -// CRON HEALTH AJAX HANDLERS -// =================================================================== - - -/** - * Manual Cron Run AJAX handler - */ -add_action('wp_ajax_igny8_cron_manual_run', 'igny8_ajax_cron_manual_run'); -function igny8_ajax_cron_manual_run() { - if (!current_user_can('manage_options')) { - wp_send_json_error('Insufficient permissions'); - } - - $hook = sanitize_text_field($_POST['hook'] ?? ''); - - // Load master dispatcher functions - if (!function_exists('igny8_get_defined_cron_jobs')) { - include_once plugin_dir_path(__FILE__) . '../cron/igny8-cron-master-dispatcher.php'; - } - - $defined_jobs = igny8_get_defined_cron_jobs(); - - if (!isset($defined_jobs[$hook])) { - wp_send_json_error('Invalid cron job'); - } - - // Run the cron job manually with timing - $start_time = microtime(true); - do_action($hook); - $execution_time = microtime(true) - $start_time; - - // Update health status - $current_time = current_time('timestamp'); - $job_health = [ - 'last_run' => $current_time, - 'success' => true, - 'execution_time' => round($execution_time, 2), - 'error_message' => '', - 'next_run' => igny8_calculate_next_run_time($current_time, 'daily', 0) - ]; - update_option('igny8_cron_health_' . $hook, $job_health); - - // Update last run in settings - $cron_settings = get_option('igny8_cron_settings', []); - $cron_settings[$hook]['last_run'] = $current_time; - update_option('igny8_cron_settings', $cron_settings); - - wp_send_json_success([ - 'message' => "Cron job $hook executed successfully in " . round($execution_time, 2) . "s", - 'execution_time' => round($execution_time, 2) - ]); -} - -/** - * Clear Cron Locks AJAX handler - */ -add_action('wp_ajax_igny8_clear_cron_locks', 'igny8_ajax_clear_cron_locks'); -function igny8_ajax_clear_cron_locks() { - if (!current_user_can('manage_options')) { - wp_send_json_error('Insufficient permissions'); - } - - if (!wp_verify_nonce($_POST['nonce'], 'igny8_clear_locks')) { - wp_send_json_error('Invalid nonce'); - } - - // Clear all execution locks - global $wpdb; - $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_igny8_%_processing'"); - $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_igny8_%_processing'"); - - wp_send_json_success(['message' => 'All execution locks cleared successfully']); -} - -/** - * Test Runware API Connection AJAX handler - */ -function igny8_ajax_test_runware_connection() { - // Security checks - if (!current_user_can('manage_options')) { - wp_send_json_error('Insufficient permissions'); - } - - if (!wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce')) { - wp_send_json_error('Security check failed'); - } - - // Get Runware API key - $api_key = get_option('igny8_runware_api_key', ''); - - if (empty($api_key)) { - wp_send_json_error(['message' => 'Missing API key.']); - } - - // Prepare payload as specified - $payload = [ - [ - 'taskType' => 'authentication', - 'apiKey' => $api_key - ], - [ - 'taskType' => 'imageInference', - 'taskUUID' => wp_generate_uuid4(), - 'positivePrompt' => 'test image connection', - 'model' => 'runware:97@1', - 'width' => 128, - 'height' => 128, - 'negativePrompt' => 'text, watermark, logo, overlay, title, caption, writing on walls, writing on objects, UI, infographic elements, post title', - 'steps' => 2, - 'CFGScale' => 5, - 'numberResults' => 1 - ] - ]; - - // Make API request - $response = wp_remote_post('https://api.runware.ai/v1', [ - 'headers' => ['Content-Type' => 'application/json'], - 'body' => json_encode($payload), - 'timeout' => 30 - ]); - - if (is_wp_error($response)) { - wp_send_json_error(['message' => $response->get_error_message()]); - } - - $body = json_decode(wp_remote_retrieve_body($response), true); - - if (isset($body['data'][0]['imageURL'])) { - wp_send_json_success(['message' => 'β Runware API connected successfully!']); - } elseif (isset($body['errors'][0]['message'])) { - wp_send_json_error(['message' => 'β ' . $body['errors'][0]['message']]); - } else { - wp_send_json_error(['message' => 'β Unknown response from Runware.']); - } -} - -/** - * Save Image Generation Settings - AJAX handler for saving image generation settings - */ -add_action('wp_ajax_igny8_save_image_settings', 'igny8_ajax_save_image_settings'); -function igny8_ajax_save_image_settings() { - // Debug: Log all received data - error_log('Image settings AJAX called with data: ' . print_r($_POST, true)); - - // Verify nonce - if (!isset($_POST['generate_image_nonce'])) { - error_log('Image settings: Missing generate_image_nonce'); - wp_send_json_error('Missing security token'); - return; - } - - if (!wp_verify_nonce($_POST['generate_image_nonce'], 'generate_image')) { - error_log('Image settings: Nonce verification failed'); - wp_send_json_error('Security check failed'); - return; - } - - // Check user capabilities - if (!current_user_can('manage_options')) { - error_log('Image settings: Insufficient permissions'); - wp_send_json_error('Insufficient permissions'); - return; - } - - // Get and sanitize form data - $image_type = sanitize_text_field($_POST['image_type'] ?? 'realistic'); - $image_provider = sanitize_text_field($_POST['image_provider'] ?? 'openai'); - $desktop_enabled = sanitize_text_field($_POST['desktop_enabled'] ?? '0'); - $mobile_enabled = sanitize_text_field($_POST['mobile_enabled'] ?? '0'); - $max_in_article_images = intval($_POST['max_in_article_images'] ?? 1); - $image_format = sanitize_text_field($_POST['image_format'] ?? 'jpg'); - $negative_prompt = sanitize_textarea_field($_POST['negative_prompt'] ?? ''); - - error_log('Image settings: Saving data - Type: ' . $image_type . ', Provider: ' . $image_provider . ', Desktop: ' . $desktop_enabled . ', Mobile: ' . $mobile_enabled . ', Max In-Article: ' . $max_in_article_images . ', Format: ' . $image_format); - - // Save image generation settings - update_option('igny8_image_type', $image_type); - update_option('igny8_image_service', $image_provider); - update_option('igny8_desktop_enabled', $desktop_enabled); - update_option('igny8_mobile_enabled', $mobile_enabled); - update_option('igny8_max_in_article_images', $max_in_article_images); - update_option('igny8_image_format', $image_format); - update_option('igny8_negative_prompt', $negative_prompt); - - error_log('Image settings: Successfully saved all options'); - - wp_send_json_success('Image settings saved successfully'); -} - -/** - * Save Image Prompt Template - AJAX handler for saving image prompt templates - */ -add_action('wp_ajax_igny8_save_image_prompt_template', 'igny8_ajax_save_image_prompt_template'); -function igny8_ajax_save_image_prompt_template() { - // Debug: Log all received data - error_log('Prompt template AJAX called with data: ' . print_r($_POST, true)); - - // Verify nonce - if (!isset($_POST['save_prompt_nonce'])) { - error_log('Prompt template: Missing save_prompt_nonce'); - wp_send_json_error('Missing security token'); - return; - } - - if (!wp_verify_nonce($_POST['save_prompt_nonce'], 'save_prompt')) { - error_log('Prompt template: Nonce verification failed'); - wp_send_json_error('Security check failed'); - return; - } - - // Check user capabilities - if (!current_user_can('manage_options')) { - error_log('Prompt template: Insufficient permissions'); - wp_send_json_error('Insufficient permissions'); - return; - } - - // Get and sanitize prompt template - $prompt_template = sanitize_textarea_field($_POST['prompt_template'] ?? ''); - $negative_prompt = sanitize_textarea_field($_POST['negative_prompt'] ?? ''); - - if (empty($prompt_template)) { - error_log('Prompt template: Empty prompt template'); - wp_send_json_error('Prompt template is required'); - return; - } - - error_log('Prompt template: Saving template: ' . $prompt_template); - error_log('Prompt template: Saving negative prompt: ' . $negative_prompt); - - // Save image prompt template and negative prompt - update_option('igny8_image_prompt_template', $prompt_template); - update_option('igny8_negative_prompt', $negative_prompt); - - error_log('Prompt template: Successfully saved'); - - wp_send_json_success('Image prompt template saved successfully'); -} - -add_action('wp_ajax_igny8_reset_image_prompt_template', 'igny8_ajax_reset_image_prompt_template'); -function igny8_ajax_reset_image_prompt_template() { - // Verify nonce - if (!wp_verify_nonce($_POST['nonce'], 'igny8_thinker_settings')) { - wp_send_json_error('Security check failed'); - } - - // Check user capabilities - if (!current_user_can('manage_options')) { - wp_send_json_error('Insufficient permissions'); - } - - // Default image prompt template - $default_value = 'Create a high-quality {image_type} image to use as a featured photo for a blog post titled "{post_title}". The image should visually represent the theme, mood, and subject implied by the image prompt: {image_prompt}. Focus on a realistic, well-composed scene that naturally communicates the topic without text or logos. Use balanced lighting, pleasing composition, and photographic detail suitable for lifestyle or editorial web content. Avoid adding any visible or readable text, brand names, or illustrative effects. **And make sure image is not blurry.**'; - - // Reset the prompt template - update_option('igny8_image_prompt_template', $default_value); - - wp_send_json_success([ - 'message' => 'Image prompt template reset to default successfully', - 'prompt_value' => $default_value - ]); -} - -// TEST AJAX handler to verify ajax.php is loaded -add_action('wp_ajax_igny8_test_ajax_connection', 'igny8_ajax_test_connection'); -function igny8_ajax_test_connection() { - // Simple test to verify ajax.php is loaded and working - wp_send_json_success([ - 'message' => 'AJAX connection working! ajax.php is loaded.', - 'timestamp' => current_time('mysql'), - 'server_time' => date('Y-m-d H:i:s'), - 'test_data' => 'This message came from core/admin/ajax.php' - ]); -} - -// Content parsing function (temporary testing code) -function igny8_convert_to_wp_blocks($content) { - // Sanitize: keep only block-level tags - $content = strip_tags($content, ' No image request logs found.
'); - - // 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); -} diff --git a/igny8-wp-plugin-for-reference-olny/core/admin/global-helpers.php b/igny8-wp-plugin-for-reference-olny/core/admin/global-helpers.php deleted file mode 100644 index a3bde6d5..00000000 --- a/igny8-wp-plugin-for-reference-olny/core/admin/global-helpers.php +++ /dev/null @@ -1,1084 +0,0 @@ -get_var($wpdb->prepare( - "SELECT cluster_name FROM {$wpdb->prefix}igny8_clusters WHERE id = %d", - $cluster_id - )); - - if (!empty($cluster_name)) { - return $cluster_name; - } - - // If no cluster found, return empty string - return ''; -} - - -/** - * Convert cluster name to cluster ID - * Used across multiple submodules and forms - * - * @param string $cluster_name The cluster name - * @return int The cluster ID or 0 if not found - */ -function igny8_get_cluster_id_by_name($cluster_name) { - if (empty($cluster_name) || !is_string($cluster_name)) { - return 0; - } - - global $wpdb; - - // Query the clusters table directly - $cluster_id = $wpdb->get_var($wpdb->prepare( - "SELECT id FROM {$wpdb->prefix}igny8_clusters WHERE cluster_name = %s", - $cluster_name - )); - - return $cluster_id ? (int)$cluster_id : 0; -} - -/** - * Get cluster options for dropdowns - * Used across all modules that need cluster selection - * - * @return array Array of cluster options in format [['value' => id, 'label' => name], ...] - */ -function igny8_get_cluster_options() { - global $wpdb; - - // Get all active clusters from database - $clusters = $wpdb->get_results( - "SELECT id, cluster_name FROM {$wpdb->prefix}igny8_clusters WHERE status = 'active' ORDER BY cluster_name ASC" - ); - - $options = [ - ['value' => '', 'label' => 'No Cluster'] - ]; - - if ($clusters) { - foreach ($clusters as $cluster) { - $options[] = [ - 'value' => $cluster->id, - 'label' => $cluster->cluster_name - ]; - } - } - - return $options; -} - -/** - * Get standardized cluster lookup configuration for tables - * This eliminates the need to repeat lookup configuration in every table - * - * @return array Standardized cluster lookup configuration - */ -function igny8_get_cluster_lookup_config() { - return [ - 'type' => 'lookup', - 'source_field' => 'cluster_id', - 'display_field' => 'cluster_name', - 'sortable' => true, - 'join_query' => 'LEFT JOIN {prefix}igny8_clusters c ON {table_name}.cluster_id = c.id', - 'select_field' => 'c.cluster_name as cluster_name' - ]; -} - -/** - * Get standardized cluster lookup configuration for ideas table - * Uses keyword_cluster_id instead of cluster_id - * - * @return array Standardized cluster lookup configuration for ideas - */ -function igny8_get_ideas_cluster_lookup_config() { - return [ - 'type' => 'lookup', - 'source_field' => 'keyword_cluster_id', - 'display_field' => 'cluster_name', - 'sortable' => true, - 'join_query' => 'LEFT JOIN {prefix}igny8_clusters c ON {table_name}.keyword_cluster_id = c.id', - 'select_field' => 'c.cluster_name as cluster_name' - ]; -} - -/** - * Get dynamic table configuration based on AI mode - */ -function igny8_get_dynamic_table_config($table_id) { - $config = $GLOBALS['igny8_tables_config'][$table_id] ?? []; - - // Apply AI-specific modifications - if ($table_id === 'writer_queue') { - $writer_mode = igny8_get_ai_setting('writer_mode', 'manual'); - - // Hide due_date column for AI mode - if ($writer_mode === 'ai' && isset($config['columns']['due_date'])) { - unset($config['columns']['due_date']); - - // Also remove from filters - if (isset($config['filters']['due_date'])) { - unset($config['filters']['due_date']); - } - } - } - - // Apply status filter for drafts table - if ($table_id === 'writer_drafts') { - $config['default_filter'] = [ - 'status' => ['draft'] - ]; - } - - // Apply status filter for published table - if ($table_id === 'writer_published') { - $config['default_filter'] = [ - 'status' => ['published'] - ]; - } - - return $config; -} - -/** - * Get dynamic form configuration based on table columns - */ -function igny8_get_dynamic_form_config($form_id) { - $form_config = $GLOBALS['igny8_forms_config'][$form_id] ?? []; - - // For writer_queue form, conditionally include due_date field - if ($form_id === 'writer_queue') { - $writer_mode = igny8_get_ai_setting('writer_mode', 'manual'); - - // Remove due_date field for AI mode - if ($writer_mode === 'ai') { - $form_config['fields'] = array_filter($form_config['fields'], function($field) { - return $field['name'] !== 'due_date'; - }); - // Re-index array - $form_config['fields'] = array_values($form_config['fields']); - } - } - - return $form_config; -} - -/** - * Convert difficulty number to predefined difficulty range name - * - * @param float $difficulty The difficulty value (0-100) - * @return string The difficulty range name - */ -function igny8_get_difficulty_range_name($difficulty) { - if (empty($difficulty) || !is_numeric($difficulty)) { - return ''; - } - - $difficulty = floatval($difficulty); - - if ($difficulty <= 20) { - return 'Very Easy'; - } elseif ($difficulty <= 40) { - return 'Easy'; - } elseif ($difficulty <= 60) { - return 'Medium'; - } elseif ($difficulty <= 80) { - return 'Hard'; - } else { - return 'Very Hard'; - } -} - -/** - * Convert difficulty text label to numeric range for database queries - * - * @param string $difficulty_label The difficulty text label - * @return array|false Array with 'min' and 'max' keys, or false if invalid - */ -function igny8_get_difficulty_numeric_range($difficulty_label) { - if (empty($difficulty_label)) { - return false; - } - - switch ($difficulty_label) { - case 'Very Easy': - return ['min' => 0, 'max' => 20]; - case 'Easy': - return ['min' => 21, 'max' => 40]; - case 'Medium': - return ['min' => 41, 'max' => 60]; - case 'Hard': - return ['min' => 61, 'max' => 80]; - case 'Very Hard': - return ['min' => 81, 'max' => 100]; - default: - return false; - } -} - -/** - * Get page description - */ -function igny8_get_page_description() { - $current_page = $_GET['page'] ?? ''; - $sm = $_GET['sm'] ?? ''; - - switch ($current_page) { - case 'igny8-home': - return 'Welcome to Igny8 AI SEO OS - Your comprehensive SEO management platform.'; - case 'igny8-planner': - if ($sm === 'keywords') { - return 'Manage your keywords, track search volumes, and organize by intent and difficulty.'; - } elseif ($sm === 'clusters') { - return 'Group related keywords into content clusters for better topical authority.'; - } elseif ($sm === 'ideas') { - return 'Generate and organize content ideas based on your keyword research.'; - } elseif ($sm === 'mapping') { - return 'Map keywords and clusters to existing pages and content.'; - } else { - return 'Plan and organize your content strategy with keyword research and clustering.'; - } - case 'igny8-writer': - if ($sm === 'drafts') { - return 'Manage your content drafts and work in progress.'; - } elseif ($sm === 'templates') { - return 'Create and manage content templates for consistent writing.'; - } else { - return 'Content creation and writing tools for SEO-optimized content.'; - } - case 'igny8-optimizer': - if ($sm === 'audits') { - return 'Run comprehensive SEO audits on your content and pages.'; - } elseif ($sm === 'suggestions') { - return 'Get AI-powered optimization suggestions for better rankings.'; - } else { - return 'SEO optimization and performance tools for better rankings.'; - } - case 'igny8-linker': - if ($sm === 'backlinks') { - return 'Track and manage your backlink profile and authority.'; - } elseif ($sm === 'campaigns') { - return 'Plan and execute link building campaigns effectively.'; - } else { - return 'Link building and backlink management for improved authority.'; - } - case 'igny8-personalize': - if ($sm === 'settings') { - return 'Configure global settings, display options, and advanced personalization settings.'; - } elseif ($sm === 'content-generation') { - return 'Configure AI prompts, field detection, and content generation parameters.'; - } elseif ($sm === 'rewrites') { - return 'View and manage personalized content variations and rewrites.'; - } elseif ($sm === 'front-end') { - return 'Manage front-end display settings, shortcode usage, and implementation guides.'; - } else { - return 'Content personalization and targeting for better engagement.'; - } - case 'igny8-settings': - $sp = $_GET['sp'] ?? 'general'; - if ($sp === 'status') { - return 'Monitor system health, database status, and module performance.'; - } elseif ($sp === 'integration') { - return 'Configure API keys and integrate with external services.'; - } elseif ($sp === 'import-export') { - return 'Import and export data, manage backups, and transfer content.'; - } else { - return 'Configure plugin settings, automation, and table preferences.'; - } - case 'igny8-analytics': - return 'Performance analytics and reporting for data-driven decisions.'; - case 'igny8-schedules': - return 'Content scheduling and automation for consistent publishing.'; - case 'igny8-help': - return 'Documentation and support resources for getting started.'; - default: - return 'Igny8 AI SEO OS - Advanced SEO Management'; - } -} - -/** - * Get page title - */ -function igny8_get_page_title() { - $current_page = $_GET['page'] ?? ''; - $sm = $_GET['sm'] ?? ''; - - switch ($current_page) { - case 'igny8-home': - return 'Igny8 Home'; - case 'igny8-planner': - if ($sm === 'keywords') { - return 'Keywords'; - } elseif ($sm === 'clusters') { - return 'Clusters'; - } elseif ($sm === 'ideas') { - return 'Ideas'; - } elseif ($sm === 'mapping') { - return 'Mapping'; - } else { - return 'Planner'; - } - case 'igny8-writer': - if ($sm === 'tasks') { - return 'Content Queue / Tasks'; - } elseif ($sm === 'drafts') { - return 'Content Generated'; - } elseif ($sm === 'published') { - return 'Live Content'; - } else { - return 'Content Writer'; - } - case 'igny8-optimizer': - if ($sm === 'audits') { - return 'Audits'; - } elseif ($sm === 'suggestions') { - return 'Suggestions'; - } else { - return 'Optimizer'; - } - case 'igny8-linker': - if ($sm === 'backlinks') { - return 'Backlinks'; - } elseif ($sm === 'campaigns') { - return 'Campaigns'; - } else { - return 'Linker'; - } - case 'igny8-personalize': - if ($sm === 'settings') { - return 'Personalization Settings'; - } elseif ($sm === 'content-generation') { - return 'Content Generation'; - } elseif ($sm === 'rewrites') { - return 'Rewrites'; - } elseif ($sm === 'front-end') { - return 'Front-end'; - } else { - return 'Personalize'; - } - case 'igny8-thinker': - $sp = $_GET['sp'] ?? 'main'; - if ($sp === 'main') { - return 'AI Thinker Dashboard'; - } elseif ($sp === 'prompts') { - return 'AI Prompts'; - } elseif ($sp === 'profile') { - return 'AI Profile'; - } elseif ($sp === 'strategies') { - return 'Content Strategies'; - } elseif ($sp === 'image-testing') { - return 'AI Image Testing'; - } else { - return 'AI Thinker'; - } - case 'igny8-settings': - $sp = $_GET['sp'] ?? 'general'; - if ($sp === 'status') { - return 'System Status'; - } elseif ($sp === 'integration') { - return 'API Integration'; - } elseif ($sp === 'import-export') { - return 'Import/Export'; - } else { - return 'General Settings'; - } - case 'igny8-analytics': - return 'Analytics'; - case 'igny8-schedules': - return 'Schedules'; - case 'igny8-test': - $sp = $_GET['sp'] ?? 'system-testing'; - if ($sp === 'system-testing') { - return 'System Testing'; - } elseif ($sp === 'temp-function-testing') { - return 'Function Testing'; - } else { - return 'Test Page'; - } - case 'igny8-help': - return 'Help'; - default: - return 'Igny8 AI SEO OS'; - } -} - - - - - - - - -// REMOVED: Task variations functionality - tasks don't need variations - -/** - * Safe logging helper for Igny8 operations - * - * @param string $type Log type (e.g., 'queue_to_writer', 'task_created') - * @param mixed $data Log data (string, array, or object) - */ -function igny8_write_log($type, $data) { - global $wpdb; - - try { - $wpdb->insert($wpdb->prefix . 'igny8_logs', [ - 'level' => 'info', - 'message' => sanitize_text_field($type), - 'context' => is_array($data) ? wp_json_encode($data) : (is_string($data) ? $data : null), - 'source' => 'igny8_plugin', - 'user_id' => get_current_user_id(), - ], ['%s', '%s', '%s', '%s', '%d']); - } catch (Exception $e) { - // Log to error log if database logging fails - error_log('Igny8: Failed to write to logs table: ' . $e->getMessage()); - } -} - -/** - * Get clusters for select dropdown - * - * @return array Array of cluster options for select dropdown - */ -function igny8_get_clusters_for_select() { - global $wpdb; - - $clusters = $wpdb->get_results( - "SELECT id, cluster_name FROM {$wpdb->prefix}igny8_clusters ORDER BY cluster_name ASC" - ); - - $options = ['' => 'Select Cluster']; - foreach ($clusters as $cluster) { - $options[$cluster->id] = $cluster->cluster_name; - } - - return $options; -} - -/** - * Get sector options for dropdowns - * Used across all modules that need sector selection - * Only returns sectors from saved planner settings - * - * @return array Array of sector options in format [['value' => id, 'label' => name], ...] - */ -function igny8_get_sector_options() { - // Get saved sector selection from user meta - $user_id = get_current_user_id(); - $saved_selection = get_user_meta($user_id, 'igny8_planner_sector_selection', true); - - $options = []; - - // If no saved selection, return empty options - if (empty($saved_selection)) { - return $options; - } - - // Add only child sectors (not parent) - if (isset($saved_selection['children']) && is_array($saved_selection['children'])) { - foreach ($saved_selection['children'] as $child) { - if (isset($child['id']) && isset($child['name'])) { - $options[] = [ - 'value' => $child['id'], - 'label' => $child['name'] - ]; - } - } - } - - return $options; -} - -/** - * Get sectors for select dropdown - * Only returns sectors from saved planner settings - * - * @return array Array of sector options for select dropdown - */ -function igny8_get_sectors_for_select() { - // Get saved sector selection from user meta - $user_id = get_current_user_id(); - $saved_selection = get_user_meta($user_id, 'igny8_planner_sector_selection', true); - - $options = []; - - // If no saved selection, return empty options - if (empty($saved_selection)) { - return $options; - } - - // Add only child sectors (not parent) - if (isset($saved_selection['children']) && is_array($saved_selection['children'])) { - foreach ($saved_selection['children'] as $child) { - if (isset($child['id']) && isset($child['name'])) { - $options[$child['id']] = $child['name']; - } - } - } - - return $options; -} - -/** - * Get ideas for select dropdown - * - * @return array Array of idea options for select dropdown - */ -function igny8_get_ideas_for_select() { - global $wpdb; - - $ideas = $wpdb->get_results( - "SELECT id, idea_title FROM {$wpdb->prefix}igny8_content_ideas ORDER BY idea_title ASC" - ); - - $options = ['' => 'Select Idea']; - foreach ($ideas as $idea) { - $options[$idea->id] = $idea->idea_title; - } - - return $options; -} - -/** - * Transform database field names to human-readable labels - * - * @param string $field_name The database field name (snake_case) - * @return string The human-readable label (Title Case) - */ -function igny8_humanize_label($field) { - // Handle non-string input safely - if (!is_string($field)) { - return ''; - } - - // Remove any potentially dangerous characters - $field = sanitize_key($field); - - // Specific field mappings for better readability - $field_mappings = [ - // Clusters table - 'cluster_name' => 'Cluster Name', - 'sector_id' => 'Sectors', - 'keyword_count' => 'Keywords', - 'total_volume' => 'Total Volume', - 'avg_difficulty' => 'Avg KD', - 'mapped_pages_count' => 'Mapped Pages', - 'created_at' => 'Created', - 'updated_at' => 'Updated', - - // Keywords table - 'search_volume' => 'Volume', - 'difficulty' => 'Difficulty', - 'cpc' => 'CPC', - 'intent' => 'Intent', - 'status' => 'Status', - 'cluster_id' => 'Cluster', - - // Ideas table - 'idea_title' => 'Title', - 'idea_description' => 'Description', - 'content_structure' => 'Structure', - 'content_angle' => 'Content Angle', - 'keyword_cluster_id' => 'Cluster', - 'estimated_word_count' => 'Words', - 'ai_generated' => 'AI Generated', - 'mapped_post_id' => 'Mapped Post', - 'tasks_count' => 'Tasks Count', - - // Tasks table - 'content_type' => 'Content Type', - 'cluster_id' => 'Cluster', - 'keywords' => 'Keywords', - 'schedule_at' => 'Scheduled', - 'assigned_post_id' => 'Assigned Post', - 'created_at' => 'Created', - 'updated_at' => 'Updated', - - // Campaigns table - 'backlink_count' => 'Backlinks', - 'live_links_count' => 'Live Links', - - // Mapping table - 'page_id' => 'Page ID', - 'coverage_percentage' => 'Coverage %', - 'coverage_status' => 'Status', - 'last_updated' => 'Updated' - ]; - - // Check for specific mapping first - if (isset($field_mappings[$field])) { - return $field_mappings[$field]; - } - - // Fallback: convert snake_case to Title Case - return ucwords(str_replace('_', ' ', $field)); -} - -/** - * Transform database field names to human-readable labels (legacy function) - * @deprecated Use igny8_humanize_label() instead - */ -function igny8_transform_field_label($field_name) { - return igny8_humanize_label($field_name); -} - - - - -/** - * Map cluster to keywords - * - * @param int $cluster_id Cluster ID to map keywords to - * @param array $keyword_ids Array of keyword IDs to map - * @return array ['success' => bool, 'message' => string, 'mapped_count' => int] - */ -function igny8_map_cluster_to_keywords($cluster_id, $keyword_ids) { - global $wpdb; - - if (empty($cluster_id) || !is_numeric($cluster_id)) { - return ['success' => false, 'message' => 'Invalid cluster ID provided', 'mapped_count' => 0]; - } - - if (empty($keyword_ids) || !is_array($keyword_ids)) { - return ['success' => false, 'message' => 'No keywords provided for mapping', 'mapped_count' => 0]; - } - - $cluster_id = intval($cluster_id); - - // Verify cluster exists - $cluster_exists = $wpdb->get_var($wpdb->prepare( - "SELECT COUNT(*) FROM {$wpdb->prefix}igny8_clusters WHERE id = %d", - $cluster_id - )); - - if (!$cluster_exists) { - return ['success' => false, 'message' => 'Cluster not found', 'mapped_count' => 0]; - } - - // Sanitize and validate keyword IDs - $keyword_ids = array_map('intval', $keyword_ids); - $keyword_ids = array_filter($keyword_ids, function($id) { return $id > 0; }); - - if (empty($keyword_ids)) { - return ['success' => false, 'message' => 'No valid keyword IDs provided', 'mapped_count' => 0]; - } - - // Get old cluster IDs for metrics update - $placeholders = implode(',', array_fill(0, count($keyword_ids), '%d')); - $old_cluster_ids = $wpdb->get_col($wpdb->prepare( - "SELECT DISTINCT cluster_id FROM {$wpdb->prefix}igny8_keywords WHERE id IN ({$placeholders}) AND cluster_id IS NOT NULL", - $keyword_ids - )); - - // Update keywords to new cluster - $mapped_count = $wpdb->query($wpdb->prepare( - "UPDATE {$wpdb->prefix}igny8_keywords SET cluster_id = %d, status = 'mapped', updated_at = CURRENT_TIMESTAMP WHERE id IN ({$placeholders})", - array_merge([$cluster_id], $keyword_ids) - )); - - if ($mapped_count === false) { - return ['success' => false, 'message' => 'Failed to map keywords to cluster', 'mapped_count' => 0]; - } - - // Update metrics for old clusters (they lost keywords) - if (!empty($old_cluster_ids)) { - foreach (array_unique($old_cluster_ids) as $old_cluster_id) { - if ($old_cluster_id && $old_cluster_id != $cluster_id) { - igny8_update_cluster_metrics($old_cluster_id); - } - } - } - - // Update metrics for new cluster (gained keywords) - igny8_update_cluster_metrics($cluster_id); - - return [ - 'success' => true, - 'message' => "Successfully mapped {$mapped_count} keyword(s) to cluster", - 'mapped_count' => $mapped_count - ]; -} - -/** - * ============================================= - * 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] - */ - -/** - * Check if automation is enabled for the current user - * - * @return bool True if automation is enabled - */ -function igny8_is_automation_enabled() { - // Default ON for admin users, OFF for others - if (current_user_can('manage_options')) { - return true; - } - - // Check if user has specific automation capability - return current_user_can('edit_posts'); -} - -/** - * Helper function to get sector name - * Used for table display to show sector names instead of IDs - * - * @param mixed $sector_id The sector ID - * @return string The sector name or empty string if not found or 0/null - */ -function igny8_get_sector_name($sector_id) { - // Return empty string for 0, null, or empty values - if (empty($sector_id) || !is_numeric($sector_id) || intval($sector_id) == 0) { - return ''; - } - - // Check if sectors taxonomy exists - if (!taxonomy_exists('sectors')) { - return ''; - } - - // Try to get from WordPress taxonomy - $term = get_term(intval($sector_id), 'sectors'); - if ($term && !is_wp_error($term)) { - return $term->name; - } - - // If term not found, return empty string instead of fallback - return ''; -} - -/** - * Generate cluster name from keywords - * - * @param array $keywords Array of keyword objects - * @return string Generated cluster name - */ -function igny8_generate_cluster_name_from_keywords($keywords) { - if (empty($keywords)) { - return 'Auto-Generated Cluster'; - } - - // Get the most common words from keywords - $all_words = []; - foreach ($keywords as $keyword) { - $words = explode(' ', strtolower($keyword->keyword)); - $all_words = array_merge($all_words, $words); - } - - // Count word frequency - $word_count = array_count_values($all_words); - arsort($word_count); - - // Get top 2-3 most common words - $top_words = array_slice(array_keys($word_count), 0, 3); - - if (empty($top_words)) { - return 'Auto-Generated Cluster'; - } - - return ucwords(implode(' ', $top_words)) . ' Cluster'; -} - -/** - * Calculate relevance score for mapping suggestions - * - * @param string $term The search term - * @param string $title The post title - * @param string $slug The post slug - * @return int Relevance score (0-100) - */ -function igny8_calculate_relevance_score($term, $title, $slug) { - $score = 0; - $term_lower = strtolower($term); - $title_lower = strtolower($title); - $slug_lower = strtolower($slug); - - // Exact match in title - if (strpos($title_lower, $term_lower) !== false) { - $score += 50; - } - - // Exact match in slug - if (strpos($slug_lower, $term_lower) !== false) { - $score += 30; - } - - // Word match in title - $title_words = explode(' ', $title_lower); - $term_words = explode(' ', $term_lower); - foreach ($term_words as $word) { - if (in_array($word, $title_words)) { - $score += 10; - } - } - - return min($score, 100); -} - -/** - * Remove duplicate suggestions from mapping suggestions - * - * @param array $suggestions Array of suggestion objects - * @return array Deduplicated suggestions - */ -function igny8_remove_duplicate_suggestions($suggestions) { - $seen = []; - $unique = []; - - foreach ($suggestions as $suggestion) { - $key = $suggestion['page_id']; - if (!isset($seen[$key])) { - $seen[$key] = true; - $unique[] = $suggestion; - } - } - - return $unique; -} - -/** - * Get Import/Export Configuration for a table - * - * @param string $table_id The table ID (e.g., 'planner_keywords') - * @return array|false Configuration array or false if not found - */ -function igny8_get_import_export_config($table_id) { - static $config_cache = null; - - if ($config_cache === null) { - $config_path = plugin_dir_path(dirname(__FILE__)) . '../modules/config/import-export-config.php'; - if (file_exists($config_path)) { - $config_cache = include $config_path; - } else { - $config_cache = []; - } - } - - return isset($config_cache[$table_id]) ? $config_cache[$table_id] : false; -} - - -/** - * AJAX handler for testing API connection - */ -function igny8_test_connection_ajax() { - try { - // Check if user has permission - if (!current_user_can('manage_options')) { - wp_send_json_error('Insufficient permissions'); - return; - } - - // Verify nonce - if (!check_ajax_referer('igny8_test_connection', 'nonce', false)) { - wp_send_json_error('Invalid nonce'); - return; - } - - // Test basic HTTP functionality - $test_url = 'https://httpbin.org/get'; - $response = wp_remote_get($test_url, ['timeout' => 10]); - - if (is_wp_error($response)) { - wp_send_json_error('HTTP request failed: ' . $response->get_error_message()); - return; - } - - $response_code = wp_remote_retrieve_response_code($response); - if ($response_code !== 200) { - wp_send_json_error('HTTP request returned status code: ' . $response_code); - return; - } - - wp_send_json_success('Connection test passed'); - - } catch (Exception $e) { - wp_send_json_error('Exception: ' . $e->getMessage()); - } -} -add_action('wp_ajax_igny8_test_connection', 'igny8_test_connection_ajax'); - -/** - * AJAX handler for testing API with response - */ -function igny8_test_api_ajax() { - try { - // Check if user has permission - if (!current_user_can('manage_options')) { - wp_send_json_error('Insufficient permissions'); - return; - } - - // Verify nonce - if (!check_ajax_referer('igny8_ajax_nonce', 'nonce', false)) { - wp_send_json_error('Invalid nonce'); - return; - } - - // Get API key - $api_key = get_option('igny8_api_key', ''); - if (empty($api_key)) { - wp_send_json_error('API key not configured'); - return; - } - - // Get parameters - $with_response = isset($_POST['with_response']) ? (bool) $_POST['with_response'] : false; - - // Test API connection - $result = igny8_test_connection($api_key, $with_response); - - if (is_wp_error($result)) { - wp_send_json_error($result->get_error_message()); - return; - } - - if (is_array($result) && isset($result['success'])) { - if ($result['success']) { - wp_send_json_success($result); - } else { - wp_send_json_error($result['message'] ?? 'API test failed'); - } - } else { - wp_send_json_success(['message' => 'API connection successful', 'response' => $result]); - } - - } catch (Exception $e) { - wp_send_json_error('Exception: ' . $e->getMessage()); - } -} -add_action('wp_ajax_igny8_test_api', 'igny8_test_api_ajax'); - -/** - * Get saved sector selection from user meta - * - * @return array|false Saved sector selection data or false if not set - */ -function igny8_get_saved_sector_selection() { - $user_id = get_current_user_id(); - $saved_selection = get_user_meta($user_id, 'igny8_planner_sector_selection', true); - return !empty($saved_selection) ? $saved_selection : false; -} - -/** - * Get system-wide AI workflow data for main dashboard - * - * @return array Array of step data with status and counts for all 9 workflow steps - */ -function igny8_get_system_workflow_data() { - global $wpdb; - - // Get counts for each step - $keywords_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_keywords"); - $unmapped_keywords = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_keywords WHERE cluster_id IS NULL OR cluster_id = 0"); - $clusters_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_clusters"); - $ideas_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_content_ideas"); - $queued_ideas = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_content_ideas WHERE status = 'new'"); - $queued_tasks = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_tasks WHERE status IN ('pending', 'queued', 'new')"); - $draft_tasks = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_tasks WHERE status IN ('draft', 'in_progress', 'review')"); - $published_tasks = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_tasks WHERE status = 'completed'"); - - // Check sector selection - $sector_selected = !empty(igny8_get_saved_sector_selection()); - - // Check if modules are enabled - $planner_enabled = igny8_is_module_enabled('planner'); - $writer_enabled = igny8_is_module_enabled('writer'); - - return [ - 'keywords' => [ - 'count' => $keywords_count, - 'unmapped' => $unmapped_keywords, - 'status' => $keywords_count > 0 ? 'completed' : 'missing', - 'module_enabled' => $planner_enabled, - 'url' => $planner_enabled ? '?page=igny8-planner&sm=keywords' : null - ], - 'sector' => [ - 'selected' => $sector_selected, - 'status' => $sector_selected ? 'completed' : 'missing', - 'module_enabled' => $planner_enabled, - 'url' => $planner_enabled ? '?page=igny8-planner' : null - ], - 'clusters' => [ - 'count' => $clusters_count, - 'unmapped_keywords' => $unmapped_keywords, - 'status' => $unmapped_keywords == 0 && $clusters_count > 0 ? 'completed' : ($unmapped_keywords > 0 ? 'in_progress' : 'missing'), - 'module_enabled' => $planner_enabled, - 'url' => $planner_enabled ? '?page=igny8-planner&sm=clusters' : null - ], - 'ideas' => [ - 'count' => $ideas_count, - 'status' => $ideas_count > 0 ? 'completed' : 'missing', - 'module_enabled' => $planner_enabled, - 'url' => $planner_enabled ? '?page=igny8-planner&sm=ideas' : null - ], - 'queue' => [ - 'queued_ideas' => $queued_ideas, - 'status' => $queued_ideas == 0 && $ideas_count > 0 ? 'completed' : ($queued_ideas > 0 ? 'in_progress' : 'missing'), - 'module_enabled' => $planner_enabled, - 'url' => $planner_enabled ? '?page=igny8-planner&sm=ideas' : null - ], - 'drafts' => [ - 'queued_tasks' => $queued_tasks, - 'draft_tasks' => $draft_tasks, - 'status' => $queued_tasks > 0 ? 'in_progress' : ($draft_tasks > 0 ? 'completed' : 'missing'), - 'module_enabled' => $writer_enabled, - 'url' => $writer_enabled ? '?page=igny8-writer&sm=tasks' : null - ], - 'publish' => [ - 'published_tasks' => $published_tasks, - 'draft_tasks' => $draft_tasks, - 'status' => $published_tasks > 0 ? 'completed' : ($draft_tasks > 0 ? 'in_progress' : 'missing'), - 'module_enabled' => $writer_enabled, - 'url' => $writer_enabled ? '?page=igny8-writer&sm=drafts' : null - ] - ]; -} - -/** - * Display image prompts in a formatted way for table display - * - * @param string $image_prompts JSON string of image prompts - * @return string Formatted display string - */ - - diff --git a/igny8-wp-plugin-for-reference-olny/core/admin/init.php b/igny8-wp-plugin-for-reference-olny/core/admin/init.php deleted file mode 100644 index 882bed6a..00000000 --- a/igny8-wp-plugin-for-reference-olny/core/admin/init.php +++ /dev/null @@ -1,135 +0,0 @@ - $settings) { - foreach ($settings as $name => $config) { - register_setting($group, $name, $config); - } - } -} - -/** - * Settings Configuration (grouped) - */ -function igny8_get_settings_config() { - return [ - 'igny8_table_settings' => [ - 'igny8_records_per_page' => [ - 'type' => 'integer', - 'default' => 20, - 'sanitize_callback' => 'absint' - ] - ], - 'igny8_ai_integration_settings' => [ - 'igny8_ai_cluster_building' => ['type' => 'string', 'default' => 'enabled', 'sanitize_callback' => 'sanitize_text_field'], - 'igny8_ai_content_ideas' => ['type' => 'string', 'default' => 'enabled', 'sanitize_callback' => 'sanitize_text_field'], - 'igny8_ai_auto_mapping' => ['type' => 'string', 'default' => 'enabled', 'sanitize_callback' => 'sanitize_text_field'] - ], - 'igny8_api_settings' => [ - 'igny8_api_key' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field'], - 'igny8_runware_api_key' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field'], - 'igny8_model' => ['type' => 'string', 'default' => 'gpt-4.1', 'sanitize_callback' => 'sanitize_text_field'], - 'igny8_image_service' => ['type' => 'string', 'default' => 'openai', 'sanitize_callback' => 'sanitize_text_field'], - 'igny8_image_model' => ['type' => 'string', 'default' => 'dall-e-3', 'sanitize_callback' => 'sanitize_text_field'], - 'igny8_runware_model' => ['type' => 'string', 'default' => 'runware:97@1', 'sanitize_callback' => 'sanitize_text_field'] - ], - 'igny8_personalize_settings_group' => [ - 'igny8_content_engine_global_status' => ['sanitize_callback' => 'igny8_sanitize_checkbox_setting'], - 'igny8_content_engine_enabled_post_types' => ['sanitize_callback' => 'igny8_sanitize_array_setting'], - 'igny8_content_engine_insertion_position' => ['sanitize_callback' => 'sanitize_text_field'], - 'igny8_content_engine_display_mode' => ['sanitize_callback' => 'sanitize_text_field'], - 'igny8_content_engine_teaser_text' => ['sanitize_callback' => 'sanitize_textarea_field'], - 'igny8_content_engine_save_variations' => ['sanitize_callback' => 'intval'], - 'igny8_content_engine_field_mode' => ['sanitize_callback' => 'sanitize_text_field'], - 'igny8_content_engine_detection_prompt' => ['sanitize_callback' => 'sanitize_textarea_field'], - 'igny8_content_engine_context_source' => ['sanitize_callback' => 'sanitize_textarea_field'], - 'igny8_content_engine_include_page_context' => ['sanitize_callback' => 'intval'], - 'igny8_content_engine_content_length' => ['sanitize_callback' => 'sanitize_text_field'], - 'igny8_content_engine_rewrite_prompt' => ['sanitize_callback' => 'sanitize_textarea_field'], - 'igny8_content_engine_fixed_fields_config' => ['sanitize_callback' => 'igny8_sanitize_fields_config'] - ] - ]; -} - -/** - * ------------------------------------------------------------------------ - * SANITIZATION HELPERS - * ------------------------------------------------------------------------ - */ -function igny8_sanitize_checkbox_setting($raw) { - return isset($_POST['igny8_content_engine_global_status']) ? 'enabled' : 'disabled'; -} - -function igny8_sanitize_array_setting($raw) { - return is_array($raw) ? array_map('sanitize_text_field', $raw) : []; -} - -function igny8_sanitize_fields_config($raw) { - if (!is_array($raw)) return []; - $sanitized = []; - foreach ($raw as $index => $field) { - $sanitized[$index] = [ - 'label' => sanitize_text_field($field['label'] ?? ''), - 'type' => sanitize_text_field($field['type'] ?? 'text'), - 'options' => sanitize_text_field($field['options'] ?? '') - ]; - } - return $sanitized; -} - - -// MOVED TO: igny8.php - Admin assets enqueuing moved to main plugin file - -// --------------------------------------------------------------------- -// WORDPRESS FEATURE REGISTRATION -// --------------------------------------------------------------------- - -function igny8_init_wordpress_features() { - // Initialize module manager - add_action('init', 'igny8_module_manager'); - - // Register taxonomies - add_action('init', 'igny8_register_taxonomies'); - - // Register post meta once - add_action('init', function() { - if (!get_option('igny8_post_meta_registered')) { - igny8_register_post_meta(); - update_option('igny8_post_meta_registered', true); - } - }); -} - -//Initialize WordPress features -igny8_init_wordpress_features(); diff --git a/igny8-wp-plugin-for-reference-olny/core/admin/menu.php b/igny8-wp-plugin-for-reference-olny/core/admin/menu.php deleted file mode 100644 index ddf8018f..00000000 --- a/igny8-wp-plugin-for-reference-olny/core/admin/menu.php +++ /dev/null @@ -1,356 +0,0 @@ -'; - $breadcrumb .= ''; - - if ($current_page === 'igny8-planner') { - $breadcrumb .= ''; - $breadcrumb .= ''; - - if ($sm === 'keywords') { - $breadcrumb .= ''; - $breadcrumb .= ''; - } elseif ($sm === 'clusters') { - $breadcrumb .= ''; - $breadcrumb .= ''; - } elseif ($sm === 'ideas') { - $breadcrumb .= ''; - $breadcrumb .= ''; - } elseif ($sm === 'mapping') { - $breadcrumb .= ''; - $breadcrumb .= ''; - } - } elseif ($current_page === 'igny8-writer') { - $breadcrumb .= ''; - $breadcrumb .= ''; - - if ($sm === 'drafts') { - $breadcrumb .= ''; - $breadcrumb .= ''; - } elseif ($sm === 'templates') { - $breadcrumb .= ''; - $breadcrumb .= ''; - } - } elseif ($current_page === 'igny8-optimizer') { - $breadcrumb .= ''; - $breadcrumb .= ''; - - if ($sm === 'audits') { - $breadcrumb .= ''; - $breadcrumb .= ''; - } elseif ($sm === 'suggestions') { - $breadcrumb .= ''; - $breadcrumb .= ''; - } - } elseif ($current_page === 'igny8-linker') { - $breadcrumb .= ''; - $breadcrumb .= ''; - - if ($sm === 'backlinks') { - $breadcrumb .= ''; - $breadcrumb .= ''; - } elseif ($sm === 'campaigns') { - $breadcrumb .= ''; - $breadcrumb .= ''; - } - } elseif ($current_page === 'igny8-personalize') { - $breadcrumb .= ''; - $breadcrumb .= ''; - - if ($sm === 'settings') { - $breadcrumb .= ''; - $breadcrumb .= ''; - } elseif ($sm === 'content-generation') { - $breadcrumb .= ''; - $breadcrumb .= ''; - } elseif ($sm === 'rewrites') { - $breadcrumb .= ''; - $breadcrumb .= ''; - } elseif ($sm === 'front-end') { - $breadcrumb .= ''; - $breadcrumb .= ''; - } - } elseif (strpos($current_page, 'igny8-analytics') !== false) { - $breadcrumb .= ''; - $breadcrumb .= ''; - } elseif (strpos($current_page, 'igny8-schedules') !== false) { - $breadcrumb .= ''; - $breadcrumb .= ''; - } elseif (strpos($current_page, 'igny8-settings') !== false) { - $breadcrumb .= ''; - $breadcrumb .= ''; - } elseif (strpos($current_page, 'igny8-help') !== false) { - $breadcrumb .= ''; - $breadcrumb .= ''; - } - - $breadcrumb .= ''; - return $breadcrumb; -} - -/** - * Render submenu navigation - */ -function igny8_render_submenu() { - $current_page = $_GET['page'] ?? ''; - $sm = $_GET['sm'] ?? ''; - $submenu = ''; - - if ($current_page === 'igny8-planner') { - $submenu .= 'Dashboard'; - $submenu .= 'Keywords'; - $submenu .= 'Clusters'; - $submenu .= 'Ideas'; - } elseif ($current_page === 'igny8-writer') { - $submenu .= 'Dashboard'; - $submenu .= 'Tasks'; - $submenu .= 'Drafts'; - $submenu .= 'Published'; - } elseif ($current_page === 'igny8-thinker') { - $sp = $_GET['sp'] ?? 'main'; - $submenu .= 'Dashboard'; - $submenu .= 'Prompts'; - $submenu .= 'Profile'; - $submenu .= 'Strategies'; - $submenu .= 'Image Testing'; - } elseif ($current_page === 'igny8-optimizer') { - $submenu .= 'Dashboard'; - $submenu .= 'Audits'; - $submenu .= 'Suggestions'; - } elseif ($current_page === 'igny8-linker') { - $submenu .= 'Dashboard'; - $submenu .= 'Backlinks'; - $submenu .= 'Campaigns'; - } elseif ($current_page === 'igny8-personalize') { - $submenu .= 'Dashboard'; - $submenu .= 'Settings'; - $submenu .= 'Content Generation'; - $submenu .= 'Rewrites'; - $submenu .= 'Front-end'; - } elseif ($current_page === 'igny8-settings') { - $sp = $_GET['sp'] ?? 'general'; - $submenu .= 'Settings'; - $submenu .= 'Status'; - $submenu .= 'Integration'; - $submenu .= 'Import/Export'; - } elseif ($current_page === 'igny8-help') { - $sp = $_GET['sp'] ?? 'help'; - $submenu .= 'Help & Support'; - $submenu .= 'Documentation'; - $submenu .= 'System Testing'; - $submenu .= 'Function Testing'; - } - - return $submenu; -} - -/** - * Register admin menu pages - */ -function igny8_register_admin_menu() { - // Ensure module manager is available - if (!function_exists('igny8_is_module_enabled')) { - return; - } - // Main menu page - add_menu_page( - 'Igny8 AI SEO', // Page title - 'Igny8 AI SEO', // Menu title - 'manage_options', // Capability - 'igny8-home', // Menu slug - 'igny8_home_page', // Callback function - 'dashicons-chart-line', // Icon - 30 // Position - ); - - // Home page - add_submenu_page( - 'igny8-home', // Parent slug - 'Dashboard', // Page title - 'Dashboard', // Menu title - 'manage_options', // Capability - 'igny8-home', // Menu slug - 'igny8_home_page' // Callback function - ); - - // Module submenus (only if enabled) - if (igny8_is_module_enabled('planner')) { - add_submenu_page( - 'igny8-home', - 'Content Planner', - 'Planner', - 'manage_options', - 'igny8-planner', - 'igny8_planner_page' - ); - } - - if (igny8_is_module_enabled('writer')) { - add_submenu_page( - 'igny8-home', - 'Content Writer', - 'Writer', - 'manage_options', - 'igny8-writer', - 'igny8_writer_page' - ); - } - - if (igny8_is_module_enabled('thinker')) { - add_submenu_page( - 'igny8-home', - 'AI Thinker', - 'Thinker', - 'manage_options', - 'igny8-thinker', - 'igny8_thinker_page' - ); - - // Prompts subpage under Thinker - add_submenu_page( - 'igny8-thinker', - 'AI Prompts', - 'Prompts', - 'manage_options', - 'igny8-thinker&sp=prompts', - 'igny8_thinker_page' - ); - } - - if (igny8_is_module_enabled('schedules')) { - add_submenu_page( - 'igny8-home', - 'Smart Automation Schedules', - 'Schedules', - 'manage_options', - 'igny8-schedules', - 'igny8_schedules_page' - ); - } - - - // Analytics before Settings (only if enabled) - if (igny8_is_module_enabled('analytics')) { - add_submenu_page( - 'igny8-home', - 'Analytics', - 'Analytics', - 'manage_options', - 'igny8-analytics', - 'igny8_analytics_page' - ); - } - - // Cron Health page - - // Settings page - add_submenu_page( - 'igny8-home', - 'Settings', - 'Settings', - 'manage_options', - 'igny8-settings', - 'igny8_settings_page' - ); - - - // Help page - add_submenu_page( - 'igny8-home', - 'Help', - 'Help', - 'manage_options', - 'igny8-help', - 'igny8_help_page' - ); - - // Documentation subpage under Help - add_submenu_page( - 'igny8-help', - 'Documentation', - 'Documentation', - 'manage_options', - 'igny8-help&sp=docs', - 'igny8_help_page' - ); - - // System Testing subpage under Help - add_submenu_page( - 'igny8-help', - 'System Testing', - 'System Testing', - 'manage_options', - 'igny8-help&sp=system-testing', - 'igny8_help_page' - ); - - // Function Testing subpage under Help - add_submenu_page( - 'igny8-help', - 'Function Testing', - 'Function Testing', - 'manage_options', - 'igny8-help&sp=function-testing', - 'igny8_help_page' - ); - -} - -// Static page wrapper functions - each page handles its own layout -function igny8_home_page() { - include_once plugin_dir_path(__FILE__) . '../../modules/home.php'; -} - -function igny8_planner_page() { - include_once plugin_dir_path(__FILE__) . '../../modules/planner/planner.php'; -} - -function igny8_writer_page() { - include_once plugin_dir_path(__FILE__) . '../../modules/writer/writer.php'; -} - -function igny8_thinker_page() { - include_once plugin_dir_path(__FILE__) . '../../modules/thinker/thinker.php'; -} - -function igny8_settings_page() { - include_once plugin_dir_path(__FILE__) . '../../modules/settings/general-settings.php'; -} - -function igny8_analytics_page() { - include_once plugin_dir_path(__FILE__) . '../../modules/analytics/analytics.php'; -} - -function igny8_schedules_page() { - include_once plugin_dir_path(__FILE__) . '../../modules/settings/schedules.php'; -} - -function igny8_help_page() { - include_once plugin_dir_path(__FILE__) . '../../modules/help/help.php'; -} - -// Hook into admin_menu -add_action('admin_menu', 'igny8_register_admin_menu'); diff --git a/igny8-wp-plugin-for-reference-olny/core/admin/meta-boxes.php b/igny8-wp-plugin-for-reference-olny/core/admin/meta-boxes.php deleted file mode 100644 index 85136547..00000000 --- a/igny8-wp-plugin-for-reference-olny/core/admin/meta-boxes.php +++ /dev/null @@ -1,387 +0,0 @@ -ID, '_igny8_meta_title', true); - $meta_desc = get_post_meta($post->ID, '_igny8_meta_description', true); - $primary_kw = get_post_meta($post->ID, '_igny8_primary_keywords', true); - $secondary_kw = get_post_meta($post->ID, '_igny8_secondary_keywords', true); - ?> --- ID, '_igny8_inarticle_images', true); - if (!is_array($images)) $images = []; - - // Add CSS for grid layout and remove button - ?> - - '; - echo '
-
- -
-
- -
-
- -
- --
'; - echo ''; - - - - - echo ''; - - // Sort images by device type and ID - $sorted_images = []; - foreach ($images as $label => $data) { - $sorted_images[$label] = $data; - } - - // Custom sort function to order by device type (desktop first) then by ID - uksort($sorted_images, function($a, $b) { - $a_parts = explode('-', $a); - $b_parts = explode('-', $b); - - $a_device = $a_parts[0]; - $b_device = $b_parts[0]; - - // Desktop comes before mobile - if ($a_device === 'desktop' && $b_device === 'mobile') return -1; - if ($a_device === 'mobile' && $b_device === 'desktop') return 1; - - // If same device, sort by ID number - if ($a_device === $b_device) { - $a_id = intval($a_parts[1]); - $b_id = intval($b_parts[1]); - return $a_id - $b_id; - } - - return 0; - }); - - foreach ($sorted_images as $label => $data) { - echo '
'; - - - // Inline JS - ?> - - $data) { - if (!empty($data['attachment_id'])) { - $filtered[$label] = [ - 'label' => sanitize_text_field($label), - 'attachment_id' => intval($data['attachment_id']), - 'url' => wp_get_attachment_url(intval($data['attachment_id'])), - 'device' => sanitize_text_field($data['device']) - ]; - } - } - update_post_meta($post_id, '_igny8_inarticle_images', $filtered); - - if (WP_DEBUG === true) { - error_log("[IGNY8 DEBUG] Saving In-Article Images for Post ID: $post_id"); - error_log(print_r($filtered, true)); - } -}); - diff --git a/igny8-wp-plugin-for-reference-olny/core/admin/module-manager-class.php b/igny8-wp-plugin-for-reference-olny/core/admin/module-manager-class.php deleted file mode 100644 index 2d940f2e..00000000 --- a/igny8-wp-plugin-for-reference-olny/core/admin/module-manager-class.php +++ /dev/null @@ -1,181 +0,0 @@ -init_modules(); - add_action('admin_init', [$this, 'register_module_settings']); - } - - /** - * Initialize module definitions - main modules only - */ - private function init_modules() { - $this->modules = [ - 'planner' => [ - 'name' => 'Planner', - 'description' => 'Keyword research and content planning with clusters, ideas, and mapping tools.', - 'default' => true, - 'icon' => 'dashicons-search', - 'category' => 'main', - 'cron_jobs' => [ - 'igny8_auto_cluster_cron', - 'igny8_auto_generate_ideas_cron', - 'igny8_auto_queue_cron' - ] - ], - 'writer' => [ - 'name' => 'Writer', - 'description' => 'AI-powered content generation with drafts and templates management.', - 'default' => false, - 'icon' => 'dashicons-edit', - 'category' => 'main', - 'cron_jobs' => [ - 'igny8_auto_generate_content_cron', - 'igny8_auto_generate_images_cron', - 'igny8_auto_publish_drafts_cron' - ] - ], - 'analytics' => [ - 'name' => 'Analytics', - 'description' => 'Performance tracking and data analysis with comprehensive reporting.', - 'default' => false, - 'icon' => 'dashicons-chart-bar', - 'category' => 'admin', - 'cron_jobs' => [ - 'igny8_process_ai_queue_cron', - 'igny8_auto_recalc_cron', - 'igny8_health_check_cron' - ] - ], - 'schedules' => [ - 'name' => 'Schedules', - 'description' => 'Content scheduling and automation with calendar management.', - 'default' => false, - 'icon' => 'dashicons-calendar-alt', - 'category' => 'admin' - ], - 'thinker' => [ - 'name' => 'AI Thinker', - 'description' => 'AI-powered content strategy, prompts, and intelligent content planning tools.', - 'default' => true, - 'icon' => 'dashicons-lightbulb', - 'category' => 'admin' - ] - ]; - } - - /** - * Check if a module is enabled - */ - public function is_module_enabled($module) { - $settings = get_option('igny8_module_settings', []); - return isset($settings[$module]) ? (bool) $settings[$module] : (isset($this->modules[$module]) ? $this->modules[$module]['default'] : false); - } - - /** - * Get all enabled modules - */ - public function get_enabled_modules() { - $enabled = []; - foreach ($this->modules as $key => $module) { - if ($this->is_module_enabled($key)) { - $enabled[$key] = $module; - } - } - return $enabled; - } - - /** - * Get all modules - */ - public function get_modules() { - return $this->modules; - } - - /** - * Register module settings - */ - public function register_module_settings() { - register_setting('igny8_module_settings', 'igny8_module_settings'); - } - - /** - * Save module settings - */ - public function save_module_settings() { - if (!isset($_POST['igny8_module_nonce']) || !wp_verify_nonce($_POST['igny8_module_nonce'], 'igny8_module_settings')) { - wp_die('Security check failed'); - } - - $settings = $_POST['igny8_module_settings'] ?? []; - - // Initialize all modules as disabled first - $all_modules = $this->get_modules(); - $final_settings = []; - foreach ($all_modules as $module_key => $module) { - $final_settings[$module_key] = false; // Default to disabled - } - - // Set enabled modules to true - foreach ($settings as $key => $value) { - if (isset($final_settings[$key])) { - $final_settings[$key] = (bool) $value; - } - } - - update_option('igny8_module_settings', $final_settings); - - // Force page reload using JavaScript - echo ''; - exit; - } -} - -// Initialize the module manager -function igny8_module_manager() { - return Igny8_Module_Manager::get_instance(); -} - -// Helper functions for easy access -function igny8_is_module_enabled($module) { - return igny8_module_manager()->is_module_enabled($module); -} - -function igny8_get_enabled_modules() { - return igny8_module_manager()->get_enabled_modules(); -} - -function igny8_get_modules() { - return igny8_module_manager()->get_modules(); -} diff --git a/igny8-wp-plugin-for-reference-olny/core/cron/igny8-cron-handlers.php b/igny8-wp-plugin-for-reference-olny/core/cron/igny8-cron-handlers.php deleted file mode 100644 index 90aef380..00000000 --- a/igny8-wp-plugin-for-reference-olny/core/cron/igny8-cron-handlers.php +++ /dev/null @@ -1,1610 +0,0 @@ - 0) { - igny8_log_ai_event('AI Queue Processed', 'ai', 'queue_processing', 'success', 'AI queue tasks processed', "Tasks processed: $processed"); - - // Set global variables for detailed logging - $GLOBALS['igny8_cron_processed_count'] = $processed; - $GLOBALS['igny8_cron_result_details'] = "Processed {$processed} AI queue tasks"; - echo "Igny8 AI QUEUE HANDLER: Set global variables - processed_count: $processed, result_details: Processed {$processed} AI queue tasks- '; - echo '' . esc_html($label) . '
'; - } - echo '
'; - echo wp_get_attachment_image($data['attachment_id'], 'thumbnail', false, ['style' => 'width: 150px; height: 150px; object-fit: cover;']); - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo '
"; - } else { - igny8_log_ai_event('AI Queue Empty', 'ai', 'queue_processing', 'info', 'No tasks in queue', 'All tasks are already processed'); - - // Set global variables for detailed logging - $GLOBALS['igny8_cron_processed_count'] = 0; - $GLOBALS['igny8_cron_result_details'] = "No AI queue tasks to process"; - } - - } catch (Exception $e) { - igny8_log_ai_event('AI Queue Error', 'ai', 'queue_processing', 'error', 'AI queue processing failed', $e->getMessage()); - - // Set global variables for detailed logging (failure case) - $GLOBALS['igny8_cron_processed_count'] = 0; - $GLOBALS['igny8_cron_result_details'] = "FAILED: " . $e->getMessage(); - } finally { - // Always release the lock - delete_transient($lock_key); - } -} - -/** - * Auto Cluster Cron Handler - * - * Automatically clusters unmapped keywords with taxonomy safety. - */ -function igny8_auto_cluster_cron_handler() { - // Suppress PHP warnings for model rates in cron context - $old_error_reporting = error_reporting(); - error_reporting($old_error_reporting & ~E_WARNING); - - echo ""; - echo "Igny8 CRON HANDLER: auto_cluster started"; - return; - } - echo "Igny8 CRON HANDLER: Automation enabled, continuing
"; - error_log("Igny8 CRON HANDLER: auto_cluster started"); - - // Check if automation is enabled via cron settings - echo "Igny8 CRON HANDLER: Checking if automation is enabled
"; - $cron_settings = get_option('igny8_cron_settings', []); - $job_settings = $cron_settings['igny8_auto_cluster_cron'] ?? []; - $auto_cluster_enabled = $job_settings['enabled'] ?? false; - echo "Igny8 CRON HANDLER: auto_cluster_enabled = " . ($auto_cluster_enabled ? 'enabled' : 'disabled') . "
"; - - if (!$auto_cluster_enabled) { - echo "Igny8 CRON HANDLER: Automation disabled, exiting
"; - error_log("Igny8 CRON HANDLER: auto_cluster automation disabled"); - echo "
"; - - // Check if AI mode is enabled - echo "Igny8 CRON HANDLER: Checking AI mode
"; - $planner_mode = igny8_get_ai_setting('planner_mode', 'manual'); - echo "Igny8 CRON HANDLER: planner_mode = " . $planner_mode . "
"; - - if ($planner_mode !== 'ai') { - echo "Igny8 CRON HANDLER: AI mode disabled, exiting
"; - error_log("Igny8 CRON HANDLER: AI mode disabled"); - echo ""; - return; - } - echo "Igny8 CRON HANDLER: AI mode enabled, continuing
"; - - // Check if sector is selected - echo "Igny8 CRON HANDLER: Checking sector options
"; - - // Check if function exists first - if (!function_exists('igny8_get_sector_options')) { - echo "Igny8 CRON HANDLER: ERROR - igny8_get_sector_options function not found
"; - igny8_log_ai_event('Auto Cluster Failed', 'planner', 'auto_cluster', 'error', 'Sector options function not available', 'Function igny8_get_sector_options not found'); - echo "Igny8 CRON HANDLER: Exiting due to missing function
"; - echo ""; - return; - } - - try { - echo "Igny8 CRON HANDLER: Calling igny8_get_sector_options()
"; - $sector_options = igny8_get_sector_options(); - echo "Igny8 CRON HANDLER: sector_options count = " . count($sector_options) . "
"; - echo "Igny8 CRON HANDLER: sector_options content: " . print_r($sector_options, true) . "
"; - } catch (Exception $e) { - echo "Igny8 CRON HANDLER: ERROR - Exception in igny8_get_sector_options: " . $e->getMessage() . "
"; - igny8_log_ai_event('Auto Cluster Failed', 'planner', 'auto_cluster', 'error', 'Exception in sector options function', $e->getMessage()); - echo "Igny8 CRON HANDLER: Exiting due to sector options exception
"; - echo ""; - return; - } - - if (empty($sector_options)) { - echo "Igny8 CRON HANDLER: No sector selected, checking alternatives
"; - echo "Igny8 CRON HANDLER: Checking if igny8_get_sector_options function exists: " . (function_exists('igny8_get_sector_options') ? 'YES' : 'NO') . "
"; - - // Try to get sectors directly from database - global $wpdb; - $sectors = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}igny8_sectors WHERE status = 'active'"); - echo "Igny8 CRON HANDLER: Direct DB query found " . count($sectors) . " active sectors
"; - - if (empty($sectors)) { - echo "Igny8 CRON HANDLER: ERROR - No active sectors found, cannot proceed
"; - igny8_log_ai_event('Auto Cluster Failed', 'planner', 'auto_cluster', 'error', 'No active sectors configured', 'Sector configuration required for clustering'); - echo "Igny8 CRON HANDLER: Exiting due to missing sectors
"; - echo ""; - return; - } else { - echo "Igny8 CRON HANDLER: Found sectors in DB, using them
"; - $sector_options = $sectors; - } - } - echo "Igny8 CRON HANDLER: Sector selected, continuing
"; - - global $wpdb; - echo "Igny8 CRON HANDLER: Database connection established
"; - - // Get unmapped keywords (use dynamic limit from settings) - $limit = $GLOBALS['igny8_cron_limit'] ?? null; - if ($limit === null) { - // Try to get limit from Smart Automation Jobs table directly - $cron_limits = get_option('igny8_cron_limits', []); - $limit = $cron_limits['igny8_auto_cluster_cron'] ?? null; - - if ($limit === null) { - error_log('Igny8 Auto Cluster Cron: No limit set in Smart Automation Jobs table'); - $GLOBALS['igny8_cron_processed_count'] = 0; - $GLOBALS['igny8_cron_result_details'] = "FAILED: No limit set in Smart Automation Jobs table"; - return; - } - - echo "Igny8 CRON HANDLER: Using limit from Smart Automation Jobs table: $limit
"; - } - echo "Igny8 CRON HANDLER: Querying unmapped keywords (limit: $limit)
"; - $query = "SELECT id, keyword FROM {$wpdb->prefix}igny8_keywords WHERE cluster_id IS NULL OR cluster_id = 0 LIMIT $limit"; - echo "Igny8 CRON HANDLER: SQL Query: " . $query . "
"; - - $unmapped_keywords = $wpdb->get_results($query); - - echo "Igny8 CRON HANDLER: Found " . count($unmapped_keywords) . " unmapped keywords
"; - - if (!empty($unmapped_keywords)) { - echo "Igny8 CRON HANDLER: Sample keywords:
"; - foreach (array_slice($unmapped_keywords, 0, 3) as $keyword) { - echo "- ID: " . $keyword->id . ", Keyword: " . $keyword->keyword . "
"; - } - } - - if (empty($unmapped_keywords)) { - echo "Igny8 CRON HANDLER: ERROR - No unmapped keywords found
"; - igny8_log_ai_event('Auto Cluster Failed', 'planner', 'auto_cluster', 'error', 'No unmapped keywords available for clustering', 'All keywords are already clustered or no keywords exist'); - echo "Igny8 CRON HANDLER: Exiting due to no keywords to process
"; - echo ""; - return; - } - - $keyword_ids = array_column($unmapped_keywords, 'id'); - echo "Igny8 CRON HANDLER: Processing " . count($keyword_ids) . " keywords
"; - - // Log automation start - echo "Igny8 CRON HANDLER: Logging automation start
"; - igny8_log_ai_event('Auto Cluster Started', 'planner', 'auto_cluster', 'info', 'Starting automated clustering', 'Keywords: ' . count($keyword_ids)); - - // Direct clustering without AJAX simulation - echo "Igny8 CRON HANDLER: Starting direct clustering process
"; - - // Set up user context for permissions - if (!current_user_can('manage_options')) { - // Get admin user for cron context - $admin_users = get_users(['role' => 'administrator', 'number' => 1]); - if (!empty($admin_users)) { - wp_set_current_user($admin_users[0]->ID); - echo "Igny8 CRON HANDLER: Set admin user context for permissions
"; - } - } - - // Get keywords data - echo "Igny8 CRON HANDLER: Getting keywords data
"; - $placeholders = implode(',', array_fill(0, count($keyword_ids), '%d')); - $keywords = $wpdb->get_results($wpdb->prepare(" - SELECT * FROM {$wpdb->prefix}igny8_keywords - WHERE id IN ({$placeholders}) - ", $keyword_ids)); - - echo "Igny8 CRON HANDLER: Found " . count($keywords) . " keywords in database
"; - - if (empty($keywords)) { - echo "Igny8 CRON HANDLER: ERROR - No valid keywords found
"; - igny8_log_ai_event('Auto Cluster Failed', 'planner', 'auto_cluster', 'error', 'No valid keywords found in database', 'Keyword IDs: ' . implode(',', $keyword_ids)); - echo ""; - return; - } - - // Check if keywords already have clusters - $keywords_with_clusters = array_filter($keywords, function($keyword) { - return !empty($keyword->cluster_id) && $keyword->cluster_id > 0; - }); - - if (!empty($keywords_with_clusters)) { - echo "Igny8 CRON HANDLER: WARNING - Some keywords already have clusters
"; - $keyword_names = array_column($keywords_with_clusters, 'keyword'); - echo "Igny8 CRON HANDLER: Already clustered: " . implode(', ', array_slice($keyword_names, 0, 3)) . "
"; - } - - // Get clustering prompt - echo "Igny8 CRON HANDLER: Getting clustering prompt
"; - $prompt_template = wp_unslash(igny8_get_ai_setting('clustering_prompt', igny8_get_default_clustering_prompt())); - echo "Igny8 CRON HANDLER: Prompt length: " . strlen($prompt_template) . "
"; - - // Generate session ID for progress tracking - $session_id = 'cron_clustering_' . time() . '_' . wp_generate_password(8, false); - echo "Igny8 CRON HANDLER: Session ID: " . $session_id . "
"; - - // Log AI request initiation - igny8_log_ai_event('Cron AI Request Initiated', 'planner', 'clustering', 'info', 'Starting cron AI clustering process', 'Keywords: ' . count($keyword_ids) . ', Session: ' . $session_id); - - // Process with AI - echo "Igny8 CRON HANDLER: Calling AI processing
"; - try { - $ai_result = igny8_process_ai_request('clustering', $keywords, $prompt_template); - echo "Igny8 CRON HANDLER: AI processing completed
"; - - if ($ai_result === false) { - echo "Igny8 CRON HANDLER: ERROR - AI processing returned false
"; - igny8_log_ai_event('Cron AI Processing Failed', 'planner', 'clustering', 'error', 'AI processing returned false', 'Check OpenAI API configuration'); - echo ""; - return; - } - - if (!is_array($ai_result) || !isset($ai_result['clusters'])) { - echo "Igny8 CRON HANDLER: ERROR - AI returned invalid result
"; - igny8_log_ai_event('Cron AI Processing Failed', 'planner', 'clustering', 'error', 'AI returned invalid result', 'Result type: ' . gettype($ai_result)); - echo ""; - return; - } - - echo "Igny8 CRON HANDLER: AI returned " . count($ai_result['clusters']) . " clusters
"; - - // Debug: Show AI response structure - echo "Igny8 CRON HANDLER: AI Response Debug:
"; - echo "Igny8 CRON HANDLER: Full AI result structure: " . json_encode($ai_result, JSON_PRETTY_PRINT) . "
"; - - igny8_log_ai_event('Cron AI Processing Complete', 'planner', 'clustering', 'success', 'AI returned ' . count($ai_result['clusters']) . ' clusters', 'Clusters: ' . json_encode(array_column($ai_result['clusters'], 'name'))); - - // Log database operations start - echo "Igny8 CRON HANDLER: Starting database operations
"; - igny8_log_ai_event('Cron Database Operations Started', 'planner', 'clustering', 'info', 'Starting to create clusters in database', 'Clusters to create: ' . count($ai_result['clusters'])); - - // Get sector options for assignment logic (same as AJAX handler) - echo "Igny8 CRON HANDLER: Getting sector options for assignment
"; - $sector_options = igny8_get_sector_options(); - $sector_count = count($sector_options); - echo "Igny8 CRON HANDLER: Found " . $sector_count . " sectors
"; - - // Create clusters in database (following AJAX handler process) - $created_clusters = []; - $clusters_created = 0; - $keywords_processed = 0; - - foreach ($ai_result['clusters'] as $cluster_data) { - echo "Igny8 CRON HANDLER: Processing cluster: " . $cluster_data['name'] . "
"; - - try { - - // Determine sector_id based on sector count (same logic as AJAX handler) - $sector_id = 1; // Default fallback - echo "Igny8 CRON HANDLER: Determining sector assignment
"; - - if ($sector_count == 1) { - // Only 1 sector: assign all clusters to that sector - $sector_id = $sector_options[0]['value']; - echo "Igny8 CRON HANDLER: Single sector found, assigning to sector ID: " . $sector_id . "
"; - } elseif ($sector_count > 1) { - // Multiple sectors: use AI response sector assignment - if (isset($cluster_data['sector']) && !empty($cluster_data['sector'])) { - echo "Igny8 CRON HANDLER: AI provided sector: " . $cluster_data['sector'] . "
"; - // Find sector ID by matching sector name from AI response - foreach ($sector_options as $sector) { - if (strtolower(trim($sector['label'])) === strtolower(trim($cluster_data['sector']))) { - $sector_id = $sector['value']; - echo "Igny8 CRON HANDLER: Matched sector ID: " . $sector_id . "
"; - break; - } - } - } - // If no match found or no sector in AI response, use first sector as fallback - if ($sector_id == 1 && !isset($cluster_data['sector'])) { - $sector_id = $sector_options[0]['value']; - echo "Igny8 CRON HANDLER: No sector match, using first sector ID: " . $sector_id . "
"; - } - } - - // Create cluster record in database (same as AJAX handler) - echo "Igny8 CRON HANDLER: Creating cluster record in database
"; - $result = $wpdb->insert( - $wpdb->prefix . 'igny8_clusters', - [ - 'cluster_name' => sanitize_text_field($cluster_data['name']), - 'sector_id' => $sector_id, - 'status' => 'active', - 'keyword_count' => count($cluster_data['keywords']), - 'total_volume' => 0, - 'avg_difficulty' => 0, - 'mapped_pages_count' => 0, - 'created_at' => current_time('mysql') - ], - ['%s', '%d', '%s', '%d', '%d', '%f', '%d', '%s'] - ); - - if ($result) { - $cluster_id = $wpdb->insert_id; - $created_clusters[] = $cluster_id; - $clusters_created++; - echo "Igny8 CRON HANDLER: SUCCESS - Created cluster record with ID: " . $cluster_id . "
"; - - // Trigger taxonomy term creation for AI-generated cluster (same as AJAX handler) - echo "Igny8 CRON HANDLER: Triggering taxonomy term creation
"; - try { - do_action('igny8_cluster_added', $cluster_id); - echo "Igny8 CRON HANDLER: SUCCESS - Taxonomy term creation triggered
"; - igny8_log_ai_event('Cron Cluster Taxonomy Triggered', 'planner', 'clustering', 'info', 'Triggered igny8_cluster_added action', "Cluster: {$cluster_data['name']} (ID: {$cluster_id})"); - } catch (Exception $e) { - echo "Igny8 CRON HANDLER: WARNING - Taxonomy term creation failed: " . $e->getMessage() . "
"; - igny8_log_ai_event('Cron Cluster Taxonomy Failed', 'planner', 'clustering', 'warning', 'Taxonomy term creation failed', "Cluster: {$cluster_data['name']} (ID: {$cluster_id}), Error: " . $e->getMessage()); - } catch (Throwable $e) { - echo "Igny8 CRON HANDLER: ERROR - Fatal error in taxonomy term creation: " . $e->getMessage() . "
"; - igny8_log_ai_event('Cron Cluster Taxonomy Fatal Error', 'planner', 'clustering', 'error', 'Fatal error in taxonomy term creation', "Cluster: {$cluster_data['name']} (ID: {$cluster_id}), Error: " . $e->getMessage()); - } - - // Log cluster creation - igny8_log_ai_event('Cron Cluster Created', 'planner', 'clustering', 'success', 'Cluster created successfully', "Cluster: {$cluster_data['name']} (ID: {$cluster_id})"); - - // Update keywords with cluster_id (same as AJAX handler) - if (isset($cluster_data['keywords']) && is_array($cluster_data['keywords'])) { - echo "Igny8 CRON HANDLER: Processing " . count($cluster_data['keywords']) . " keywords for this cluster
"; - - foreach ($cluster_data['keywords'] as $keyword_name) { - echo "Igny8 CRON HANDLER: Looking for keyword: " . $keyword_name . "
"; - - $update_result = $wpdb->update( - $wpdb->prefix . 'igny8_keywords', - ['cluster_id' => $cluster_id], - ['keyword' => $keyword_name], - ['%d'], - ['%s'] - ); - - if ($update_result !== false) { - $keywords_processed++; - echo "Igny8 CRON HANDLER: SUCCESS - Assigned keyword '" . $keyword_name . "' to cluster ID " . $cluster_id . "
"; - } else { - echo "Igny8 CRON HANDLER: ERROR - Failed to update keyword '" . $keyword_name . "': " . $wpdb->last_error . "
"; - } - } - - // Log keyword updates - igny8_log_ai_event('Cron Keywords Updated', 'planner', 'clustering', 'success', 'Keywords assigned to cluster', "Cluster: {$cluster_data['name']}, Keywords: " . count($cluster_data['keywords'])); - } else { - echo "Igny8 CRON HANDLER: WARNING - No keywords found in cluster data
"; - } - - // Update cluster metrics (same as AJAX handler) - echo "Igny8 CRON HANDLER: Updating cluster metrics
"; - try { - $metrics_result = igny8_update_cluster_metrics($cluster_id); - if ($metrics_result) { - echo "Igny8 CRON HANDLER: SUCCESS - Cluster metrics updated
"; - igny8_log_ai_event('Cron Metrics Updated', 'planner', 'clustering', 'success', 'Cluster metrics calculated', "Cluster: {$cluster_data['name']}"); - } else { - echo "Igny8 CRON HANDLER: WARNING - Failed to update cluster metrics
"; - igny8_log_ai_event('Cron Metrics Update Failed', 'planner', 'clustering', 'warning', 'Failed to update cluster metrics', "Cluster: {$cluster_data['name']}"); - } - } catch (Exception $e) { - echo "Igny8 CRON HANDLER: ERROR - Exception during metrics update: " . $e->getMessage() . "
"; - igny8_log_ai_event('Cron Metrics Update Error', 'planner', 'clustering', 'error', 'Exception during metrics update', "Cluster: {$cluster_data['name']}, Error: " . $e->getMessage()); - } - } else { - // Log cluster creation failure - echo "Igny8 CRON HANDLER: ERROR - Failed to create cluster record: " . $wpdb->last_error . "
"; - igny8_log_ai_event('Cron Cluster Creation Failed', 'planner', 'clustering', 'error', 'Failed to create cluster in database', "Cluster: {$cluster_data['name']}, Error: " . $wpdb->last_error); - } - - } catch (Exception $e) { - echo "Igny8 CRON HANDLER: ERROR - Exception processing cluster '{$cluster_data['name']}': " . $e->getMessage() . "
"; - igny8_log_ai_event('Cron Cluster Processing Error', 'planner', 'clustering', 'error', 'Exception processing cluster', "Cluster: {$cluster_data['name']}, Error: " . $e->getMessage()); - echo "Igny8 CRON HANDLER: Continuing with next cluster
"; - continue; - } catch (Throwable $e) { - echo "Igny8 CRON HANDLER: FATAL ERROR - Fatal error processing cluster '{$cluster_data['name']}': " . $e->getMessage() . "
"; - igny8_log_ai_event('Cron Cluster Processing Fatal Error', 'planner', 'clustering', 'error', 'Fatal error processing cluster', "Cluster: {$cluster_data['name']}, Error: " . $e->getMessage()); - echo "Igny8 CRON HANDLER: Continuing with next cluster
"; - continue; - } - } - - echo "Igny8 CRON HANDLER: Clustering completed successfully
"; - echo "Igny8 CRON HANDLER: Database clusters created: " . $clusters_created . "
"; - echo "Igny8 CRON HANDLER: Keywords processed: " . $keywords_processed . "
"; - - // Verify the results by checking database - echo "Igny8 CRON HANDLER: Verifying results in database
"; - $verify_clusters = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_clusters WHERE status = 'active'"); - $verify_keywords = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_keywords WHERE cluster_id IS NOT NULL AND cluster_id > 0"); - $verify_taxonomy_terms = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id WHERE tt.taxonomy = 'clusters'"); - - echo "Igny8 CRON HANDLER: Total active clusters in database: " . $verify_clusters . "
"; - echo "Igny8 CRON HANDLER: Total clustered keywords in database: " . $verify_keywords . "
"; - echo "Igny8 CRON HANDLER: Total taxonomy terms for clusters: " . $verify_taxonomy_terms . "
"; - - // Log completion with verification - igny8_log_ai_event('Cron Auto Cluster Complete', 'planner', 'auto_cluster', 'success', 'Cron automated clustering completed', 'Database clusters: ' . $clusters_created . ', Keywords processed: ' . $keywords_processed . ', Verified clusters: ' . $verify_clusters . ', Verified keywords: ' . $verify_keywords . ', Verified taxonomy terms: ' . $verify_taxonomy_terms); - - // Set global variables for detailed logging - $GLOBALS['igny8_cron_processed_count'] = $keywords_processed; - $GLOBALS['igny8_cron_result_details'] = "Processed {$keywords_processed} keywords, created {$clusters_created} clusters"; - - } catch (Exception $e) { - echo "Igny8 CRON HANDLER: ERROR - Exception during AI processing: " . $e->getMessage() . "
"; - igny8_log_ai_event('Cron Auto Cluster Failed', 'planner', 'auto_cluster', 'error', 'Exception during AI processing', $e->getMessage()); - - // Set global variables for detailed logging (failure case) - $GLOBALS['igny8_cron_processed_count'] = 0; - $GLOBALS['igny8_cron_result_details'] = "FAILED: " . $e->getMessage(); - - echo ""; - return; - } - echo "Igny8 CRON HANDLER: auto_cluster completed
"; - echo ""; // Close the handler div - - // Restore error reporting - error_reporting($old_error_reporting); -} - -/** - * Auto Generate Ideas Cron Handler - * - * Automatically generates ideas from clusters without ideas. - */ -function igny8_auto_generate_ideas_cron_handler() { - // Suppress PHP warnings for model rates in cron context - $old_error_reporting = error_reporting(); - error_reporting($old_error_reporting & ~E_WARNING); - - echo ""; - echo "Igny8 CRON HANDLER: auto_generate_ideas started"; - return; - } - echo "Igny8 CRON HANDLER: Automation enabled, continuing
"; - error_log("Igny8 CRON HANDLER: auto_generate_ideas started"); - - // Check if automation is enabled via cron settings - echo "Igny8 CRON HANDLER: Checking if automation is enabled
"; - $cron_settings = get_option('igny8_cron_settings', []); - $job_settings = $cron_settings['igny8_auto_generate_ideas_cron'] ?? []; - $auto_ideas_enabled = $job_settings['enabled'] ?? false; - echo "Igny8 CRON HANDLER: auto_ideas_enabled = " . ($auto_ideas_enabled ? 'enabled' : 'disabled') . "
"; - - if (!$auto_ideas_enabled) { - echo "Igny8 CRON HANDLER: Automation disabled, exiting
"; - error_log("Igny8 CRON HANDLER: auto_generate_ideas automation disabled"); - echo "
"; - - // Check if AI mode is enabled - echo "Igny8 CRON HANDLER: Checking AI mode
"; - $planner_mode = igny8_get_ai_setting('planner_mode', 'manual'); - echo "Igny8 CRON HANDLER: planner_mode = " . $planner_mode . "
"; - - if ($planner_mode !== 'ai') { - echo "Igny8 CRON HANDLER: AI mode disabled, exiting
"; - error_log("Igny8 CRON HANDLER: AI mode disabled"); - echo ""; - return; - } - echo "Igny8 CRON HANDLER: AI mode enabled, continuing
"; - - global $wpdb; - echo "Igny8 CRON HANDLER: Database connection established
"; - - // Get clusters without ideas (use dynamic limit from settings) - $limit = $GLOBALS['igny8_cron_limit'] ?? null; - if ($limit === null) { - error_log('Igny8 Auto Cluster Cron: No limit set in Smart Automation Jobs table'); - $GLOBALS['igny8_cron_processed_count'] = 0; - $GLOBALS['igny8_cron_result_details'] = "FAILED: No limit set in Smart Automation Jobs table"; - return; - } - echo "Igny8 CRON HANDLER: Querying clusters without ideas (limit: $limit)
"; - $clusters_without_ideas = $wpdb->get_results(" - SELECT c.id FROM {$wpdb->prefix}igny8_clusters c - LEFT JOIN {$wpdb->prefix}igny8_content_ideas i ON c.id = i.keyword_cluster_id - WHERE i.id IS NULL - LIMIT $limit - "); - - echo "Igny8 CRON HANDLER: Found " . count($clusters_without_ideas) . " clusters without ideas
"; - - if (empty($clusters_without_ideas)) { - echo "Igny8 CRON HANDLER: No clusters without ideas found, exiting
"; - igny8_log_ai_event('Auto Generate Ideas Skipped', 'planner', 'auto_generate_ideas', 'info', 'No clusters without ideas found', 'All clusters already have ideas'); - echo ""; - return; - } - - $cluster_ids = array_column($clusters_without_ideas, 'id'); - echo "Igny8 CRON HANDLER: Processing " . count($cluster_ids) . " clusters
"; - - // Log automation start - echo "Igny8 CRON HANDLER: Logging automation start
"; - igny8_log_ai_event('Auto Generate Ideas Started', 'planner', 'auto_generate_ideas', 'info', 'Starting automated idea generation', 'Clusters: ' . count($cluster_ids)); - - // Set up user context for permissions - echo "Igny8 CRON HANDLER: Setting up user context
"; - if (!current_user_can('manage_options')) { - // Get admin user for cron context - $admin_users = get_users(['role' => 'administrator', 'number' => 1]); - if (!empty($admin_users)) { - wp_set_current_user($admin_users[0]->ID); - echo "Igny8 CRON HANDLER: Set admin user context for permissions
"; - } - } - - // Simulate AJAX request to existing AI function - echo "Igny8 CRON HANDLER: Starting AJAX simulation
"; - $_POST['cluster_ids'] = json_encode($cluster_ids); - $_POST['nonce'] = wp_create_nonce('igny8_planner_settings'); - - // Capture output to prevent JSON response in cron - ob_start(); - igny8_ajax_ai_generate_ideas(); - $output = ob_get_clean(); - - echo "Igny8 CRON HANDLER: AJAX execution completed
"; - - // Parse JSON response - $response = json_decode($output, true); - echo "Igny8 CRON HANDLER: Response parsed, success = " . ($response['success'] ?? 'false') . "
"; - - if ($response && $response['success']) { - $ideas_created = $response['data']['ideas_created'] ?? count($cluster_ids); - echo "Igny8 CRON HANDLER: SUCCESS - Ideas generated: " . $ideas_created . "
"; - igny8_log_ai_event('Auto Generate Ideas Complete', 'planner', 'auto_generate_ideas', 'success', 'Automated idea generation completed', 'Ideas created: ' . $ideas_created); - - // Set global variables for detailed logging - $GLOBALS['igny8_cron_processed_count'] = $ideas_created; - $GLOBALS['igny8_cron_result_details'] = "Processed {$ideas_created} clusters, created {$ideas_created} ideas"; - } else { - $error_msg = $response['data']['message'] ?? 'Unknown error'; - echo "Igny8 CRON HANDLER: ERROR - " . $error_msg . "
"; - igny8_log_ai_event('Auto Generate Ideas Failed', 'planner', 'auto_generate_ideas', 'error', 'Automated idea generation failed', $error_msg); - - // Set global variables for detailed logging (failure case) - $GLOBALS['igny8_cron_processed_count'] = 0; - $GLOBALS['igny8_cron_result_details'] = "FAILED: " . $error_msg; - } - - echo "Igny8 CRON HANDLER: auto_generate_ideas completed
"; - echo ""; // Close the handler div - - // Restore error reporting - error_reporting($old_error_reporting); -} - -/** - * Auto Queue Cron Handler - * - * Automatically queues new ideas for content generation. - */ -function igny8_auto_queue_cron_handler() { - // Suppress PHP warnings for model rates in cron context - $old_error_reporting = error_reporting(); - error_reporting($old_error_reporting & ~E_WARNING); - - echo ""; - echo "Igny8 CRON HANDLER: auto_queue started"; - return; - } - echo "Igny8 CRON HANDLER: Automation enabled, continuing
"; - error_log("Igny8 CRON HANDLER: auto_queue started"); - - // Check if automation is enabled via cron settings - echo "Igny8 CRON HANDLER: Checking if automation is enabled
"; - $cron_settings = get_option('igny8_cron_settings', []); - $job_settings = $cron_settings['igny8_auto_queue_cron'] ?? []; - $auto_queue_enabled = $job_settings['enabled'] ?? false; - echo "Igny8 CRON HANDLER: auto_queue_enabled = " . ($auto_queue_enabled ? 'enabled' : 'disabled') . "
"; - - if (!$auto_queue_enabled) { - echo "Igny8 CRON HANDLER: Automation disabled, exiting
"; - error_log("Igny8 CRON HANDLER: auto_queue automation disabled"); - echo "
"; - - global $wpdb; - echo "Igny8 CRON HANDLER: Database connection established
"; - - // Get new ideas (use dynamic limit from settings) - $limit = $GLOBALS['igny8_cron_limit'] ?? null; - if ($limit === null) { - // Try to get limit from Smart Automation Jobs table directly - $cron_limits = get_option('igny8_cron_limits', []); - $limit = $cron_limits['igny8_auto_queue_cron'] ?? null; - - if ($limit === null) { - error_log('Igny8 Auto Queue Cron: No limit set in Smart Automation Jobs table'); - $GLOBALS['igny8_cron_processed_count'] = 0; - $GLOBALS['igny8_cron_result_details'] = "FAILED: No limit set in Smart Automation Jobs table"; - return; - } - - echo "Igny8 CRON HANDLER: Using limit from Smart Automation Jobs table: $limit
"; - } - echo "Igny8 CRON HANDLER: Querying new ideas (limit: $limit)
"; - $new_ideas = $wpdb->get_results(" - SELECT id FROM {$wpdb->prefix}igny8_content_ideas - WHERE status = 'new' - LIMIT $limit - "); - - echo "Igny8 CRON HANDLER: Found " . count($new_ideas) . " new ideas
"; - - if (empty($new_ideas)) { - echo "Igny8 CRON HANDLER: No new ideas found, exiting
"; - igny8_log_ai_event('Auto Queue Skipped', 'planner', 'auto_queue', 'info', 'No new ideas found', 'All ideas are already queued'); - echo ""; - return; - } - - $idea_ids = array_column($new_ideas, 'id'); - echo "Igny8 CRON HANDLER: Processing " . count($idea_ids) . " ideas
"; - - // Log automation start - echo "Igny8 CRON HANDLER: Logging automation start
"; - igny8_log_ai_event('Auto Queue Started', 'planner', 'auto_queue', 'info', 'Starting automated queueing', 'Ideas: ' . count($idea_ids)); - - // Set up user context for permissions - echo "Igny8 CRON HANDLER: Setting up user context
"; - if (!current_user_can('edit_posts')) { - // Get admin user for cron context - $admin_users = get_users(['role' => 'administrator', 'number' => 1]); - if (!empty($admin_users)) { - wp_set_current_user($admin_users[0]->ID); - echo "Igny8 CRON HANDLER: Set admin user context for permissions
"; - } - } - - // Skip AJAX simulation and go directly to function calls - // (AJAX has issues in cron context with nonce verification) - echo "Igny8 CRON HANDLER: Skipping AJAX simulation (cron context issues)
"; - echo "Igny8 CRON HANDLER: Using direct function calls
"; - - $created = []; - $skipped = []; - $failed = []; - - foreach ($idea_ids as $idea_id) { - echo "Igny8 CRON HANDLER: Processing idea ID: " . $idea_id . "
"; - $result = igny8_create_task_from_idea($idea_id); - - if ($result['success']) { - if (!empty($result['task_id'])) { - $created[] = $result['task_id']; - echo "Igny8 CRON HANDLER: SUCCESS - Created task ID: " . $result['task_id'] . "
"; - } else { - $skipped[] = $idea_id; - echo "Igny8 CRON HANDLER: SKIPPED - " . $result['message'] . "
"; - } - } else { - $failed[] = $idea_id; - echo "Igny8 CRON HANDLER: FAILED - " . $result['message'] . "
"; - } - } - - // Update metrics for processed ideas - foreach ($idea_ids as $idea_id) { - igny8_update_idea_metrics($idea_id); - } - - $created_count = count($created); - $skipped_count = count($skipped); - $failed_count = count($failed); - - echo "Igny8 CRON HANDLER: DIRECT SUCCESS - Tasks created: " . $created_count . ", Skipped: " . $skipped_count . ", Failed: " . $failed_count . "
"; - igny8_log_ai_event('Auto Queue Complete', 'planner', 'auto_queue', 'success', 'Automated queueing completed (direct)', 'Tasks created: ' . $created_count . ', Skipped: ' . $skipped_count . ', Failed: ' . $failed_count); - - // Set global variables for detailed logging - $GLOBALS['igny8_cron_processed_count'] = $created_count; - $GLOBALS['igny8_cron_result_details'] = "Processed {$created_count} ideas, created {$created_count} tasks"; - - echo "Igny8 CRON HANDLER: auto_queue completed
"; - echo ""; // Close the handler div - - // Restore error reporting - error_reporting($old_error_reporting); -} - -/** - * Auto Generate Content Cron Handler - * - * Automatically generates content from queued tasks. - */ -function igny8_auto_generate_content_cron_handler() { - // Suppress PHP warnings for model rates in cron context - $old_error_reporting = error_reporting(); - error_reporting($old_error_reporting & ~E_WARNING); - - echo ""; - echo "Igny8 CRON HANDLER: auto_generate_content started"; - return; - } - echo "Igny8 CRON HANDLER: Automation enabled, continuing
"; - error_log("Igny8 CRON HANDLER: auto_generate_content started"); - - // Check if automation is enabled via cron settings - echo "Igny8 CRON HANDLER: Checking if automation is enabled
"; - $cron_settings = get_option('igny8_cron_settings', []); - $job_settings = $cron_settings['igny8_auto_generate_content_cron'] ?? []; - $auto_content_enabled = $job_settings['enabled'] ?? false; - echo "Igny8 CRON HANDLER: auto_content_enabled = " . ($auto_content_enabled ? 'enabled' : 'disabled') . "
"; - - if (!$auto_content_enabled) { - echo "Igny8 CRON HANDLER: Automation disabled, exiting
"; - error_log("Igny8 CRON HANDLER: auto_generate_content automation disabled"); - echo "
"; - - // Check if AI mode is enabled - echo "Igny8 CRON HANDLER: Checking AI mode
"; - $writer_mode = igny8_get_ai_setting('writer_mode', 'manual'); - echo "Igny8 CRON HANDLER: writer_mode = " . $writer_mode . "
"; - - if ($writer_mode !== 'ai') { - echo "Igny8 CRON HANDLER: AI mode disabled, exiting
"; - error_log("Igny8 CRON HANDLER: AI mode disabled"); - echo ""; - return; - } - echo "Igny8 CRON HANDLER: AI mode enabled, continuing
"; - - global $wpdb; - echo "Igny8 CRON HANDLER: Database connection established
"; - - // Get queued tasks (use dynamic limit from settings) - $limit = $GLOBALS['igny8_cron_limit'] ?? null; - if ($limit === null) { - // Try to get limit from Smart Automation Jobs table directly - $cron_limits = get_option('igny8_cron_limits', []); - $limit = $cron_limits['igny8_auto_generate_content_cron'] ?? null; - - if ($limit === null) { - error_log('Igny8 Auto Generate Content Cron: No limit set in Smart Automation Jobs table'); - $GLOBALS['igny8_cron_processed_count'] = 0; - $GLOBALS['igny8_cron_result_details'] = "FAILED: No limit set in Smart Automation Jobs table"; - return; - } - - echo "Igny8 CRON HANDLER: Using limit from Smart Automation Jobs table: $limit
"; - } - echo "Igny8 CRON HANDLER: Querying queued tasks (limit: $limit)
"; - $queued_tasks = $wpdb->get_results(" - SELECT id FROM {$wpdb->prefix}igny8_tasks - WHERE status = 'queued' - LIMIT $limit - "); - - echo "Igny8 CRON HANDLER: Found " . count($queued_tasks) . " queued tasks
"; - - if (empty($queued_tasks)) { - echo "Igny8 CRON HANDLER: No queued tasks found, exiting
"; - igny8_log_ai_event('Auto Generate Content Skipped', 'writer', 'auto_generate_content', 'info', 'No queued tasks found', 'All tasks are already processed'); - echo ""; - return; - } - - $task_ids = array_column($queued_tasks, 'id'); - echo "Igny8 CRON HANDLER: Processing " . count($task_ids) . " tasks
"; - - // Log automation start - echo "Igny8 CRON HANDLER: Logging automation start
"; - igny8_log_ai_event('Auto Generate Content Started', 'writer', 'auto_generate_content', 'info', 'Starting automated content generation', 'Tasks: ' . count($task_ids)); - - // Set up user context for permissions - echo "Igny8 CRON HANDLER: Setting up user context
"; - if (!current_user_can('manage_options')) { - // Get admin user for cron context - $admin_users = get_users(['role' => 'administrator', 'number' => 1]); - if (!empty($admin_users)) { - wp_set_current_user($admin_users[0]->ID); - echo "Igny8 CRON HANDLER: Set admin user context for permissions
"; - } - } - - $completed = 0; - $failed = 0; - - foreach ($task_ids as $task_id) { - echo "π SECTION 1: PROCESSING TASK " . $task_id . "
"; - echo "Igny8 CRON HANDLER: Processing task ID: " . $task_id . "
"; - echo "Igny8 CRON HANDLER: Sending to AI for content generation...
"; - - // Log AI request initiation - igny8_log_ai_event('AI Request Initiated', 'writer', 'auto_content_generation', 'info', 'Starting AI content generation', 'Task ID: ' . $task_id); - - // Get AI configuration for logging - $api_key = get_option('igny8_api_key'); - $model = get_option('igny8_model', 'gpt-4.1'); - - // Log AI request details - echo "Igny8 CRON HANDLER: AI Request Details - Model: " . $model . "
"; - echo "Igny8 CRON HANDLER: API Key Status: " . (empty($api_key) ? 'Missing' : 'Configured') . "
"; - igny8_log_ai_event('AI Request Details', 'writer', 'auto_content_generation', 'info', 'AI request configuration', 'Model: ' . $model . ', API Key: ' . (empty($api_key) ? 'Missing' : 'Configured')); - - // Log the actual AI request being made - echo "Igny8 CRON HANDLER: Making AI API call to OpenAI...
"; - igny8_log_ai_event('AI API Call', 'writer', 'auto_content_generation', 'info', 'Calling OpenAI API', 'Model: ' . $model . ', Task: ' . $task_id); - - // Simulate AJAX request to content generation function - $_POST['task_id'] = $task_id; - $_POST['nonce'] = wp_create_nonce('igny8_writer_settings'); - - // Capture the response from AI call - ob_start(); - igny8_ajax_ai_generate_content(); - $ai_response = ob_get_clean(); - - // Log AI response - echo "Igny8 CRON HANDLER: AI Response received
"; - echo "Igny8 CRON HANDLER: Response Length: " . strlen($ai_response) . " characters
"; - igny8_log_ai_event('AI Response Received', 'writer', 'auto_content_generation', 'info', 'AI processing completed', 'Response captured for task: ' . $task_id); - - // Parse the AI response to extract success/error status - $json_response = null; - $response_status = 'unknown'; - $error_message = ''; - - // Try multiple methods to extract JSON from response - if (preg_match('/\{.*\}/s', $ai_response, $matches)) { - $json_response = json_decode($matches[0], true); - } - - // If no JSON found, try to detect response type from content - if (!$json_response) { - if (strpos($ai_response, 'success') !== false && strpos($ai_response, 'true') !== false) { - $response_status = 'success'; - echo "Igny8 CRON HANDLER: β AI Response - SUCCESS (detected from content)
"; - igny8_log_ai_event('AI Response Success', 'writer', 'auto_content_generation', 'success', 'AI content generation successful', 'Task ID: ' . $task_id); - } elseif (strpos($ai_response, 'error') !== false || strpos($ai_response, 'failed') !== false) { - $response_status = 'error'; - $error_message = 'Error detected in response content'; - echo "Igny8 CRON HANDLER: β AI Response - FAILED (detected from content)
"; - echo "Igny8 CRON HANDLER: Error: " . $error_message . "
"; - igny8_log_ai_event('AI Response Failed', 'writer', 'auto_content_generation', 'error', 'AI content generation failed', 'Task ID: ' . $task_id . ', Error: ' . $error_message); - } else { - $response_status = 'unknown'; - echo "Igny8 CRON HANDLER: β οΈ AI Response - UNKNOWN FORMAT
"; - igny8_log_ai_event('AI Response Unknown', 'writer', 'auto_content_generation', 'warning', 'AI response format not recognized', 'Task ID: ' . $task_id . ', Raw response: ' . substr($ai_response, 0, 200)); - } - } else { - // JSON response found, parse it - if (isset($json_response['success']) && $json_response['success']) { - $response_status = 'success'; - echo "Igny8 CRON HANDLER: β AI Response - SUCCESS (JSON parsed)
"; - igny8_log_ai_event('AI Response Success', 'writer', 'auto_content_generation', 'success', 'AI content generation successful', 'Task ID: ' . $task_id); - } else { - $response_status = 'error'; - $error_message = $json_response['data']['message'] ?? $json_response['message'] ?? 'Unknown error'; - echo "Igny8 CRON HANDLER: β AI Response - FAILED (JSON parsed)
"; - echo "Igny8 CRON HANDLER: Error: " . $error_message . "
"; - igny8_log_ai_event('AI Response Failed', 'writer', 'auto_content_generation', 'error', 'AI content generation failed', 'Task ID: ' . $task_id . ', Error: ' . $error_message); - } - } - - // Log response details for debugging - echo "Igny8 CRON HANDLER: Response Status: " . $response_status . "
"; - echo "Igny8 CRON HANDLER: Response Length: " . strlen($ai_response) . " characters
"; - igny8_log_ai_event('AI Response Analysis', 'writer', 'auto_content_generation', 'info', 'Response analysis completed', 'Status: ' . $response_status . ', Length: ' . strlen($ai_response) . ', Task: ' . $task_id); - - // Get the actual post ID from the most recently created post by this user - $recent_posts = get_posts([ - 'post_type' => 'post', - 'post_status' => 'draft', - 'author' => get_current_user_id(), - 'numberposts' => 1, - 'orderby' => 'date', - 'order' => 'DESC' - ]); - - $actual_post_id = !empty($recent_posts) ? $recent_posts[0]->ID : null; - - $response = ['success' => true, 'data' => ['post_id' => $actual_post_id]]; - - if ($response && $response['success']) { - echo "Igny8 CRON HANDLER: β AI Response received successfully
"; - - echo "π§ SECTION 2: PROCESSING AI RESPONSE & SAVING DATA
"; - echo "Igny8 CRON HANDLER: Processing AI response data...
"; - - // The detailed field processing logs will come from igny8_create_post_from_ai_response function - // which already has all the debugging output we added - - echo "β SECTION 3: FINAL VERIFICATION & SUCCESS SUMMARY
"; - - if (!empty($response['data']['post_id'])) { - $post_id = $response['data']['post_id']; - echo "Igny8 CRON HANDLER: β Post Created Successfully - ID: " . $post_id . "
"; - echo "Igny8 CRON HANDLER: Checking fields for post ID: " . $post_id . "
"; - - // Verify all saved fields - $meta_title = get_post_meta($post_id, '_igny8_meta_title', true); - $meta_description = get_post_meta($post_id, '_igny8_meta_description', true); - $primary_keywords = get_post_meta($post_id, '_igny8_primary_keywords', true); - $secondary_keywords = get_post_meta($post_id, '_igny8_secondary_keywords', true); - $word_count = get_post_meta($post_id, '_igny8_word_count', true); - - echo "Igny8 CRON HANDLER: β Meta Title: " . (!empty($meta_title) ? 'Saved' : 'Missing') . "
"; - echo "Igny8 CRON HANDLER: β Meta Description: " . (!empty($meta_description) ? 'Saved' : 'Missing') . "
"; - echo "Igny8 CRON HANDLER: β Primary Keywords: " . (!empty($primary_keywords) ? 'Saved' : 'Missing') . "
"; - echo "Igny8 CRON HANDLER: β Secondary Keywords: " . (!empty($secondary_keywords) ? 'Saved' : 'Missing') . "
"; - echo "Igny8 CRON HANDLER: β Word Count: " . (!empty($word_count) ? 'Saved' : 'Missing') . "
"; - - // Verify taxonomies - $cluster_terms = wp_get_object_terms($post_id, 'clusters'); - $sector_terms = wp_get_object_terms($post_id, 'sectors'); - - echo "Igny8 CRON HANDLER: β Cluster Taxonomy: " . (!empty($cluster_terms) && !is_wp_error($cluster_terms) ? 'Associated' : 'Not Associated') . "
"; - echo "Igny8 CRON HANDLER: β Sector Taxonomy: " . (!empty($sector_terms) && !is_wp_error($sector_terms) ? 'Associated' : 'Not Associated') . "
"; - - // Verify Yoast meta - $yoast_meta = get_post_meta($post_id, '_yoast_wpseo_metadesc', true); - echo "Igny8 CRON HANDLER: β Yoast Meta Description: " . (!empty($yoast_meta) ? 'Saved' : 'Missing') . "
"; - - $completed++; - echo "π TASK " . $task_id . " COMPLETED SUCCESSFULLY!
"; - } else { - echo "β Post creation failed - No post ID returned
"; - $failed++; - } - } else { - $failed++; - $error_msg = $response['data']['message'] ?? 'Unknown error'; - echo "β TASK " . $task_id . " FAILED: " . $error_msg . "
"; - } - - echo "
"; - } - - echo "Igny8 CRON HANDLER: Content generation completed - Success: " . $completed . ", Failed: " . $failed . "
"; - - if ($completed > 0) { - igny8_log_ai_event('Auto Generate Content Complete', 'writer', 'auto_generate_content', 'success', 'Automated content generation completed', 'Content generated: ' . $completed . ', Failed: ' . $failed); - - // Set global variables for detailed logging - $GLOBALS['igny8_cron_processed_count'] = $completed; - $GLOBALS['igny8_cron_result_details'] = "Processed {$completed} tasks, generated {$completed} content pieces"; - echo "Igny8 AUTO GENERATE CONTENT HANDLER: Set global variables - processed_count: $completed, result_details: Processed {$completed} tasks, generated {$completed} content pieces
"; - } else { - igny8_log_ai_event('Auto Generate Content Failed', 'writer', 'auto_generate_content', 'error', 'Automated content generation failed', 'No content was generated'); - - // Set global variables for detailed logging (failure case) - $GLOBALS['igny8_cron_processed_count'] = 0; - $GLOBALS['igny8_cron_result_details'] = "FAILED: No content was generated"; - } - - echo "Igny8 CRON HANDLER: auto_generate_content completed
"; - echo ""; // Close the handler div - - // Restore error reporting - error_reporting($old_error_reporting); -} - -/** - * Auto Publish Drafts Cron Handler - * - * Automatically publishes completed draft posts. - */ -function igny8_auto_publish_drafts_cron_handler() { - echo ""; - echo "Igny8 CRON HANDLER: auto_publish_drafts started"; - return; - } - echo "Igny8 CRON HANDLER: Automation enabled, continuing
"; - error_log("Igny8 CRON HANDLER: auto_publish_drafts started"); - - // Check if automation is enabled via cron settings - echo "Igny8 CRON HANDLER: Checking if automation is enabled
"; - $cron_settings = get_option('igny8_cron_settings', []); - $job_settings = $cron_settings['igny8_auto_publish_drafts_cron'] ?? []; - $auto_publish_enabled = $job_settings['enabled'] ?? false; - echo "Igny8 CRON HANDLER: auto_publish_enabled = " . ($auto_publish_enabled ? 'enabled' : 'disabled') . "
"; - - if (!$auto_publish_enabled) { - echo "Igny8 CRON HANDLER: Automation disabled, exiting
"; - error_log("Igny8 CRON HANDLER: auto_publish_drafts automation disabled"); - echo "
"; - - global $wpdb; - - // Get completed tasks with published content (use admin limit) - $limit = $GLOBALS['igny8_cron_limit'] ?? null; - if ($limit === null) { - // Try to get limit from Smart Automation Jobs table directly - $cron_limits = get_option('igny8_cron_limits', []); - $limit = $cron_limits['igny8_auto_publish_drafts_cron'] ?? null; - - if ($limit === null) { - error_log('Igny8 Auto Publish Drafts Cron: No limit set in Smart Automation Jobs table'); - $GLOBALS['igny8_cron_processed_count'] = 0; - $GLOBALS['igny8_cron_result_details'] = "FAILED: No limit set in Smart Automation Jobs table"; - return; - } - - echo "Igny8 CRON HANDLER: Using limit from Smart Automation Jobs table: $limit
"; - } - echo "Igny8 CRON HANDLER: Querying completed tasks with draft posts (limit: $limit)
"; - $completed_tasks = $wpdb->get_results(" - SELECT t.id, t.assigned_post_id FROM {$wpdb->prefix}igny8_tasks t - INNER JOIN {$wpdb->posts} p ON t.assigned_post_id = p.ID - WHERE t.status = 'completed' - AND p.post_status = 'draft' - LIMIT $limit - "); - - echo "Igny8 CRON HANDLER: Found " . count($completed_tasks) . " completed tasks with draft posts
"; - - if (empty($completed_tasks)) { - echo "Igny8 CRON HANDLER: No draft posts found, exiting
"; - igny8_log_ai_event('Auto Publish Drafts Skipped', 'writer', 'auto_publish_drafts', 'info', 'No draft posts found', 'All content is already published'); - echo ""; - return; - } - - $post_ids = array_column($completed_tasks, 'assigned_post_id'); - - // Log automation start - igny8_log_ai_event('Auto Publish Drafts Started', 'writer', 'auto_publish_drafts', 'info', 'Starting automated publishing', 'Posts: ' . count($post_ids)); - - $published = 0; - echo "Igny8 CRON HANDLER: Processing " . count($post_ids) . " draft posts
"; - - foreach ($post_ids as $post_id) { - echo "Igny8 CRON HANDLER: Publishing post ID: " . $post_id . "
"; - $result = wp_update_post([ - 'ID' => $post_id, - 'post_status' => 'publish' - ]); - - if (!is_wp_error($result) && $result) { - $published++; - echo "Igny8 CRON HANDLER: β SUCCESS - Published post ID: " . $post_id . "
"; - } else { - echo "Igny8 CRON HANDLER: β FAILED - Could not publish post ID: " . $post_id . "
"; - } - } - - echo "Igny8 CRON HANDLER: Publishing completed - Published: " . $published . " posts
"; - - if ($published > 0) { - igny8_log_ai_event('Auto Publish Drafts Complete', 'writer', 'auto_publish_drafts', 'success', 'Automated publishing completed', 'Posts published: ' . $published); - - // Set global variables for detailed logging - $GLOBALS['igny8_cron_processed_count'] = $published; - $GLOBALS['igny8_cron_result_details'] = "Processed {$published} drafts, published {$published} posts"; - } else { - igny8_log_ai_event('Auto Publish Drafts Failed', 'writer', 'auto_publish_drafts', 'error', 'Automated publishing failed', 'No posts were published'); - - // Set global variables for detailed logging (failure case) - $GLOBALS['igny8_cron_processed_count'] = 0; - $GLOBALS['igny8_cron_result_details'] = "FAILED: No posts were published"; - } - - echo "Igny8 CRON HANDLER: auto_publish_drafts completed
"; - echo ""; // Close the handler div -} - -/** - * Auto Optimizer Cron Handler (NEW) - * - * Automatically optimizes content and keywords. - */ -function igny8_auto_optimizer_cron_handler() { - // Check if automation is enabled - if (igny8_get_ai_setting('auto_optimizer_enabled', 'disabled') !== 'enabled') { - return; - } - - global $wpdb; - - // Get posts that need optimization (use admin limit) - $limit = $GLOBALS['igny8_cron_limit'] ?? null; - if ($limit === null) { - // Try to get limit from Smart Automation Jobs table directly - $cron_limits = get_option('igny8_cron_limits', []); - $limit = $cron_limits['igny8_auto_optimizer_cron'] ?? null; - - if ($limit === null) { - error_log('Igny8 Auto Optimizer Cron: No limit set in Smart Automation Jobs table'); - $GLOBALS['igny8_cron_processed_count'] = 0; - $GLOBALS['igny8_cron_result_details'] = "FAILED: No limit set in Smart Automation Jobs table"; - return; - } - - echo "Igny8 CRON HANDLER: Using limit from Smart Automation Jobs table: $limit
"; - } - $posts_to_optimize = $wpdb->get_results(" - SELECT p.ID, p.post_title - FROM {$wpdb->posts} p - LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = 'igny8_optimized' - WHERE p.post_status = 'publish' - AND p.post_type IN ('post', 'page') - AND pm.meta_value IS NULL - LIMIT $limit - "); - - if (empty($posts_to_optimize)) { - igny8_log_ai_event('Auto Optimizer Skipped', 'optimizer', 'auto_optimizer', 'info', 'No posts need optimization', 'All posts are already optimized'); - return; - } - - $post_ids = array_column($posts_to_optimize, 'ID'); - - // Log automation start - igny8_log_ai_event('Auto Optimizer Started', 'optimizer', 'auto_optimizer', 'info', 'Starting automated optimization', 'Posts: ' . count($post_ids)); - - $optimized = 0; - foreach ($post_ids as $post_id) { - // Run optimization process - $result = igny8_optimize_post_content($post_id); - if ($result['success']) { - $optimized++; - // Mark as optimized - update_post_meta($post_id, 'igny8_optimized', current_time('mysql')); - } - } - - if ($optimized > 0) { - igny8_log_ai_event('Auto Optimizer Complete', 'optimizer', 'auto_optimizer', 'success', 'Automated optimization completed', 'Posts optimized: ' . $optimized); - } else { - igny8_log_ai_event('Auto Optimizer Failed', 'optimizer', 'auto_optimizer', 'error', 'Automated optimization failed', 'No posts were optimized'); - } -} - -/** - * Auto Recalc Cron Handler (NEW) - * - * Automatically recalculates metrics for all entities. - */ -function igny8_auto_recalc_cron_handler() { - global $wpdb; - - // Get entities that need recalculation - $limit = $GLOBALS['igny8_cron_limit'] ?? null; - if ($limit === null) { - error_log('Igny8 Auto Cluster Cron: No limit set in Smart Automation Jobs table'); - $GLOBALS['igny8_cron_processed_count'] = 0; - $GLOBALS['igny8_cron_result_details'] = "FAILED: No limit set in Smart Automation Jobs table"; - return; - } - $clusters_to_recalc = $wpdb->get_results(" - SELECT id FROM {$wpdb->prefix}igny8_clusters - WHERE status = 'active' - LIMIT $limit - "); - - $tasks_to_recalc = $wpdb->get_results(" - SELECT id FROM {$wpdb->prefix}igny8_tasks - WHERE status IN ('completed', 'in_progress') - LIMIT $limit - "); - - $total_recalc = count($clusters_to_recalc) + count($tasks_to_recalc); - - if ($total_recalc === 0) { - igny8_log_ai_event('Auto Recalc Skipped', 'analytics', 'auto_recalc', 'info', 'No entities need recalculation', 'All metrics are up to date'); - return; - } - - // Log automation start - igny8_log_ai_event('Auto Recalc Started', 'analytics', 'auto_recalc', 'info', 'Starting automated recalculation', 'Entities: ' . $total_recalc); - - $recalculated = 0; - - // Recalculate cluster metrics - foreach ($clusters_to_recalc as $cluster) { - igny8_update_cluster_metrics($cluster->id); - $recalculated++; - } - - // Recalculate task metrics - foreach ($tasks_to_recalc as $task) { - igny8_update_task_metrics($task->id); - $recalculated++; - } - - if ($recalculated > 0) { - igny8_log_ai_event('Auto Recalc Complete', 'analytics', 'auto_recalc', 'success', 'Automated recalculation completed', 'Entities recalculated: ' . $recalculated); - } else { - igny8_log_ai_event('Auto Recalc Failed', 'analytics', 'auto_recalc', 'error', 'Automated recalculation failed', 'No entities were recalculated'); - } -} - -/** - * Health Check Cron Handler (NEW) - * - * Performs system health checks, cleanup, and dependency validation. - */ -function igny8_health_check_cron_handler() { - global $wpdb; - - $health_issues = []; - $cleanup_performed = []; - $dependency_issues = []; - - // Check for orphaned records - $orphaned_keywords = $wpdb->get_var(" - SELECT COUNT(*) FROM {$wpdb->prefix}igny8_keywords k - LEFT JOIN {$wpdb->prefix}igny8_clusters c ON k.cluster_id = c.id - WHERE k.cluster_id IS NOT NULL AND k.cluster_id > 0 AND c.id IS NULL - "); - - if ($orphaned_keywords > 0) { - $health_issues[] = "Found $orphaned_keywords orphaned keywords"; - - // Clean up orphaned keywords - $wpdb->query(" - UPDATE {$wpdb->prefix}igny8_keywords - SET cluster_id = NULL - WHERE cluster_id IS NOT NULL - AND cluster_id > 0 - AND cluster_id NOT IN (SELECT id FROM {$wpdb->prefix}igny8_clusters) - "); - $cleanup_performed[] = "Cleaned up $orphaned_keywords orphaned keywords"; - } - - // Check for old failed AI tasks - $old_failed_tasks = $wpdb->get_var(" - SELECT COUNT(*) FROM {$wpdb->prefix}igny8_ai_queue - WHERE status = 'failed' - AND created_at < DATE_SUB(NOW(), INTERVAL 7 DAY) - "); - - if ($old_failed_tasks > 0) { - $wpdb->query(" - DELETE FROM {$wpdb->prefix}igny8_ai_queue - WHERE status = 'failed' - AND created_at < DATE_SUB(NOW(), INTERVAL 7 DAY) - "); - $cleanup_performed[] = "Cleaned up $old_failed_tasks old failed AI tasks"; - } - - // Dependency validation - check execution order - $dependency_issues = igny8_validate_automation_dependencies(); - - // Check database table sizes - $table_sizes = []; - $tables = ['igny8_keywords', 'igny8_clusters', 'igny8_content_ideas', 'igny8_tasks', 'igny8_ai_queue']; - - foreach ($tables as $table) { - $count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}$table"); - $table_sizes[$table] = $count; - } - - // Log health check results - if (empty($health_issues) && empty($dependency_issues)) { - igny8_log_ai_event('Health Check Passed', 'system', 'health_check', 'success', 'System health check completed', 'No issues found'); - } else { - $all_issues = array_merge($health_issues, $dependency_issues); - igny8_log_ai_event('Health Check Issues', 'system', 'health_check', 'warning', 'Health check found issues', implode('; ', $all_issues)); - } - - if (!empty($cleanup_performed)) { - igny8_log_ai_event('Health Check Cleanup', 'system', 'health_check', 'info', 'Cleanup performed', implode('; ', $cleanup_performed)); - } - - // Store health metrics - update_option('igny8_health_metrics', [ - 'last_check' => current_time('mysql'), - 'table_sizes' => $table_sizes, - 'issues_found' => count($health_issues), - 'dependency_issues' => count($dependency_issues), - 'cleanup_performed' => count($cleanup_performed) - ]); -} - -/** - * Validate automation dependencies and execution order - */ -function igny8_validate_automation_dependencies() { - $issues = []; - global $wpdb; - - // Check if clusters exist but no ideas generated - $clusters_without_ideas = $wpdb->get_var(" - SELECT COUNT(*) FROM {$wpdb->prefix}igny8_clusters c - LEFT JOIN {$wpdb->prefix}igny8_content_ideas i ON c.id = i.keyword_cluster_id - WHERE c.status = 'active' AND i.id IS NULL - "); - - if ($clusters_without_ideas > 0) { - $issues[] = "Found $clusters_without_ideas clusters without content ideas (auto_generate_ideas may be disabled)"; - } - - // Check if ideas exist but not queued - $ideas_not_queued = $wpdb->get_var(" - SELECT COUNT(*) FROM {$wpdb->prefix}igny8_content_ideas i - LEFT JOIN {$wpdb->prefix}igny8_tasks t ON i.id = t.idea_id - WHERE i.status = 'new' AND t.id IS NULL - "); - - if ($ideas_not_queued > 0) { - $issues[] = "Found $ideas_not_queued ideas not queued for content generation (auto_queue may be disabled)"; - } - - // Check if tasks exist but no content generated - $tasks_without_content = $wpdb->get_var(" - SELECT COUNT(*) FROM {$wpdb->prefix}igny8_tasks t - LEFT JOIN {$wpdb->posts} p ON t.assigned_post_id = p.ID - WHERE t.status = 'queued' AND (p.ID IS NULL OR p.post_content = '') - "); - - if ($tasks_without_content > 0) { - $issues[] = "Found $tasks_without_content tasks without content (auto_generate_content may be disabled)"; - } - - // Check if content exists but no images - $content_without_images = $wpdb->get_var(" - SELECT COUNT(*) FROM {$wpdb->posts} p - LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = 'igny8_has_ai_image' - WHERE p.post_status = 'publish' - AND p.post_type IN ('post', 'page') - AND p.post_content LIKE '%[image%' - AND pm.meta_value IS NULL - "); - - if ($content_without_images > 0) { - $issues[] = "Found $content_without_images posts with image placeholders but no AI images (auto_generate_images may be disabled)"; - } - - // Check if content exists but not published - $drafts_not_published = $wpdb->get_var(" - SELECT COUNT(*) FROM {$wpdb->posts} p - INNER JOIN {$wpdb->prefix}igny8_tasks t ON p.ID = t.assigned_post_id - WHERE p.post_status = 'draft' - AND t.status = 'completed' - "); - - if ($drafts_not_published > 0) { - $issues[] = "Found $drafts_not_published completed tasks with unpublished content (auto_publish_drafts may be disabled)"; - } - - return $issues; -} - -/** - * Auto Generate Images Cron Handler (NEW) - * - * Automatically generates images for content that needs them. - */ -function igny8_auto_generate_images_cron_handler() { - // Check if automation is enabled - if (igny8_get_ai_setting('auto_generate_images_enabled', 'disabled') !== 'enabled') { - return; - } - - // Check if AI mode is enabled - if (igny8_get_ai_setting('writer_mode', 'manual') !== 'ai') { - return; - } - - global $wpdb; - - // Get posts that need images (use admin limit) - $limit = $GLOBALS['igny8_cron_limit'] ?? null; - if ($limit === null) { - // Try to get limit from Smart Automation Jobs table directly - $cron_limits = get_option('igny8_cron_limits', []); - $limit = $cron_limits['igny8_auto_generate_images_cron'] ?? null; - - if ($limit === null) { - error_log('Igny8 Auto Generate Images Cron: No limit set in Smart Automation Jobs table'); - $GLOBALS['igny8_cron_processed_count'] = 0; - $GLOBALS['igny8_cron_result_details'] = "FAILED: No limit set in Smart Automation Jobs table"; - return; - } - - echo "Igny8 CRON HANDLER: Using limit from Smart Automation Jobs table: $limit
"; - } - $posts_needing_images = $wpdb->get_results(" - SELECT p.ID, p.post_title, p.post_content - FROM {$wpdb->posts} p - LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = 'igny8_has_ai_image' - WHERE p.post_status = 'publish' - AND p.post_type IN ('post', 'page') - AND pm.meta_value IS NULL - AND p.post_content LIKE '%[image%' - LIMIT $limit - "); - - if (empty($posts_needing_images)) { - igny8_log_ai_event('Auto Generate Images Skipped', 'writer', 'auto_generate_images', 'info', 'No posts need images', 'All posts already have images or no image placeholders found'); - return; - } - - $post_ids = array_column($posts_needing_images, 'ID'); - - // Log automation start - igny8_log_ai_event('Auto Generate Images Started', 'writer', 'auto_generate_images', 'info', 'Starting automated image generation', 'Posts: ' . count($post_ids)); - - $generated = 0; - foreach ($post_ids as $post_id) { - // Run image generation process - $result = igny8_generate_post_images($post_id); - if ($result['success']) { - $generated++; - // Mark as having AI image - update_post_meta($post_id, 'igny8_has_ai_image', current_time('mysql')); - } - } - - if ($generated > 0) { - igny8_log_ai_event('Auto Generate Images Complete', 'writer', 'auto_generate_images', 'success', 'Automated image generation completed', 'Images generated: ' . $generated); - - // Set global variables for detailed logging - $GLOBALS['igny8_cron_processed_count'] = $generated; - $GLOBALS['igny8_cron_result_details'] = "Processed {$generated} posts, generated {$generated} images"; - } else { - igny8_log_ai_event('Auto Generate Images Failed', 'writer', 'auto_generate_images', 'error', 'Automated image generation failed', 'No images were generated'); - - // Set global variables for detailed logging (failure case) - $GLOBALS['igny8_cron_processed_count'] = 0; - $GLOBALS['igny8_cron_result_details'] = "FAILED: No images were generated"; - } -} - -/** - * Test Cron Endpoint Handler (NEW) - * - * Simple test handler for cron endpoint validation. - */ -function igny8_test_cron_endpoint() { - igny8_log_ai_event('Cron Test', 'system', 'test_endpoint', 'info', 'Cron endpoint test executed', 'Test successful at ' . current_time('mysql')); - - // Return success response for external cron - if (defined('DOING_CRON') && DOING_CRON) { - return [ - 'success' => true, - 'message' => 'Igny8 CRON endpoint is working', - 'timestamp' => current_time('mysql'), - 'server' => $_SERVER['SERVER_NAME'] ?? 'unknown' - ]; - } -} - -/** - * Safe taxonomy term creation with duplicate prevention - * - * @param string $term_name Term name - * @param string $taxonomy Taxonomy name - * @param array $args Additional arguments - * @return int|WP_Error Term ID or error - */ -function igny8_safe_create_term($term_name, $taxonomy, $args = []) { - // Check if term already exists - $existing_term = get_term_by('name', $term_name, $taxonomy); - if ($existing_term) { - return $existing_term->term_id; - } - - // Create term with duplicate slug prevention - $slug = sanitize_title($term_name); - $counter = 1; - $original_slug = $slug; - - while (term_exists($slug, $taxonomy)) { - $slug = $original_slug . '-' . $counter; - $counter++; - } - - $args['slug'] = $slug; - - $result = wp_insert_term($term_name, $taxonomy, $args); - - if (is_wp_error($result)) { - // If still error, try to get existing term by slug - $existing = get_term_by('slug', $slug, $taxonomy); - if ($existing) { - return $existing->term_id; - } - return $result; - } - - return $result['term_id']; -} - -/** - * Enhanced cluster term creation with safety checks - * - * @param int $cluster_id Cluster ID - * @return int|false Term ID or false on failure - */ -function igny8_safe_create_cluster_term($cluster_id) { - global $wpdb; - - // Get cluster data - $cluster = $wpdb->get_row($wpdb->prepare( - "SELECT cluster_name, cluster_term_id FROM {$wpdb->prefix}igny8_clusters WHERE id = %d", - $cluster_id - )); - - if (!$cluster) { - return false; - } - - // Skip if already mapped - if (!empty($cluster->cluster_term_id)) { - return $cluster->cluster_term_id; - } - - // Ensure taxonomy exists - if (!taxonomy_exists('clusters') && function_exists('igny8_register_taxonomies')) { - igny8_register_taxonomies(); - } - - // Create term safely - $term_id = igny8_safe_create_term($cluster->cluster_name, 'clusters'); - - if ($term_id && !is_wp_error($term_id)) { - // Update cluster with term ID - $wpdb->update( - "{$wpdb->prefix}igny8_clusters", - ['cluster_term_id' => $term_id], - ['id' => $cluster_id], - ['%d'], - ['%d'] - ); - - return $term_id; - } - - return false; -} - -// =================================================================== -// REGISTER CRON HOOKS -// =================================================================== - -// Register all cron job hooks -add_action('igny8_auto_cluster_cron', 'igny8_auto_cluster_cron_handler'); -add_action('igny8_auto_generate_ideas_cron', 'igny8_auto_generate_ideas_cron_handler'); -add_action('igny8_auto_queue_cron', 'igny8_auto_queue_cron_handler'); -add_action('igny8_auto_generate_content_cron', 'igny8_auto_generate_content_cron_handler'); -add_action('igny8_auto_generate_images_cron', 'igny8_auto_generate_images_cron_handler'); -add_action('igny8_auto_publish_drafts_cron', 'igny8_auto_publish_drafts_cron_handler'); -add_action('igny8_process_ai_queue_cron', 'igny8_process_ai_queue_cron_handler'); -add_action('igny8_auto_recalc_cron', 'igny8_auto_recalc_cron_handler'); -add_action('igny8_auto_optimizer_cron', 'igny8_auto_optimizer_cron_handler'); -add_action('igny8_health_check_cron', 'igny8_health_check_cron_handler'); diff --git a/igny8-wp-plugin-for-reference-olny/core/cron/igny8-cron-master-dispatcher.php b/igny8-wp-plugin-for-reference-olny/core/cron/igny8-cron-master-dispatcher.php deleted file mode 100644 index a8294f44..00000000 --- a/igny8-wp-plugin-for-reference-olny/core/cron/igny8-cron-master-dispatcher.php +++ /dev/null @@ -1,384 +0,0 @@ -"; - echo "Igny8 MASTER DISPATCHER: Starting smart automation check
"; - error_log("Igny8 MASTER DISPATCHER: Starting smart automation check"); - - // Get all defined cron jobs - $cron_jobs = igny8_get_defined_cron_jobs(); - $current_time = current_time('timestamp'); - $executed_jobs = []; - $skipped_jobs = []; - - echo "Igny8 MASTER DISPATCHER: Found " . count($cron_jobs) . " defined jobs
"; - - // Get settings and limits - $cron_settings = get_option('igny8_cron_settings', []); - $cron_limits = get_option('igny8_cron_limits', []); - - // Initialize default settings if missing - if (empty($cron_settings)) { - $cron_settings = igny8_get_default_cron_settings(); - update_option('igny8_cron_settings', $cron_settings); - echo "Igny8 MASTER DISPATCHER: Initialized default settings
"; - } - - if (empty($cron_limits)) { - $cron_limits = igny8_get_default_cron_limits(); - update_option('igny8_cron_limits', $cron_limits); - echo "Igny8 MASTER DISPATCHER: Initialized default limits
"; - } - - // Process each job in priority order - foreach ($cron_jobs as $job_name => $job_config) { - echo "Igny8 MASTER DISPATCHER: Checking job: " . $job_name . "
"; - - // Check if job is enabled - $job_settings = $cron_settings[$job_name] ?? []; - if (!($job_settings['enabled'] ?? false)) { - echo "Igny8 MASTER DISPATCHER: Job disabled, skipping
"; - $skipped_jobs[] = $job_name; - continue; - } - - // Check if job is due (simplified - just check if enabled and not recently run) - $last_run = $job_settings['last_run'] ?? 0; - $time_since_last_run = $current_time - $last_run; - - // Run job if it hasn't been run in the last 5 minutes (to prevent duplicate runs) - if ($time_since_last_run < 300) { - echo "Igny8 MASTER DISPATCHER: Job run recently, skipping
"; - $skipped_jobs[] = $job_name; - continue; - } - - // Check if job is already running (duplicate prevention) - $lock_key = 'igny8_cron_running_' . $job_name; - if (get_transient($lock_key)) { - echo "Igny8 MASTER DISPATCHER: Job already running, skipping
"; - $skipped_jobs[] = $job_name; - continue; - } - - // Set lock for this job - $max_execution_time = $job_config['max_execution_time'] ?? 300; - set_transient($lock_key, true, $max_execution_time); - - echo "Igny8 MASTER DISPATCHER: Executing job: " . $job_name . "
"; - - try { - // Get job limit - $job_limit = $cron_limits[$job_name] ?? 1; - - // Set limit as global variable for handlers to use - $GLOBALS['igny8_cron_limit'] = $job_limit; - - // Execute the job - $start_time = microtime(true); - do_action($job_name); - $execution_time = microtime(true) - $start_time; - - // Update last run time - $cron_settings[$job_name]['last_run'] = $current_time; - update_option('igny8_cron_settings', $cron_settings); - - // Track individual job execution with detailed logging - $processed_count = $GLOBALS['igny8_cron_processed_count'] ?? 0; - $result_details = $GLOBALS['igny8_cron_result_details'] ?? ''; - - echo "Igny8 MASTER DISPATCHER: Global variables - processed_count: $processed_count, result_details: $result_details
"; - - $job_health = [ - 'last_run' => $current_time, - 'success' => true, - 'last_success' => true, - 'execution_time' => round($execution_time, 2), - 'error_message' => '', - 'processed_count' => $processed_count, - 'result_details' => $result_details, - 'execution_method' => (isset($_GET['import_key']) && !empty($_GET['import_key'])) ? 'external_url' : 'server_cron' - ]; - update_option('igny8_cron_health_' . $job_name, $job_health); - - $executed_jobs[] = [ - 'job' => $job_name, - 'execution_time' => round($execution_time, 2), - 'success' => true - ]; - - echo "Igny8 MASTER DISPATCHER: Job completed successfully in " . round($execution_time, 2) . "s
"; - - } catch (Exception $e) { - echo "Igny8 MASTER DISPATCHER: Job failed: " . $e->getMessage() . "
"; - error_log("Igny8 MASTER DISPATCHER: Job $job_name failed - " . $e->getMessage()); - - // Track individual job failure - $job_health = [ - 'last_run' => $current_time, - 'success' => false, - 'last_success' => false, - 'execution_time' => 0, - 'error_message' => $e->getMessage(), - 'processed_count' => $GLOBALS['igny8_cron_processed_count'] ?? 0, - 'result_details' => 'FAILED: ' . $e->getMessage(), - 'execution_method' => (isset($_GET['import_key']) && !empty($_GET['import_key'])) ? 'external_url' : 'server_cron' - ]; - update_option('igny8_cron_health_' . $job_name, $job_health); - - $executed_jobs[] = [ - 'job' => $job_name, - 'execution_time' => 0, - 'success' => false, - 'last_success' => false, - 'error' => $e->getMessage() - ]; - } catch (Throwable $e) { - echo "Igny8 MASTER DISPATCHER: Job fatal error: " . $e->getMessage() . "
"; - error_log("Igny8 MASTER DISPATCHER: Job $job_name fatal error - " . $e->getMessage()); - - // Track individual job failure - $job_health = [ - 'last_run' => $current_time, - 'success' => false, - 'last_success' => false, - 'execution_time' => 0, - 'error_message' => $e->getMessage(), - 'processed_count' => $GLOBALS['igny8_cron_processed_count'] ?? 0, - 'result_details' => 'FAILED: ' . $e->getMessage(), - 'execution_method' => (isset($_GET['import_key']) && !empty($_GET['import_key'])) ? 'external_url' : 'server_cron' - ]; - update_option('igny8_cron_health_' . $job_name, $job_health); - - $executed_jobs[] = [ - 'job' => $job_name, - 'execution_time' => 0, - 'success' => false, - 'last_success' => false, - 'error' => $e->getMessage() - ]; - } finally { - // Always release the lock - delete_transient($lock_key); - } - } - - // Log summary - echo "Igny8 MASTER DISPATCHER: Execution summary
"; - echo "Igny8 MASTER DISPATCHER: Jobs executed: " . count($executed_jobs) . "
"; - echo "Igny8 MASTER DISPATCHER: Jobs skipped: " . count($skipped_jobs) . "
"; - - // Store execution log - update_option('igny8_cron_last_execution', [ - 'timestamp' => $current_time, - 'executed' => $executed_jobs, - 'skipped' => $skipped_jobs - ]); - - echo "Igny8 MASTER DISPATCHER: Smart automation check completed
"; - echo ""; - - // Return success response for external cron - return [ - 'success' => true, - 'message' => 'Master dispatcher executed successfully', - 'executed' => count($executed_jobs), - 'skipped' => count($skipped_jobs), - 'timestamp' => current_time('mysql') - ]; -} - -/** - * Get all defined cron jobs with their configurations - */ -function igny8_get_defined_cron_jobs() { - return [ - 'igny8_auto_cluster_cron' => [ - 'handler' => 'igny8_auto_cluster_cron_handler', - 'priority' => 1, - 'max_execution_time' => 600, // 10 minutes - 'description' => 'Auto cluster unmapped keywords', - 'module' => 'planner' - ], - 'igny8_auto_generate_ideas_cron' => [ - 'handler' => 'igny8_auto_generate_ideas_cron_handler', - 'priority' => 2, - 'max_execution_time' => 300, // 5 minutes - 'description' => 'Auto generate ideas from clusters', - 'module' => 'planner' - ], - 'igny8_auto_queue_cron' => [ - 'handler' => 'igny8_auto_queue_cron_handler', - 'priority' => 3, - 'max_execution_time' => 300, // 5 minutes - 'description' => 'Auto queue new ideas', - 'module' => 'planner' - ], - 'igny8_auto_generate_content_cron' => [ - 'handler' => 'igny8_auto_generate_content_cron_handler', - 'priority' => 4, - 'max_execution_time' => 600, // 10 minutes - 'description' => 'Auto generate content from queued tasks', - 'module' => 'writer' - ], - 'igny8_auto_generate_images_cron' => [ - 'handler' => 'igny8_auto_generate_images_cron_handler', - 'priority' => 5, - 'max_execution_time' => 900, // 15 minutes - 'description' => 'Auto generate images for content', - 'module' => 'writer' - ], - 'igny8_auto_publish_drafts_cron' => [ - 'handler' => 'igny8_auto_publish_drafts_cron_handler', - 'priority' => 6, - 'max_execution_time' => 300, // 5 minutes - 'description' => 'Auto publish completed drafts', - 'module' => 'writer' - ], - 'igny8_process_ai_queue_cron' => [ - 'handler' => 'igny8_process_ai_queue_cron_handler', - 'priority' => 7, - 'max_execution_time' => 300, // 5 minutes - 'description' => 'Process AI queue tasks', - 'module' => 'ai' - ], - 'igny8_auto_recalc_cron' => [ - 'handler' => 'igny8_auto_recalc_cron_handler', - 'priority' => 8, - 'max_execution_time' => 300, // 5 minutes - 'description' => 'Auto recalculate metrics', - 'module' => 'analytics' - ], - 'igny8_auto_optimizer_cron' => [ - 'handler' => 'igny8_auto_optimizer_cron_handler', - 'priority' => 9, - 'max_execution_time' => 300, // 5 minutes - 'description' => 'Auto optimize content and keywords', - 'module' => 'optimizer' - ], - 'igny8_health_check_cron' => [ - 'handler' => 'igny8_health_check_cron_handler', - 'priority' => 10, - 'max_execution_time' => 300, // 5 minutes - 'description' => 'System health check and cleanup', - 'module' => 'system' - ] - ]; -} - - -/** - * Get default cron settings - */ -function igny8_get_default_cron_settings() { - $jobs = igny8_get_defined_cron_jobs(); - $settings = []; - - foreach ($jobs as $job_name => $config) { - $settings[$job_name] = [ - 'enabled' => false, // Default to disabled - 'last_run' => 0 - ]; - } - - return $settings; -} - -/** - * Get default cron limits - */ -function igny8_get_default_cron_limits() { - return [ - 'igny8_auto_cluster_cron' => 1, - 'igny8_auto_generate_ideas_cron' => 1, - 'igny8_auto_queue_cron' => 1, - 'igny8_auto_generate_content_cron' => 1, - 'igny8_auto_generate_images_cron' => 1, - 'igny8_auto_publish_drafts_cron' => 1, - 'igny8_process_ai_queue_cron' => 1, - 'igny8_auto_recalc_cron' => 1, - 'igny8_auto_optimizer_cron' => 1, - 'igny8_health_check_cron' => 1 - ]; -} - -/** - * Update cron settings for a specific job - */ -function igny8_update_cron_job_settings($job_name, $settings) { - $cron_settings = get_option('igny8_cron_settings', []); - $cron_settings[$job_name] = array_merge($cron_settings[$job_name] ?? [], $settings); - update_option('igny8_cron_settings', $cron_settings); -} - -/** - * Update cron limits for a specific job - */ -function igny8_update_cron_job_limits($job_name, $limit) { - $cron_limits = get_option('igny8_cron_limits', []); - $cron_limits[$job_name] = $limit; - update_option('igny8_cron_limits', $cron_limits); -} - -/** - * Get health status for a specific job - */ -function igny8_get_job_health_status($job_name) { - $health = get_option('igny8_cron_health_' . $job_name, []); - $cron_settings = get_option('igny8_cron_settings', []); - $job_settings = $cron_settings[$job_name] ?? []; - - return [ - 'enabled' => $job_settings['enabled'] ?? false, - 'last_run' => isset($health['last_run']) ? date('Y-m-d H:i:s', $health['last_run']) : 'Never', - 'last_success' => $health['success'] ?? null, - 'execution_time' => isset($health['execution_time']) ? round($health['execution_time'], 2) : 0, - 'error_message' => $health['error_message'] ?? '', - 'processed_count' => $health['processed_count'] ?? 0, - 'result_details' => $health['result_details'] ?? '', - 'execution_method' => $health['execution_method'] ?? 'unknown' - ]; -} - -/** - * Get cron job status and next run time - */ -function igny8_get_cron_job_status($job_name) { - $cron_settings = get_option('igny8_cron_settings', []); - $job_settings = $cron_settings[$job_name] ?? []; - - if (empty($job_settings)) { - return [ - 'enabled' => false, - 'last_run' => 'Never' - ]; - } - - return [ - 'enabled' => $job_settings['enabled'] ?? false, - 'last_run' => isset($job_settings['last_run']) && $job_settings['last_run'] ? date('Y-m-d H:i:s', $job_settings['last_run']) : 'Never' - ]; -} diff --git a/igny8-wp-plugin-for-reference-olny/core/db/db-migration.php b/igny8-wp-plugin-for-reference-olny/core/db/db-migration.php deleted file mode 100644 index d939ea47..00000000 --- a/igny8-wp-plugin-for-reference-olny/core/db/db-migration.php +++ /dev/null @@ -1,253 +0,0 @@ -prefix . 'igny8_example_table'; - // $column_exists = $wpdb->get_var("SHOW COLUMNS FROM `$table_name` LIKE 'new_column'"); - // if (!$column_exists) { - // $wpdb->query("ALTER TABLE `$table_name` ADD COLUMN `new_column` VARCHAR(255) DEFAULT NULL"); - // } - - // Example: Create new table - // $sql = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}igny8_new_table ( - // id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - // name VARCHAR(255) NOT NULL, - // created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - // PRIMARY KEY (id) - // ) {$wpdb->get_charset_collate()};"; - // dbDelta($sql); - - // Example: Migrate data - // $wpdb->query("UPDATE {$wpdb->prefix}igny8_table SET old_field = new_field WHERE condition"); - - error_log("Igny8 Migration: Successfully migrated from $from_version to $to_version"); - return true; - - } catch (Exception $e) { - error_log("Igny8 Migration Error: " . $e->getMessage()); - return false; - } -} - -/** - * ======================================================================== - * MIGRATION UTILITIES - * ======================================================================== - */ - -/** - * Backup table before migration - */ -function igny8_backup_table($table_name, $suffix = null) { - global $wpdb; - - if (!$suffix) { - $suffix = '_backup_' . date('Y_m_d_H_i_s'); - } - - $backup_table = $table_name . $suffix; - - try { - $wpdb->query("CREATE TABLE `$backup_table` LIKE `$table_name`"); - $wpdb->query("INSERT INTO `$backup_table` SELECT * FROM `$table_name`"); - return $backup_table; - } catch (Exception $e) { - error_log("Igny8 Migration: Failed to backup table $table_name - " . $e->getMessage()); - return false; - } -} - -/** - * Restore table from backup - */ -function igny8_restore_table($table_name, $backup_table) { - global $wpdb; - - try { - $wpdb->query("DROP TABLE IF EXISTS `$table_name`"); - $wpdb->query("CREATE TABLE `$table_name` LIKE `$backup_table`"); - $wpdb->query("INSERT INTO `$table_name` SELECT * FROM `$backup_table`"); - return true; - } catch (Exception $e) { - error_log("Igny8 Migration: Failed to restore table $table_name - " . $e->getMessage()); - return false; - } -} - -/** - * Check if table exists - */ -function igny8_table_exists($table_name) { - global $wpdb; - return $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name; -} - -/** - * Check if column exists in table - */ -function igny8_column_exists($table_name, $column_name) { - global $wpdb; - $result = $wpdb->get_var("SHOW COLUMNS FROM `$table_name` LIKE '$column_name'"); - return !empty($result); -} - -/** - * Get table structure - */ -function igny8_get_table_structure($table_name) { - global $wpdb; - return $wpdb->get_results("DESCRIBE `$table_name`", ARRAY_A); -} - -/** - * ======================================================================== - * AUTO-MIGRATION ON PLUGIN UPDATE - * ======================================================================== - */ - -/** - * Auto-run migrations on plugin update - */ -function igny8_auto_run_migrations() { - if (current_user_can('manage_options') && igny8_is_migration_needed()) { - igny8_run_migrations(); - } -} - -// Hook to auto-run migrations on admin_init -add_action('admin_init', 'igny8_auto_run_migrations'); - -/** - * ======================================================================== - * MIGRATION STATUS & LOGGING - * ======================================================================== - */ - -/** - * Log migration event - */ -function igny8_log_migration($from_version, $to_version, $status = 'success', $message = '') { - $log_entry = [ - 'timestamp' => current_time('mysql'), - 'from_version' => $from_version, - 'to_version' => $to_version, - 'status' => $status, - 'message' => $message, - 'user_id' => get_current_user_id() - ]; - - // Store in options (you could also use the logs table) - $migration_logs = get_option('igny8_migration_logs', []); - $migration_logs[] = $log_entry; - - // Keep only last 50 migration logs - if (count($migration_logs) > 50) { - $migration_logs = array_slice($migration_logs, -50); - } - - update_option('igny8_migration_logs', $migration_logs); -} - -/** - * Get migration logs - */ -function igny8_get_migration_logs($limit = 10) { - $logs = get_option('igny8_migration_logs', []); - return array_slice(array_reverse($logs), 0, $limit); -} - -/** - * Clear migration logs - */ -function igny8_clear_migration_logs() { - delete_option('igny8_migration_logs'); -} diff --git a/igny8-wp-plugin-for-reference-olny/core/db/db.php b/igny8-wp-plugin-for-reference-olny/core/db/db.php deleted file mode 100644 index 059f9122..00000000 --- a/igny8-wp-plugin-for-reference-olny/core/db/db.php +++ /dev/null @@ -1,970 +0,0 @@ -prefix . 'igny8_content_ideas'; - - // Only run cleanup if table exists - if (!$wpdb->get_var("SHOW TABLES LIKE '$table_name'")) { - return true; - } - - try { - // Remove legacy priority column if it exists (from very old versions) - $priority_exists = $wpdb->get_var("SHOW COLUMNS FROM `$table_name` LIKE 'priority'"); - if ($priority_exists) { - // Remove index first if it exists - $index_exists = $wpdb->get_var("SHOW INDEX FROM `$table_name` WHERE Key_name = 'idx_priority'"); - if ($index_exists) { - $wpdb->query("ALTER TABLE `$table_name` DROP INDEX `idx_priority`"); - } - - // Drop the column - $wpdb->query("ALTER TABLE `$table_name` DROP COLUMN `priority`"); - error_log('Igny8 Cleanup: Removed legacy priority column'); - } - - // Remove legacy ai_generated column if it exists (should be source now) - $ai_generated_exists = $wpdb->get_var("SHOW COLUMNS FROM `$table_name` LIKE 'ai_generated'"); - if ($ai_generated_exists) { - // Check if source column exists - $source_exists = $wpdb->get_var("SHOW COLUMNS FROM `$table_name` LIKE 'source'"); - - if (!$source_exists) { - // Migrate data from ai_generated to source before dropping - $wpdb->query("ALTER TABLE `$table_name` ADD COLUMN `source` ENUM('AI','Manual') DEFAULT 'Manual'"); - $wpdb->query("UPDATE `$table_name` SET source = CASE WHEN ai_generated = 1 THEN 'AI' ELSE 'Manual' END"); - error_log('Igny8 Cleanup: Migrated ai_generated to source field'); - } - - // Drop the old ai_generated column - $wpdb->query("ALTER TABLE `$table_name` DROP COLUMN `ai_generated`"); - error_log('Igny8 Cleanup: Removed legacy ai_generated column'); - } - - // Update any old status values to new format - $wpdb->query("UPDATE `$table_name` SET status = 'new' WHERE status NOT IN ('new','scheduled','published')"); - - return true; - - } catch (Exception $e) { - error_log('Igny8 Cleanup Error: ' . $e->getMessage()); - return false; - } -} - -/** - * Check if legacy cleanup is needed - */ -function igny8_is_legacy_cleanup_needed() { - global $wpdb; - - $table_name = $wpdb->prefix . 'igny8_content_ideas'; - - // Check if table exists - if (!$wpdb->get_var("SHOW TABLES LIKE '$table_name'")) { - return false; - } - - // Check for legacy columns - $priority_exists = $wpdb->get_var("SHOW COLUMNS FROM `$table_name` LIKE 'priority'"); - $ai_generated_exists = $wpdb->get_var("SHOW COLUMNS FROM `$table_name` LIKE 'ai_generated'"); - - return $priority_exists || $ai_generated_exists; -} - -/** - * Auto-run legacy cleanup on admin_init if needed - */ -function igny8_auto_run_legacy_cleanup() { - if (current_user_can('manage_options') && igny8_is_legacy_cleanup_needed()) { - igny8_cleanup_legacy_structures(); - } -} - -// Hook to auto-run legacy cleanup (only for existing installations) -add_action('admin_init', 'igny8_auto_run_legacy_cleanup'); - -/** - * Auto-run logs table migration on admin_init if needed - */ -function igny8_auto_run_logs_migration() { - if (current_user_can('manage_options')) { - igny8_migrate_logs_table(); - } -} - -// Hook to auto-run logs migration (only for existing installations) -add_action('admin_init', 'igny8_auto_run_logs_migration'); - -/** - * Remove old migration option on plugin activation - */ -function igny8_cleanup_migration_options() { - delete_option('igny8_migration_ideas_schema_updated'); -} - -/** - * ======================================================================== - * COMPLETE DATABASE SCHEMA CREATION - * ======================================================================== - */ - -/** - * Create all Igny8 database tables (15 tables total) - */ -function igny8_create_all_tables() { - global $wpdb; - $charset_collate = $wpdb->get_charset_collate(); - - // Keywords table - $sql = "CREATE TABLE {$wpdb->prefix}igny8_keywords ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - keyword VARCHAR(255) NOT NULL, - search_volume INT UNSIGNED DEFAULT 0, - difficulty INT UNSIGNED DEFAULT 0, - cpc FLOAT DEFAULT 0.00, - intent VARCHAR(50) DEFAULT 'informational', - cluster_id BIGINT UNSIGNED DEFAULT NULL, - sector_id BIGINT UNSIGNED DEFAULT NULL, - mapped_post_id BIGINT UNSIGNED DEFAULT NULL, - status ENUM('unmapped','mapped','queued','published') DEFAULT 'unmapped', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (id), - UNIQUE KEY unique_keyword (keyword), - KEY idx_cluster_id (cluster_id), - KEY idx_sector_id (sector_id), - KEY idx_mapped_post_id (mapped_post_id), - KEY idx_status (status), - KEY idx_created_at (created_at) - ) $charset_collate;"; - dbDelta($sql); - - // Tasks table - $sql = "CREATE TABLE {$wpdb->prefix}igny8_tasks ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - title VARCHAR(255) NOT NULL, - description TEXT DEFAULT NULL, - status ENUM('pending','in_progress','completed','cancelled','draft','queued','review','published') DEFAULT 'pending', - priority ENUM('low','medium','high','urgent') DEFAULT 'medium', - due_date DATETIME DEFAULT NULL, - content_structure ENUM('cluster_hub','landing_page','guide_tutorial','how_to','comparison','review','top_listicle','question','product_description','service_page','home_page') DEFAULT 'cluster_hub', - content_type ENUM('post','product','page','CPT') DEFAULT 'post', - cluster_id BIGINT UNSIGNED DEFAULT NULL, - keywords TEXT DEFAULT NULL, - meta_title VARCHAR(255) DEFAULT NULL, - meta_description TEXT DEFAULT NULL, - word_count INT UNSIGNED DEFAULT 0, - raw_ai_response LONGTEXT DEFAULT NULL, - schedule_at DATETIME DEFAULT NULL, - assigned_post_id BIGINT UNSIGNED DEFAULT NULL, - idea_id BIGINT UNSIGNED DEFAULT NULL, - ai_writer ENUM('ai','human') DEFAULT 'ai', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (id), - KEY idx_content_structure (content_structure), - KEY idx_content_type (content_type), - KEY idx_cluster_id (cluster_id), - KEY idx_status (status), - KEY idx_priority (priority), - KEY idx_assigned_post_id (assigned_post_id), - KEY idx_schedule_at (schedule_at), - KEY idx_idea_id (idea_id), - KEY idx_ai_writer (ai_writer), - KEY idx_created_at (created_at) - ) $charset_collate;"; - dbDelta($sql); - - // Data table for personalization - $sql = "CREATE TABLE {$wpdb->prefix}igny8_data ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - post_id BIGINT UNSIGNED NOT NULL, - data_type VARCHAR(50) NOT NULL, - data JSON NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (id), - KEY idx_post_id (post_id), - KEY idx_data_type (data_type), - KEY idx_created_at (created_at) - ) $charset_collate;"; - dbDelta($sql); - - // Personalization variations table - stores AI-generated personalized content - $sql = "CREATE TABLE {$wpdb->prefix}igny8_variations ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - post_id BIGINT UNSIGNED NOT NULL, - fields_hash CHAR(64) NOT NULL, - fields_json LONGTEXT NOT NULL, - content LONGTEXT NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (id), - KEY idx_post_id (post_id), - KEY idx_fields_hash (fields_hash), - KEY idx_created_at (created_at), - UNIQUE KEY unique_variation (post_id, fields_hash) - ) $charset_collate;"; - dbDelta($sql); - - // Rankings table - $sql = "CREATE TABLE {$wpdb->prefix}igny8_rankings ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - post_id BIGINT UNSIGNED NOT NULL, - keyword VARCHAR(255) NOT NULL, - impressions INT UNSIGNED DEFAULT 0, - clicks INT UNSIGNED DEFAULT 0, - ctr FLOAT DEFAULT 0.00, - avg_position FLOAT DEFAULT NULL, - source ENUM('gsc','ahrefs','manual') DEFAULT 'manual', - fetched_at DATETIME DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (id), - KEY idx_post_id (post_id), - KEY idx_keyword (keyword), - KEY idx_source (source), - KEY idx_fetched_at (fetched_at) - ) $charset_collate;"; - dbDelta($sql); - - // Suggestions table - $sql = "CREATE TABLE {$wpdb->prefix}igny8_suggestions ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - post_id BIGINT UNSIGNED NOT NULL, - cluster_id BIGINT UNSIGNED DEFAULT NULL, - suggestion_type ENUM('internal_link','keyword_injection','rewrite') NOT NULL, - payload JSON DEFAULT NULL, - status ENUM('pending','applied','rejected') DEFAULT 'pending', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - applied_at DATETIME DEFAULT NULL, - PRIMARY KEY (id), - KEY idx_post_id (post_id), - KEY idx_cluster_id (cluster_id), - KEY idx_suggestion_type (suggestion_type), - KEY idx_status (status), - KEY idx_created_at (created_at) - ) $charset_collate;"; - dbDelta($sql); - - // Campaigns table - $sql = "CREATE TABLE {$wpdb->prefix}igny8_campaigns ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - cluster_id BIGINT UNSIGNED DEFAULT NULL, - target_post_id BIGINT UNSIGNED DEFAULT NULL, - name VARCHAR(255) NOT NULL, - status ENUM('active','completed','paused') DEFAULT 'active', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (id), - KEY idx_cluster_id (cluster_id), - KEY idx_target_post_id (target_post_id), - KEY idx_status (status), - KEY idx_created_at (created_at) - ) $charset_collate;"; - dbDelta($sql); - - // Content Ideas table - $sql = "CREATE TABLE {$wpdb->prefix}igny8_content_ideas ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - idea_title VARCHAR(255) NOT NULL, - idea_description LONGTEXT DEFAULT NULL, - content_structure ENUM('cluster_hub','landing_page','guide_tutorial','how_to','comparison','review','top_listicle','question','product_description','service_page','home_page') DEFAULT 'cluster_hub', - content_type ENUM('post','product','page','CPT') DEFAULT 'post', - keyword_cluster_id BIGINT UNSIGNED DEFAULT NULL, - status ENUM('new','scheduled','published') DEFAULT 'new', - estimated_word_count INT UNSIGNED DEFAULT 0, - target_keywords TEXT DEFAULT NULL, - image_prompts TEXT DEFAULT NULL, - source ENUM('AI','Manual') DEFAULT 'Manual', - mapped_post_id BIGINT UNSIGNED DEFAULT NULL, - tasks_count INT UNSIGNED DEFAULT 0, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (id), - KEY idx_idea_title (idea_title), - KEY idx_content_structure (content_structure), - KEY idx_content_type (content_type), - KEY idx_status (status), - KEY idx_keyword_cluster_id (keyword_cluster_id), - KEY idx_mapped_post_id (mapped_post_id), - KEY idx_created_at (created_at) - ) $charset_collate;"; - dbDelta($sql); - - // Clusters table - $sql = "CREATE TABLE {$wpdb->prefix}igny8_clusters ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - cluster_name VARCHAR(255) NOT NULL, - sector_id BIGINT UNSIGNED DEFAULT NULL, - cluster_term_id BIGINT UNSIGNED DEFAULT NULL, - status ENUM('active','inactive','archived') DEFAULT 'active', - keyword_count INT UNSIGNED DEFAULT 0, - total_volume INT UNSIGNED DEFAULT 0, - avg_difficulty DECIMAL(5,2) DEFAULT 0.00, - mapped_pages_count INT UNSIGNED DEFAULT 0, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (id), - KEY idx_cluster_name (cluster_name), - KEY idx_sector_id (sector_id), - KEY idx_cluster_term_id (cluster_term_id), - KEY idx_status (status), - KEY idx_created_at (created_at) - ) $charset_collate;"; - dbDelta($sql); - - // Sites table - $sql = "CREATE TABLE {$wpdb->prefix}igny8_sites ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - site_url VARCHAR(500) NOT NULL, - site_name VARCHAR(255) DEFAULT NULL, - domain_authority INT UNSIGNED DEFAULT 0, - referring_domains INT UNSIGNED DEFAULT 0, - organic_traffic INT UNSIGNED DEFAULT 0, - status ENUM('active','inactive','blocked') DEFAULT 'active', - last_crawled DATETIME DEFAULT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (id), - UNIQUE KEY unique_site_url (site_url), - KEY idx_domain_authority (domain_authority), - KEY idx_status (status), - KEY idx_last_crawled (last_crawled), - KEY idx_created_at (created_at) - ) $charset_collate;"; - dbDelta($sql); - - // Backlinks table - $sql = "CREATE TABLE {$wpdb->prefix}igny8_backlinks ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - source_url VARCHAR(500) NOT NULL, - target_url VARCHAR(500) NOT NULL, - anchor_text VARCHAR(255) DEFAULT NULL, - link_type ENUM('dofollow','nofollow','sponsored','ugc') DEFAULT 'dofollow', - domain_authority INT UNSIGNED DEFAULT 0, - page_authority INT UNSIGNED DEFAULT 0, - status ENUM('pending','live','lost','disavowed') DEFAULT 'pending', - campaign_id BIGINT UNSIGNED DEFAULT NULL, - discovered_date DATE DEFAULT NULL, - lost_date DATE DEFAULT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (id), - KEY idx_source_url (source_url(191)), - KEY idx_target_url (target_url(191)), - KEY idx_link_type (link_type), - KEY idx_status (status), - KEY idx_campaign_id (campaign_id), - KEY idx_domain_authority (domain_authority), - KEY idx_discovered_date (discovered_date), - KEY idx_created_at (created_at) - ) $charset_collate;"; - dbDelta($sql); - - // Mapping table - $sql = "CREATE TABLE {$wpdb->prefix}igny8_mapping ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - source_type ENUM('keyword','cluster','idea','task') NOT NULL, - source_id BIGINT UNSIGNED NOT NULL, - target_type ENUM('post','page','product') NOT NULL, - target_id BIGINT UNSIGNED NOT NULL, - mapping_type ENUM('primary','secondary','related') DEFAULT 'primary', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (id), - KEY idx_source_type_id (source_type, source_id), - KEY idx_target_type_id (target_type, target_id), - KEY idx_mapping_type (mapping_type), - KEY idx_created_at (created_at) - ) $charset_collate;"; - dbDelta($sql); - - // Prompts table - $sql = "CREATE TABLE {$wpdb->prefix}igny8_prompts ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - prompt_name VARCHAR(255) NOT NULL, - prompt_type ENUM('content','optimization','generation','custom') DEFAULT 'content', - prompt_text LONGTEXT NOT NULL, - variables JSON DEFAULT NULL, - is_active TINYINT(1) DEFAULT 1, - usage_count INT UNSIGNED DEFAULT 0, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (id), - UNIQUE KEY unique_prompt_name (prompt_name), - KEY idx_prompt_type (prompt_type), - KEY idx_is_active (is_active), - KEY idx_created_at (created_at) - ) $charset_collate;"; - dbDelta($sql); - - // Logs table - $sql = "CREATE TABLE {$wpdb->prefix}igny8_logs ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - event_type VARCHAR(191) NOT NULL, - message TEXT NOT NULL, - context LONGTEXT NULL, - api_id VARCHAR(255) NULL, - status VARCHAR(50) NULL, - level VARCHAR(50) NULL, - source VARCHAR(100) NULL, - user_id BIGINT UNSIGNED NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (id), - KEY idx_event_type (event_type), - KEY idx_created_at (created_at), - KEY idx_source (source), - KEY idx_status (status), - KEY idx_user_id (user_id) - ) $charset_collate;"; - dbDelta($sql); - - // AI Queue table - $sql = "CREATE TABLE {$wpdb->prefix}igny8_ai_queue ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - action VARCHAR(50) NOT NULL, - data LONGTEXT NOT NULL, - user_id BIGINT UNSIGNED NOT NULL, - status ENUM('pending','processing','completed','failed') DEFAULT 'pending', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - processed_at TIMESTAMP NULL, - result LONGTEXT NULL, - error_message TEXT NULL, - PRIMARY KEY (id), - KEY idx_user_id (user_id), - KEY idx_status (status), - KEY idx_action (action), - KEY idx_created_at (created_at) - ) $charset_collate;"; - dbDelta($sql); - - // Update database version - update_option('igny8_db_version', '0.1'); -} - - - - -/** - * Register Igny8 taxonomies with WordPress - */ -function igny8_register_taxonomies() { - // Register sectors taxonomy (hierarchical) - only if not exists - if (!taxonomy_exists('sectors')) { - register_taxonomy('sectors', ['post', 'page', 'product'], [ - 'hierarchical' => true, - 'labels' => [ - 'name' => 'Sectors', - 'singular_name' => 'Sector', - 'menu_name' => 'Sectors', - 'all_items' => 'All Sectors', - 'edit_item' => 'Edit Sector', - 'view_item' => 'View Sector', - 'update_item' => 'Update Sector', - 'add_new_item' => 'Add New Sector', - 'new_item_name' => 'New Sector Name', - 'parent_item' => 'Parent Sector', - 'parent_item_colon' => 'Parent Sector:', - 'search_items' => 'Search Sectors', - 'popular_items' => 'Popular Sectors', - 'separate_items_with_commas' => 'Separate sectors with commas', - 'add_or_remove_items' => 'Add or remove sectors', - 'choose_from_most_used' => 'Choose from most used sectors', - 'not_found' => 'No sectors found', - ], - 'public' => true, - 'show_ui' => true, - 'show_admin_column' => true, - 'show_in_nav_menus' => true, - 'show_tagcloud' => true, - 'show_in_rest' => true, - 'rewrite' => [ - 'slug' => 'sectors', - 'with_front' => false, - ], - 'capabilities' => [ - 'manage_terms' => 'manage_categories', - 'edit_terms' => 'manage_categories', - 'delete_terms' => 'manage_categories', - 'assign_terms' => 'edit_posts', - ], - ]); - } - - // Register clusters taxonomy (hierarchical) - only if not exists - if (!taxonomy_exists('clusters')) { - register_taxonomy('clusters', ['post', 'page', 'product'], [ - 'hierarchical' => true, - 'labels' => [ - 'name' => 'Content Clusters', - 'singular_name' => 'Cluster', - 'menu_name' => 'Clusters', - 'all_items' => 'All Clusters', - 'edit_item' => 'Edit Cluster', - 'view_item' => 'View Cluster', - 'update_item' => 'Update Cluster', - 'add_new_item' => 'Add New Cluster', - 'new_item_name' => 'New Cluster Name', - 'parent_item' => 'Parent Cluster', - 'parent_item_colon' => 'Parent Cluster:', - 'search_items' => 'Search Clusters', - 'popular_items' => 'Popular Clusters', - 'separate_items_with_commas' => 'Separate clusters with commas', - 'add_or_remove_items' => 'Add or remove clusters', - 'choose_from_most_used' => 'Choose from most used clusters', - 'not_found' => 'No clusters found', - ], - 'public' => true, - 'show_ui' => true, - 'show_admin_column' => true, - 'show_in_nav_menus' => true, - 'show_tagcloud' => true, - 'show_in_rest' => true, - 'rewrite' => [ - 'slug' => 'clusters', - 'with_front' => false, - ], - 'capabilities' => [ - 'manage_terms' => 'manage_categories', - 'edit_terms' => 'manage_categories', - 'delete_terms' => 'manage_categories', - 'assign_terms' => 'edit_posts', - ], - ]); - } -} - -// ========================================================== -// SEO: Prevent indexing of Cluster and Sector taxonomy pages -// ========================================================== -add_action('wp_head', function() { - if (is_tax(['clusters', 'sectors'])) { - echo '' . "\n"; - } -}, 1); - -/** - * Register Igny8 post meta fields with WordPress - */ -function igny8_register_post_meta() { - $post_types = ['post', 'page', 'product']; - - // Define all meta fields with proper schema for REST API - $meta_fields = [ - '_igny8_cluster_id' => [ - 'type' => 'integer', - 'description' => 'Assigns content to a cluster', - 'single' => true, - 'show_in_rest'=> true, - ], - '_igny8_keyword_ids' => [ - 'type' => 'array', - 'description' => 'Maps multiple keywords to content', - 'single' => true, - 'show_in_rest'=> [ - 'schema' => [ - 'type' => 'array', - 'items' => [ - 'type' => 'integer' - ] - ] - ] - ], - '_igny8_task_id' => [ - 'type' => 'integer', - 'description' => 'Links WP content back to Writer task', - 'single' => true, - 'show_in_rest'=> true, - ], - '_igny8_campaign_ids' => [ - 'type' => 'array', - 'description' => 'Associates content with backlink campaigns', - 'single' => true, - 'show_in_rest'=> [ - 'schema' => [ - 'type' => 'array', - 'items' => [ - 'type' => 'integer' - ] - ] - ] - ], - '_igny8_backlink_count' => [ - 'type' => 'integer', - 'description' => 'Quick summary count of backlinks to content', - 'single' => true, - 'show_in_rest'=> true, - ], - '_igny8_last_optimized' => [ - 'type' => 'string', - 'description' => 'Tracks last optimization timestamp', - 'single' => true, - 'show_in_rest'=> true, - ], - '_igny8_meta_title' => [ - 'type' => 'string', - 'description' => 'SEO meta title for the content', - 'single' => true, - 'show_in_rest'=> true, - ], - '_igny8_meta_description' => [ - 'type' => 'string', - 'description' => 'SEO meta description for the content', - 'single' => true, - 'show_in_rest'=> true, - ], - '_igny8_primary_keywords' => [ - 'type' => 'string', - 'description' => 'Primary keywords for the content', - 'single' => true, - 'show_in_rest'=> true, - ], - '_igny8_secondary_keywords' => [ - 'type' => 'string', - 'description' => 'Secondary keywords for the content', - 'single' => true, - 'show_in_rest'=> true, - ], - ]; - - // Register each meta field for all relevant post types - foreach ($meta_fields as $meta_key => $config) { - foreach ($post_types as $post_type) { - register_post_meta($post_type, $meta_key, $config); - } - } -} - -/** - * Set default plugin options - */ -function igny8_set_default_options() { - // Set default options if they don't exist - if (!get_option('igny8_api_key')) { - add_option('igny8_api_key', ''); - } - if (!get_option('igny8_ai_enabled')) { - add_option('igny8_ai_enabled', 1); - } - if (!get_option('igny8_debug_enabled')) { - add_option('igny8_debug_enabled', 0); - } - if (!get_option('igny8_monitoring_enabled')) { - add_option('igny8_monitoring_enabled', 1); - } - if (!get_option('igny8_version')) { - add_option('igny8_version', '0.1'); - } -} - -/** - * Migrate logs table to add missing columns for OpenAI API logging - */ -function igny8_migrate_logs_table() { - global $wpdb; - - $table_name = $wpdb->prefix . 'igny8_logs'; - - // Check if table exists - if (!$wpdb->get_var("SHOW TABLES LIKE '$table_name'")) { - return true; - } - - // Check if migration is needed - $columns = $wpdb->get_results("SHOW COLUMNS FROM $table_name"); - $column_names = array_column($columns, 'Field'); - - $needed_columns = ['api_id', 'status', 'level', 'source', 'user_id']; - $missing_columns = array_diff($needed_columns, $column_names); - - if (empty($missing_columns)) { - return true; // Migration not needed - } - - try { - // Add missing columns - if (in_array('api_id', $missing_columns)) { - $wpdb->query("ALTER TABLE `$table_name` ADD COLUMN `api_id` VARCHAR(255) NULL"); - } - if (in_array('status', $missing_columns)) { - $wpdb->query("ALTER TABLE `$table_name` ADD COLUMN `status` VARCHAR(50) NULL"); - } - if (in_array('level', $missing_columns)) { - $wpdb->query("ALTER TABLE `$table_name` ADD COLUMN `level` VARCHAR(50) NULL"); - } - if (in_array('source', $missing_columns)) { - $wpdb->query("ALTER TABLE `$table_name` ADD COLUMN `source` VARCHAR(100) NULL"); - } - if (in_array('user_id', $missing_columns)) { - $wpdb->query("ALTER TABLE `$table_name` ADD COLUMN `user_id` BIGINT UNSIGNED NULL"); - } - - // Add indexes for new columns - $indexes_to_add = [ - 'source' => "ALTER TABLE `$table_name` ADD INDEX `idx_source` (`source`)", - 'status' => "ALTER TABLE `$table_name` ADD INDEX `idx_status` (`status`)", - 'user_id' => "ALTER TABLE `$table_name` ADD INDEX `idx_user_id` (`user_id`)" - ]; - - foreach ($indexes_to_add as $column => $sql) { - if (in_array($column, $missing_columns)) { - $wpdb->query($sql); - } - } - - error_log('Igny8: Logs table migration completed successfully'); - return true; - - } catch (Exception $e) { - error_log('Igny8 Logs Migration Error: ' . $e->getMessage()); - return false; - } -} - -/** - * Complete plugin installation function - */ -function igny8_install_database() { - // Create all database tables - igny8_create_all_tables(); - - // Migrate logs table if needed - igny8_migrate_logs_table(); - - // Register taxonomies - igny8_register_taxonomies(); - - // Register post meta fields - igny8_register_post_meta(); - - // Set default options - igny8_set_default_options(); - - // Update version - update_option('igny8_version', '0.1'); - update_option('igny8_db_version', '0.1'); - - // Add word_count field to tasks table if it doesn't exist - igny8_add_word_count_to_tasks(); - - // Add raw_ai_response field to tasks table if it doesn't exist - igny8_add_raw_ai_response_to_tasks(); - - // Add tasks_count field to content_ideas table if it doesn't exist - igny8_add_tasks_count_to_content_ideas(); - - // Add image_prompts field to content_ideas table if it doesn't exist - igny8_add_image_prompts_to_content_ideas(); - - // Update idea_description field to LONGTEXT for structured JSON descriptions - igny8_update_idea_description_to_longtext(); - - // Migrate ideas and tasks table structure - igny8_migrate_ideas_tasks_structure(); - - // Run legacy cleanup if needed - igny8_cleanup_legacy_structures(); -} - -/** - * Add word_count field to tasks table if it doesn't exist - */ -function igny8_add_word_count_to_tasks() { - global $wpdb; - - // Check if word_count column exists - $column_exists = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_tasks LIKE 'word_count'"); - - if (empty($column_exists)) { - // Add word_count column - $wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_tasks ADD COLUMN word_count INT UNSIGNED DEFAULT 0 AFTER keywords"); - error_log('Igny8: Added word_count column to tasks table'); - } -} - -/** - * Add raw_ai_response field to tasks table if it doesn't exist - */ -function igny8_add_raw_ai_response_to_tasks() { - global $wpdb; - - $column_exists = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_tasks LIKE 'raw_ai_response'"); - - if (empty($column_exists)) { - // Add raw_ai_response column - $wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_tasks ADD COLUMN raw_ai_response LONGTEXT DEFAULT NULL AFTER word_count"); - error_log('Igny8: Added raw_ai_response column to tasks table'); - } -} - -/** - * Add tasks_count field to content_ideas table if it doesn't exist - */ -function igny8_add_tasks_count_to_content_ideas() { - global $wpdb; - - // Check if tasks_count column exists - $column_exists = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_content_ideas LIKE 'tasks_count'"); - - if (empty($column_exists)) { - // Add tasks_count column - $wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_content_ideas ADD COLUMN tasks_count INT UNSIGNED DEFAULT 0 AFTER mapped_post_id"); - error_log('Igny8: Added tasks_count column to content_ideas table'); - } -} - -/** - * Add image_prompts field to content_ideas table if it doesn't exist - */ -function igny8_add_image_prompts_to_content_ideas() { - global $wpdb; - - // Check if image_prompts column exists - $column_exists = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_content_ideas LIKE 'image_prompts'"); - - if (empty($column_exists)) { - // Add image_prompts column - $wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_content_ideas ADD COLUMN image_prompts TEXT DEFAULT NULL AFTER target_keywords"); - error_log('Igny8: Added image_prompts column to content_ideas table'); - } -} - -/** - * Update idea_description field to LONGTEXT to support structured JSON descriptions - */ -function igny8_update_idea_description_to_longtext() { - global $wpdb; - - // Check current column type - $column_info = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_content_ideas LIKE 'idea_description'"); - - if (!empty($column_info)) { - $column_type = $column_info[0]->Type; - - // Only update if it's not already LONGTEXT - if (strpos(strtoupper($column_type), 'LONGTEXT') === false) { - $wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_content_ideas MODIFY COLUMN idea_description LONGTEXT DEFAULT NULL"); - error_log('Igny8: Updated idea_description column to LONGTEXT'); - } - } -} - -/** - * Migrate ideas and tasks table structure - */ -function igny8_migrate_ideas_tasks_structure() { - global $wpdb; - - // Migrate ideas table - igny8_migrate_ideas_table(); - - // Migrate tasks table - igny8_migrate_tasks_table(); -} - -/** - * Migrate ideas table structure - */ -function igny8_migrate_ideas_table() { - global $wpdb; - - // Check if idea_type column exists (old column) and remove it - $old_column_exists = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_content_ideas LIKE 'idea_type'"); - if (!empty($old_column_exists)) { - // Drop the old idea_type column - $wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_content_ideas DROP COLUMN idea_type"); - error_log('Igny8: Removed idea_type column from ideas table'); - } - - // Check if content_structure column exists - $column_exists = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_content_ideas LIKE 'content_structure'"); - - if (empty($column_exists)) { - // Add content_structure column with new options - $wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_content_ideas ADD COLUMN content_structure ENUM('cluster_hub','landing_page','guide_tutorial','how_to','comparison','review','top_listicle','question','product_description','service_page','home_page') DEFAULT 'cluster_hub' AFTER idea_description"); - error_log('Igny8: Added content_structure column to ideas table'); - } else { - // Update existing content_structure column with new options - $wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_content_ideas MODIFY COLUMN content_structure ENUM('cluster_hub','landing_page','guide_tutorial','how_to','comparison','review','top_listicle','question','product_description','service_page','home_page') DEFAULT 'cluster_hub'"); - error_log('Igny8: Updated content_structure column options in ideas table'); - } - - // Check if content_type column exists - $content_type_exists = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_content_ideas LIKE 'content_type'"); - - if (empty($content_type_exists)) { - // Add content_type column - $wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_content_ideas ADD COLUMN content_type ENUM('post','product','page','CPT') DEFAULT 'post' AFTER content_structure"); - error_log('Igny8: Added content_type column to ideas table'); - } - - // Update indexes - $wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_content_ideas DROP INDEX IF EXISTS idx_idea_type"); - $wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_content_ideas ADD INDEX idx_content_structure (content_structure)"); - $wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_content_ideas ADD INDEX idx_content_type (content_type)"); -} - -/** - * Migrate tasks table structure - */ -function igny8_migrate_tasks_table() { - global $wpdb; - - // Check if content_structure column exists - $column_exists = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_tasks LIKE 'content_structure'"); - - if (empty($column_exists)) { - // Check if content_type column exists (old column) - $old_column_exists = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_tasks LIKE 'content_type'"); - - if (!empty($old_column_exists)) { - // Rename content_type to content_structure with new options - $wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_tasks CHANGE COLUMN content_type content_structure ENUM('cluster_hub','landing_page','guide_tutorial','how_to','comparison','review','top_listicle','question','product_description','service_page','home_page') DEFAULT 'cluster_hub'"); - error_log('Igny8: Renamed content_type to content_structure in tasks table'); - } else { - // Add content_structure column with new options - $wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_tasks ADD COLUMN content_structure ENUM('cluster_hub','landing_page','guide_tutorial','how_to','comparison','review','top_listicle','question','product_description','service_page','home_page') DEFAULT 'cluster_hub' AFTER due_date"); - error_log('Igny8: Added content_structure column to tasks table'); - } - } else { - // Update existing content_structure column with new options - $wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_tasks MODIFY COLUMN content_structure ENUM('cluster_hub','landing_page','guide_tutorial','how_to','comparison','review','top_listicle','question','product_description','service_page','home_page') DEFAULT 'cluster_hub'"); - error_log('Igny8: Updated content_structure column options in tasks table'); - } - - // Check if content_type column exists (new column) - $content_type_exists = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_tasks LIKE 'content_type'"); - - if (empty($content_type_exists)) { - // Add content_type column - $wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_tasks ADD COLUMN content_type ENUM('post','product','page','CPT') DEFAULT 'post' AFTER content_structure"); - error_log('Igny8: Added content_type column to tasks table'); - } - - // Update indexes - $wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_tasks DROP INDEX IF EXISTS idx_content_type"); - $wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_tasks ADD INDEX idx_content_structure (content_structure)"); - $wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_tasks ADD INDEX idx_content_type (content_type)"); -} \ No newline at end of file diff --git a/igny8-wp-plugin-for-reference-olny/core/global-layout.php b/igny8-wp-plugin-for-reference-olny/core/global-layout.php deleted file mode 100644 index 1ebd709c..00000000 --- a/igny8-wp-plugin-for-reference-olny/core/global-layout.php +++ /dev/null @@ -1,463 +0,0 @@ - -- - - - - -\ No newline at end of file diff --git a/igny8-wp-plugin-for-reference-olny/debug/_README.php b/igny8-wp-plugin-for-reference-olny/debug/_README.php deleted file mode 100644 index af1bc34e..00000000 --- a/igny8-wp-plugin-for-reference-olny/debug/_README.php +++ /dev/null @@ -1,14 +0,0 @@ - -- - --- - - - -- -- - --- - ---'Dashboard', - 'igny8-planner' => 'Planner', - 'igny8-writer' => 'Writer', - 'igny8-thinker' => 'Thinker', - 'igny8-analytics' => 'Analytics', - 'igny8-settings' => 'Settings', - 'igny8-schedules' => 'Schedules', - 'igny8-help' => 'Help' - ]; - echo $page_titles[$current_page] ?? 'IGNY8 AI SEO'; - ?>
-- --- 0, 'label' => 'Active', 'color' => 'green'], - ['value' => 0, 'label' => 'Pending', 'color' => 'amber'], - ['value' => 0, 'label' => 'Completed', 'color' => 'purple'], - ['value' => 0, 'label' => 'Recent', 'color' => 'blue'] - - ]; - $fallback_metrics = array_slice($all_fallback_metrics, 0, $max_fallback); - - foreach ($fallback_metrics as $metric): - ?> -- - - -- - -- $metric_value): - $kpi_config = $GLOBALS['igny8_kpi_config'] ?? []; - $color = ''; - if (isset($kpi_config[$table_id][$metric_key]['color'])) { - $color = $kpi_config[$table_id][$metric_key]['color']; - } else { - $color = $color_map[$color_index] ?? ''; - } - - $label = $metric_key; - if (isset($kpi_config[$table_id][$metric_key]['label'])) { - $label = $kpi_config[$table_id][$metric_key]['label']; - } - ?> -- - -- -- No content provided.'; ?> - - - - - - - --- $current_module, - 'submodule' => $current_submodule, - 'table_id' => $table_id, - 'page' => $_GET['page'] ?? '' - ]; -} - - - - - - -/** - * Track AI Logs State - */ -function igny8_track_ai_logs_state($module_info) { - // Check if AI functions are available - if (!function_exists('igny8_get_ai_setting')) { - return [ - 'status' => 'error', - 'message' => 'AI system not available', - 'details' => 'igny8_get_ai_setting function missing' - ]; - } - - // Check AI mode - $ai_mode = igny8_get_ai_setting('planner_mode', 'manual'); - $ai_enabled = $ai_mode === 'ai'; - - // Get recent AI logs (last 10 events) - $ai_logs = get_option('igny8_ai_logs', []); - $recent_logs = array_slice($ai_logs, 0, 10); - - $log_count = count($recent_logs); - $error_count = 0; - $success_count = 0; - - foreach ($recent_logs as $log) { - if ($log['status'] === 'error') { - $error_count++; - } elseif ($log['status'] === 'success') { - $success_count++; - } - } - - $details = [ - 'AI Mode: ' . ($ai_enabled ? 'β Enabled' : 'β Disabled'), - 'Recent Events: ' . $log_count, - 'Success: ' . $success_count . ' | Errors: ' . $error_count, - 'Module: ' . $module_info['module'], - 'Submodule: ' . ($module_info['submodule'] ?: 'NONE') - ]; - - if ($ai_enabled && $log_count > 0) { - $status = $error_count === 0 ? 'success' : ($error_count < $success_count ? 'warning' : 'error'); - $message = "AI Logs: {$log_count} events ({$success_count} success, {$error_count} errors)"; - } elseif ($ai_enabled) { - $status = 'success'; - $message = 'AI Logs: Ready (no events yet)'; - } else { - $status = 'warning'; - $message = 'AI Logs: AI mode disabled'; - } - - return [ - 'status' => $status, - 'message' => $message, - 'details' => implode('--Debug Functionality Moved
-- All debug monitoring functionality has been moved to the Settings > Status page. -
- - Go to Status Page - -
', $details) - ]; -} - -/** - * Track Database Validation State - */ -function igny8_track_database_validation_state() { - $module_info = igny8_get_current_module_info(); - $table_id = $module_info['table_id']; - - // Get actual database table name and fields - global $wpdb; - $table_name = igny8_get_table_name($table_id); - - if (!$table_name) { - return [ - 'status' => 'error', - 'message' => 'Table not found', - 'details' => 'Table ID: ' . $table_id . ' - No table name mapping found' - ]; - } - - // Get actual database fields - $db_fields = []; - $table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$table_name}'") !== null; - - if ($table_exists) { - $columns = $wpdb->get_results("DESCRIBE {$table_name}"); - foreach ($columns as $column) { - $db_fields[] = $column->Field; - } - } else { - return [ - 'status' => 'error', - 'message' => 'Database table missing', - 'details' => 'Table: ' . $table_name . ' does not exist in database' - ]; - } - - // Validate table config fields - $table_errors = igny8_validate_table_config_fields($table_id, $db_fields); - $filter_errors = igny8_validate_filter_config_fields($table_id, $db_fields); - $form_errors = igny8_validate_form_config_fields($table_id, $db_fields); - - $total_errors = count($table_errors) + count($filter_errors) + count($form_errors); - - if ($total_errors === 0) { - return [ - 'status' => 'success', - 'message' => 'Database validation passed', - 'details' => 'Table: β
Filters: β
Forms: β
Automation: β ' - ]; - } else { - $error_details = []; - - // Format table errors - if (!empty($table_errors)) { - // Filter out config errors and show only field names - $field_errors = array_filter($table_errors, function($error) { - return !strpos($error, 'config') && !strpos($error, 'missing'); - }); - if (!empty($field_errors)) { - $error_details[] = 'Table:
' . implode(', ', $field_errors) . ' mismatch with db, doesn\'t exist'; - } - } - - // Format filter errors - if (!empty($filter_errors)) { - // Filter out config errors and show only field names - $field_errors = array_filter($filter_errors, function($error) { - return !strpos($error, 'config') && !strpos($error, 'missing'); - }); - if (!empty($field_errors)) { - $error_details[] = 'Filters:
' . implode(', ', $field_errors) . ' mismatch with db, doesn\'t exist'; - } - } - - // Format form errors - if (!empty($form_errors)) { - // Filter out config errors and show only field names - $field_errors = array_filter($form_errors, function($error) { - return !strpos($error, 'config') && !strpos($error, 'missing'); - }); - if (!empty($field_errors)) { - $error_details[] = 'Forms:
' . implode(', ', $field_errors) . ' mismatch with db, doesn\'t exist'; - } - } - - // If no field errors, show config errors - if (empty($error_details)) { - if (!empty($table_errors)) { - $error_details[] = 'Table:
' . implode(', ', $table_errors); - } - if (!empty($filter_errors)) { - $error_details[] = 'Filters:
' . implode(', ', $filter_errors); - } - if (!empty($form_errors)) { - $error_details[] = 'Forms:
' . implode(', ', $form_errors); - } - } - - return [ - 'status' => 'error', - 'message' => 'Database validation failed', - 'details' => implode('
', $error_details) - ]; - } -} - -/** - * Validate table config fields against database schema - */ -function igny8_validate_table_config_fields($table_id, $db_fields) { - $errors = []; - - try { - // Load config file directly - $config_path = plugin_dir_path(__FILE__) . '../modules/config/tables-config.php'; - if (!file_exists($config_path)) { - return ['tables_config_file_missing']; - } - - // Define constant to bypass access control - if (!defined('IGNY8_INCLUDE_CONFIG')) { - define('IGNY8_INCLUDE_CONFIG', true); - } - - $tables_config = require $config_path; - if (!isset($tables_config[$table_id])) { - return ['table_config_not_found']; - } - - $table_config = $tables_config[$table_id]; - - // Check columns - if (isset($table_config['columns'])) { - foreach ($table_config['columns'] as $column_name => $column_config) { - // For display columns, check the source_field instead of the column name - $field_to_check = $column_name; - if (isset($column_config['source_field'])) { - $field_to_check = $column_config['source_field']; - } - - if (!in_array($field_to_check, $db_fields)) { - $errors[] = $column_name; - } - } - } - - // Check humanize_columns - if (isset($table_config['humanize_columns'])) { - foreach ($table_config['humanize_columns'] as $column_name) { - if (!in_array($column_name, $db_fields)) { - $errors[] = $column_name; - } - } - } - - } catch (Exception $e) { - $errors[] = 'config_load_error: ' . $e->getMessage(); - } - - return $errors; -} - -/** - * Validate filter config fields against database schema - */ -function igny8_validate_filter_config_fields($table_id, $db_fields) { - $errors = []; - - try { - // Load config file directly - $config_path = plugin_dir_path(__FILE__) . '../modules/config/filters-config.php'; - if (!file_exists($config_path)) { - return ['filters_config_file_missing']; - } - - // Define constant to bypass access control - if (!defined('IGNY8_INCLUDE_CONFIG')) { - define('IGNY8_INCLUDE_CONFIG', true); - } - - $filters_config = require $config_path; - if (!isset($filters_config[$table_id])) { - return []; // No filters config is OK - } - - $filter_config = $filters_config[$table_id]; - - foreach ($filter_config as $filter_name => $filter_data) { - if (isset($filter_data['field']) && !in_array($filter_data['field'], $db_fields)) { - $errors[] = $filter_data['field']; - } - } - - } catch (Exception $e) { - $errors[] = 'config_load_error: ' . $e->getMessage(); - } - - return $errors; -} - -/** - * Validate form config fields against database schema - */ -function igny8_validate_form_config_fields($table_id, $db_fields) { - $errors = []; - - try { - // Load config file directly - $config_path = plugin_dir_path(__FILE__) . '../modules/config/forms-config.php'; - if (!file_exists($config_path)) { - return ['forms_config_file_missing']; - } - - // Define constant to bypass access control - if (!defined('IGNY8_INCLUDE_CONFIG')) { - define('IGNY8_INCLUDE_CONFIG', true); - } - - $forms_config = require $config_path; - if (!isset($forms_config[$table_id])) { - return []; // No form config is OK - } - - $form_config = $forms_config[$table_id]; - if (!$form_config || !isset($form_config['fields'])) { - return []; // No form config is OK - } - - foreach ($form_config['fields'] as $field) { - if (isset($field['name']) && !in_array($field['name'], $db_fields)) { - $errors[] = $field['name']; - } - } - - } catch (Exception $e) { - $errors[] = 'config_load_error: ' . $e->getMessage(); - } - - return $errors; -} - -function igny8_test_column_type_validation($config_data, $table_id) { - if (!isset($config_data[$table_id]['columns'])) { - return ['passed' => false, 'error' => 'no columns']; - } - - $columns = $config_data[$table_id]['columns']; - foreach ($columns as $key => $column) { - if (!isset($column['type']) || !in_array($column['type'], ['text', 'number', 'date', 'select', 'textarea', 'enum'])) { - return ['passed' => false, 'error' => 'invalid type: ' . $key]; - } - } - return ['passed' => true, 'error' => '']; -} - -function igny8_test_filter_field_validation($config_data, $table_id) { - if (!isset($config_data[$table_id])) { - return ['passed' => false, 'error' => 'no filters']; - } - - $filters = $config_data[$table_id]; - foreach ($filters as $key => $filter) { - if (!isset($filter['field']) || empty($filter['field'])) { - return ['passed' => false, 'error' => 'missing field: ' . $key]; - } - } - return ['passed' => true, 'error' => '']; -} - -function igny8_test_field_validation_check($config_data, $table_id) { - if (!function_exists('igny8_get_form_config')) { - return ['passed' => false, 'error' => 'function missing']; - } - - $form_config = igny8_get_form_config($table_id); - if (!$form_config || !isset($form_config['fields'])) { - return ['passed' => false, 'error' => 'no form fields']; - } - - foreach ($form_config['fields'] as $field) { - if (!isset($field['name']) || !isset($field['type'])) { - return ['passed' => false, 'error' => 'invalid field structure']; - } - - // Test foreign key relationships - if (isset($field['type']) && $field['type'] === 'select' && (isset($field['options']) || isset($field['source']))) { - $fk_result = igny8_test_foreign_key_relationship($field, $table_id); - if (!$fk_result['passed']) { - return ['passed' => false, 'error' => 'FK issue: ' . $field['name'] . ' - ' . $fk_result['error']]; - } - } - } - return ['passed' => true, 'error' => '']; -} - -function igny8_test_foreign_key_relationship($field, $table_id) { - // Check if this is a foreign key field (like cluster_id, category_id, etc.) - $field_name = $field['name']; - - // Common foreign key patterns - $fk_patterns = ['cluster_id', 'category_id', 'parent_id', 'user_id', 'group_id']; - $is_foreign_key = false; - - foreach ($fk_patterns as $pattern) { - if (strpos($field_name, $pattern) !== false) { - $is_foreign_key = true; - break; - } - } - - if (!$is_foreign_key) { - return ['passed' => true, 'error' => '']; // Not a foreign key, skip test - } - - // CRITICAL ISSUE: Check if field uses 'source' with custom select rendering - if (isset($field['source']) && $field['type'] === 'select') { - // This is the cluster_id issue - custom select button vs form submission mismatch - return ['passed' => false, 'error' => 'custom select rendering - form submission mismatch']; - } - - return ['passed' => true, 'error' => '']; -} - -function igny8_test_query_syntax_validation($config_data, $table_id) { - if (!isset($config_data[$table_id])) { - return ['passed' => false, 'error' => 'no kpi config']; - } - - $kpis = $config_data[$table_id]; - foreach ($kpis as $key => $kpi) { - if (!isset($kpi['query']) || empty($kpi['query'])) { - return ['passed' => false, 'error' => 'missing query: ' . $key]; - } - - // Check for basic SQL syntax - if (!preg_match('/SELECT.*FROM.*\{table_name\}/i', $kpi['query'])) { - return ['passed' => false, 'error' => 'invalid query syntax: ' . $key]; - } - } - return ['passed' => true, 'error' => '']; -} - -/** - * Track Routing State - */ -function igny8_track_routing_state() { - $module_info = igny8_get_current_module_info(); - $current_page = $_GET['page'] ?? ''; - $current_submodule = $_GET['sm'] ?? ''; - - $routing_ok = !empty($current_page) && strpos($current_page, 'igny8-') === 0; - $submodule_ok = !empty($current_submodule); - - return [ - 'status' => $routing_ok ? 'success' : 'error', - 'message' => "Routing: {$current_page}" . ($submodule_ok ? " β {$current_submodule}" : ""), - 'details' => "Table ID: {$module_info['table_id']}" - ]; -} - -/** - * Track Initial Render State - */ -function igny8_track_initial_render_state() { - $module_info = igny8_get_current_module_info(); - $table_id = $module_info['table_id']; - - $render_functions = [ - 'igny8_render_filters' => 'Filters', - 'igny8_render_table_actions' => 'Actions', - 'igny8_render_pagination' => 'Pagination' - ]; - - $working_functions = 0; - $total_functions = count($render_functions); - $details = []; - - foreach ($render_functions as $function => $name) { - if (function_exists($function)) { - $working_functions++; - $details[] = "{$name}: β "; - } else { - $details[] = "{$name}: β"; - } - } - - // Test form rendering system - $form_tests = igny8_test_form_rendering_system($table_id); - $details[] = "Form Rendering: " . ($form_tests['passed'] ? 'β ' : 'β ' . $form_tests['error']); - if ($form_tests['passed']) { - $working_functions++; - } - $total_functions++; - - return [ - 'status' => $working_functions === $total_functions ? 'success' : 'warning', - 'message' => "Render functions: {$working_functions}/{$total_functions}", - 'details' => implode('
', $details) - ]; -} - -/** - * Test form rendering system - */ -function igny8_test_form_rendering_system($table_id) { - // Test 1: Form config function exists - if (!function_exists('igny8_get_form_config')) { - return ['passed' => false, 'error' => 'igny8_get_form_config missing']; - } - - // Test 2: Form rendering function exists - if (!function_exists('igny8_render_inline_form_row')) { - return ['passed' => false, 'error' => 'igny8_render_inline_form_row missing']; - } - - // Test 3: Form field rendering functions exist - $field_functions = [ - 'igny8_render_form_field', - 'igny8_render_text_field', - 'igny8_render_number_field', - 'igny8_render_select_field', - 'igny8_render_textarea_field' - ]; - - foreach ($field_functions as $func) { - if (!function_exists($func)) { - return ['passed' => false, 'error' => "{$func} missing"]; - } - } - - // Test 4: Form config loads for current table - try { - $form_config = igny8_get_form_config($table_id); - if (!$form_config) { - return ['passed' => false, 'error' => 'No form config for table']; - } - - if (empty($form_config['fields'])) { - return ['passed' => false, 'error' => 'Empty form fields']; - } - - // Test 5: Form can render without errors - $test_output = igny8_render_inline_form_row($table_id, 'add', []); - if (empty($test_output) || strpos($test_output, 'Form not configured') !== false) { - return ['passed' => false, 'error' => 'Form render failed']; - } - - } catch (Exception $e) { - return ['passed' => false, 'error' => 'Form config error: ' . $e->getMessage()]; - } - - return ['passed' => true, 'error' => '']; -} - -/** - * Track Table Rendering State - */ -function igny8_track_table_rendering_state() { - $module_info = igny8_get_current_module_info(); - $table_id = $module_info['table_id']; - - // Test 1: Table render function exists - if (!function_exists('igny8_render_table')) { - return [ - 'status' => 'error', - 'message' => 'Table render function missing', - 'details' => 'igny8_render_table function not found' - ]; - } - - // Test 2: AJAX table loading functions exist - $ajax_functions = [ - 'igny8_get_table_data' => 'Main table data loader', - 'igny8_ajax_load_table_data' => 'Submodule table loader' - ]; - - $working_ajax = 0; - $total_ajax = count($ajax_functions); - $ajax_details = []; - - foreach ($ajax_functions as $func => $name) { - if (function_exists($func)) { - $working_ajax++; - $ajax_details[] = "{$name}: β "; - } else { - $ajax_details[] = "{$name}: β"; - } - } - - // Test 3: Check if table has actually loaded data - $table_data_status = igny8_check_table_data_loaded($table_id); - - // Test 4: AJAX states tracking (only meaningful after AJAX has run) - $ajax_states = igny8_track_ajax_table_states(); - - $total_tests = 4; - $passed_tests = ($working_ajax === $total_ajax ? 1 : 0) + - ($table_data_status['passed'] ? 1 : 0) + - ($ajax_states['passed'] ? 1 : 0) + 1; // +1 for function existence - - $details = [ - 'Function exists: β ', - 'AJAX functions: ' . $working_ajax . '/' . $total_ajax, - implode('
', $ajax_details), - 'Table data loaded: ' . ($table_data_status['passed'] ? 'β ' : 'β³ ' . $table_data_status['message']), - 'AJAX states: ' . ($ajax_states['passed'] ? 'β ' : 'β³ ' . $ajax_states['error']), - $ajax_states['details'] - ]; - - return [ - 'status' => $passed_tests === $total_tests ? 'success' : ($passed_tests > 2 ? 'warning' : 'error'), - 'message' => "Table rendering: {$passed_tests}/{$total_tests} tests passed", - 'details' => implode('
', $details) - ]; -} - -/** - * Check if table has actually loaded data - */ -function igny8_check_table_data_loaded($table_id) { - // Check if there's recent AJAX response data - if (isset($GLOBALS['igny8_last_ajax_response'])) { - $response = $GLOBALS['igny8_last_ajax_response']; - $time_diff = time() - $response['timestamp']; - - // If response is recent (within 30 seconds) and has data - if ($time_diff < 30 && !empty($response['data']['items'])) { - return [ - 'passed' => true, - 'message' => 'Data loaded (' . count($response['data']['items']) . ' rows)' - ]; - } - } - - // Check if AJAX states indicate successful loading - if (isset($GLOBALS['igny8_debug_states']['TABLE_AJAX_RESPONSE_OK'])) { - $state = $GLOBALS['igny8_debug_states']['TABLE_AJAX_RESPONSE_OK']; - if ($state['status'] === true) { - return [ - 'passed' => true, - 'message' => 'AJAX response successful' - ]; - } - } - - // If no data loaded yet - return [ - 'passed' => false, - 'message' => 'Waiting for AJAX data load' - ]; -} - -/** - * Debug state helper function - stores debug states in globals - */ -function igny8_debug_state($stage, $ok, $msg) { - if (!isset($GLOBALS['igny8_debug_states'])) { - $GLOBALS['igny8_debug_states'] = []; - } - - $GLOBALS['igny8_debug_states'][$stage] = [ - 'status' => $ok, - 'message' => $msg, - 'timestamp' => time() - ]; -} - -/** - * Track AJAX table states - */ -function igny8_track_ajax_table_states() { - // The 4 AJAX states that indicate successful table loading - $ajax_states = [ - 'AJAX_NONCE_VALIDATED' => false, - 'USER_CAPABILITY_OK' => false, - 'TABLE_AJAX_RESPONSE_OK' => false, - 'TABLE_AJAX_REQUEST_SENT' => false - ]; - - $details = []; - $passed_states = 0; - - // Check each AJAX state - foreach ($ajax_states as $state_name => $state_value) { - // Check if this state has been set by AJAX functions - if (isset($GLOBALS['igny8_debug_states'][$state_name])) { - $state_data = $GLOBALS['igny8_debug_states'][$state_name]; - if ($state_data['status'] === true) { - $passed_states++; - $details[] = "{$state_name}: β "; - } else { - $details[] = "{$state_name}: β " . $state_data['message']; - } - } else { - $details[] = "{$state_name}: β³ Not triggered yet"; - } - } - - // Check if AJAX tracking system is available - if (!function_exists('igny8_debug_state')) { - return [ - 'passed' => false, - 'error' => 'AJAX tracking system not available', - 'details' => implode('
', $details) - ]; - } - - // If no states have been triggered yet (page just loaded) - if ($passed_states === 0 && !isset($GLOBALS['igny8_debug_states'])) { - return [ - 'passed' => true, - 'error' => 'AJAX tracking ready (no requests yet)', - 'details' => implode('
', $details) - ]; - } - - return [ - 'passed' => $passed_states === 4, - 'error' => $passed_states === 4 ? '' : "Only {$passed_states}/4 states passed", - 'details' => implode('
', $details) - ]; -} - -/** - * Track Filters State - */ -function igny8_track_filters_state() { - $module_info = igny8_get_current_module_info(); - $table_id = $module_info['table_id']; - - // Test 1: Filter render function exists - if (!function_exists('igny8_render_filters')) { - return [ - 'status' => 'error', - 'message' => 'Filter render function missing', - 'details' => 'igny8_render_filters function not found' - ]; - } - - // Test 2: Filter config loads for current table (direct method) - try { - // Load filters config directly like igny8_render_filters() does - $filters_config_path = plugin_dir_path(__FILE__) . '../modules/config/filters-config.php'; - if (!file_exists($filters_config_path)) { - return [ - 'status' => 'error', - 'message' => 'Filter config file missing', - 'details' => 'filters-config.php not found at: ' . $filters_config_path - ]; - } - - $filters_config = require $filters_config_path; - $filter_config = $filters_config[$table_id] ?? []; - - if (empty($filter_config)) { - return [ - 'status' => 'warning', - 'message' => 'No filter config for table', - 'details' => 'Filter config not found for: ' . $table_id - ]; - } - - $filter_count = count($filter_config); - - } catch (Exception $e) { - return [ - 'status' => 'error', - 'message' => 'Filter config error', - 'details' => 'Error loading filter config: ' . $e->getMessage() - ]; - } - - // Test 4: Filter can render without errors - try { - $test_output = igny8_render_filters($table_id); - if (empty($test_output) || strpos($test_output, 'Filter not configured') !== false) { - $render_test = 'β Filter render failed'; - } else { - $render_test = 'β Filter renders OK'; - } - } catch (Exception $e) { - $render_test = 'β Render error: ' . $e->getMessage(); - } - - $details = [ - 'Render function: β ', - 'Config file: β ', - 'Filter config: β (' . $filter_count . ' filters)', - 'Render test: ' . $render_test - ]; - - return [ - 'status' => 'success', - 'message' => "Filters: {$filter_count} filters configured", - 'details' => implode('
', $details) - ]; -} - -/** - * Track Forms State - */ -function igny8_track_forms_state() { - $module_info = igny8_get_current_module_info(); - $table_id = $module_info['table_id']; - - // Test 1: Form render function exists - if (!function_exists('igny8_render_inline_form_row')) { - return [ - 'status' => 'error', - 'message' => 'Form render function missing', - 'details' => 'igny8_render_inline_form_row function not found' - ]; - } - - // Test 2: Form config function exists - if (!function_exists('igny8_get_form_config')) { - return [ - 'status' => 'error', - 'message' => 'Form config function missing', - 'details' => 'igny8_get_form_config function not found' - ]; - } - - // Test 3: Form config loads for current table - try { - $form_config = igny8_get_form_config($table_id); - if (!$form_config) { - return [ - 'status' => 'warning', - 'message' => 'No form config for table', - 'details' => 'Form config not found for: ' . $table_id - ]; - } - - if (empty($form_config['fields'])) { - return [ - 'status' => 'warning', - 'message' => 'Empty form fields', - 'details' => 'Form config exists but has no fields' - ]; - } - - $field_count = count($form_config['fields']); - - } catch (Exception $e) { - return [ - 'status' => 'error', - 'message' => 'Form config error', - 'details' => 'Error loading form config: ' . $e->getMessage() - ]; - } - - // Test 4: Form can render without errors - try { - $test_output = igny8_render_inline_form_row($table_id, 'add', []); - if (empty($test_output) || strpos($test_output, 'Form not configured') !== false) { - $render_test = 'β Form render failed'; - } else { - $render_test = 'β Form renders OK'; - } - } catch (Exception $e) { - $render_test = 'β Render error: ' . $e->getMessage(); - } - - $details = [ - 'Function exists: β ', - 'Config function: β ', - 'Form config: β (' . $field_count . ' fields)', - 'Render test: ' . $render_test - ]; - - return [ - 'status' => 'success', - 'message' => "Forms: {$field_count} fields configured", - 'details' => implode('
', $details) - ]; -} - -/** - * Track Automation Validation State - Submodule Specific - */ -function igny8_track_automation_validation_state() { - $module_info = igny8_get_current_module_info(); - $current_submodule = $module_info['submodule']; - - $automation_errors = []; - $automation_functions = []; - $working_functions = 0; - - try { - // Define submodule-specific automation functions - switch ($current_submodule) { - case 'keywords': - $automation_functions = [ - 'igny8_update_cluster_metrics' => 'Cluster metrics update', - 'igny8_handle_keyword_cluster_update' => 'Keyword cluster updates', - 'igny8_bulk_delete_keywords' => 'Bulk keyword deletion', - 'igny8_bulk_map_keywords' => 'Bulk keyword mapping', - 'igny8_bulk_unmap_keywords' => 'Bulk keyword unmapping', - 'igny8_ajax_ai_cluster_keywords' => 'AI cluster creation', - 'igny8_workflow_triggers' => 'Workflow triggers', - 'igny8_ajax_import_keywords' => 'Keyword import automation' - ]; - break; - - case 'clusters': - $automation_functions = [ - 'igny8_update_cluster_metrics' => 'Cluster metrics update', - 'igny8_auto_create_cluster_term' => 'Cluster taxonomy creation', - 'igny8_auto_update_cluster_term' => 'Cluster taxonomy updates', - 'igny8_handle_content_cluster_association' => 'Content cluster associations', - 'igny8_ajax_ai_generate_ideas' => 'AI idea generation', - 'igny8_workflow_triggers' => 'Workflow triggers' - ]; - break; - - case 'ideas': - $automation_functions = [ - 'igny8_update_idea_metrics' => 'Idea metrics update', - 'igny8_create_task_from_idea' => 'Task creation from ideas', - 'igny8_workflow_triggers' => 'Workflow triggers', - 'igny8_write_log' => 'Automation logging', - 'igny8_ajax_ai_generate_ideas' => 'AI idea generation', - 'igny8_ajax_ai_generate_content' => 'AI content generation' - ]; - break; - - default: - // Fallback to basic automation functions - $automation_functions = [ - 'igny8_update_cluster_metrics' => 'Cluster metrics update', - 'igny8_handle_keyword_cluster_update' => 'Keyword cluster updates', - 'igny8_workflow_triggers' => 'Workflow triggers' - ]; - } - - // Check if each automation function exists - $function_details = []; - foreach ($automation_functions as $function => $description) { - if (function_exists($function)) { - $working_functions++; - $function_details[] = "{$description}: β "; - } else { - // Check if this is a known missing function - if (strpos($description, 'MISSING') !== false) { - $automation_errors[] = "{$description}: β (Function never implemented - referenced in docs but missing from code)"; - $function_details[] = "{$description}: β (NOT IMPLEMENTED)"; - } else { - $automation_errors[] = "{$description}: β ({$function} missing)"; - $function_details[] = "{$description}: β"; - } - } - } - - $total_functions = count($automation_functions); - - } catch (Exception $e) { - $automation_errors[] = 'Automation check error: ' . $e->getMessage(); - } - - if (empty($automation_errors)) { - return [ - 'status' => 'success', - 'message' => "Automation: {$working_functions}/{$total_functions} functions active", - 'details' => implode('
', $function_details) - ]; - } else { - return [ - 'status' => $working_functions > ($total_functions / 2) ? 'warning' : 'error', - 'message' => "Automation: {$working_functions}/{$total_functions} functions active", - 'details' => implode('
', $function_details) . '
Issues:
' . implode('
', $automation_errors) - ]; - } -} - -/** - * Track Database Pre-Fetch State - */ -function igny8_track_db_prefetch_state() { - global $wpdb; - $module_info = igny8_get_current_module_info(); - - // Get table name - if (function_exists('igny8_get_table_name')) { - $table_name = igny8_get_table_name($module_info['table_id']); - } else { - $table_name = $wpdb->prefix . 'igny8_' . str_replace('_', '', $module_info['table_id']); - } - - // Check if table exists - $table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$table_name}'") !== null; - - if ($table_exists) { - $row_count = $wpdb->get_var("SELECT COUNT(*) FROM {$table_name}"); - return [ - 'status' => 'success', - 'message' => "Table: {$table_name} ({$row_count} rows)", - 'details' => "Database connection: β
Table exists: β " - ]; - } else { - return [ - 'status' => 'error', - 'message' => "Table not found: {$table_name}", - 'details' => "Database connection: " . (!empty($wpdb->dbh) ? 'β ' : 'β') . "
Table exists: β" - ]; - } -} - -/** - * Track Frontend Initialization State - */ -function igny8_track_frontend_init_state() { - // Check if core.js is enqueued - $core_js_enqueued = wp_script_is('igny8-admin-js', 'enqueued'); - - // Check if required DOM elements exist (this will be checked by JavaScript) - return [ - 'status' => $core_js_enqueued ? 'success' : 'warning', - 'message' => "Frontend JS: " . ($core_js_enqueued ? 'Enqueued' : 'Not enqueued'), - 'details' => "Core.js loaded: " . ($core_js_enqueued ? 'β ' : 'β') . "
DOM elements: Checked by JS" - ]; -} - - - -// Get module debug content using consolidated evidence system -function igny8_get_module_debug_content() { - // Get current module information - $module_info = igny8_get_current_module_info(); - $module_name = ucfirst($module_info['module']); - $current_submodule = $module_info['submodule']; - - // Track debug states - split into component states and automation states - $component_states = [ - 'database' => igny8_track_database_validation_state(), - 'table' => igny8_track_initial_render_state(), - 'filters' => igny8_track_filters_state(), - 'forms' => igny8_track_forms_state() - ]; - - $automation_states = [ - 'automation' => igny8_track_automation_validation_state(), - 'ai_logs' => igny8_track_ai_logs_state($module_info) - ]; - - ob_start(); - ?> - --- - --- -- π - Module Debug: -- -- ---- - ---Module Information
- - -- Module:-
- Submodule:
- Table ID:
- Page: --- - -Submodule Components Render
-- $state_data): ?> ---- -- - - - --- --- ---- -Automation & AI Systems
-- $state_data): ?> - ---- -- - - - --- --- ---- - - ---- π€ - AI Logs & Events --- - ------ - -- Status: Loading... --- Initializing... ----- --
Loading AI events... --- - - - - Generate Images - -``` - -### JavaScript Event Handler -- **File**: `assets/js/image-queue-processor.js` -- **Function**: `processAIImageGenerationDrafts(postIds)` -- **Lines**: 7-84 -- **Validation**: Max 10 posts, requires selection - -## 2. JavaScript Processing Flow - -### Main Processing Function -- **File**: `assets/js/image-queue-processor.js` -- **Function**: `processAIImageGenerationDrafts(postIds)` -- **Lines**: 7-84 - -### Settings Retrieved -```javascript -const desktopEnabled = window.IGNY8_PAGE?.imageSettings?.desktop_enabled || false; -const mobileEnabled = window.IGNY8_PAGE?.imageSettings?.mobile_enabled || false; -const maxInArticleImages = window.IGNY8_PAGE?.imageSettings?.max_in_article_images || 1; -``` - -### Image Calculation -- **Featured Images**: Always 1 per post -- **Desktop Images**: `maxInArticleImages` per post (if enabled) -- **Mobile Images**: `maxInArticleImages` per post (if enabled) -- **Total**: `postIds.length * imagesPerPost` - -### Queue Processing -- **Sequential Processing**: One image at a time to avoid API limits -- **Progress Tracking**: Real-time progress updates with individual progress bars -- **Error Handling**: Continue processing on individual failures -- **Modal Display**: Shows progress for each image being generated - -## 3. AJAX Handlers - -### Primary Handler -- **File**: `core/admin/ajax.php` -- **Function**: `igny8_ajax_ai_generate_images_drafts()` -- **Lines**: 2913-3150 -- **Hook**: `wp_ajax_igny8_ai_generate_images_drafts` - -### Single Image Handler -- **File**: `core/admin/ajax.php` -- **Function**: `igny8_ajax_ai_generate_single_image()` -- **Lines**: 3283-3350 -- **Hook**: `wp_ajax_igny8_ai_generate_single_image` -- **Process**: - 1. Validates task ID and retrieves WordPress post ID - 2. Calls appropriate generation function based on type - 3. Saves image metadata to post meta - 4. Returns success/error response - -### Supporting Handlers -- **Image Counts**: `igny8_ajax_get_image_counts()` (Lines 2645-2809) -- **Single Image Queue**: `igny8_ajax_generate_single_image_queue()` (Lines 2814-2908) -- **Settings Save**: `igny8_ajax_save_image_settings()` (Lines 4408-4457) -- **Template Save**: `igny8_ajax_save_image_prompt_template()` (Lines 4461-4505) - -## 4. Settings and Configuration - -### WordPress Options -| **Option** | **Default** | **Purpose** | -|------------|-------------|-------------| -| `igny8_desktop_enabled` | `'1'` | Enable desktop image generation | -| `igny8_mobile_enabled` | `'0'` | Enable mobile image generation | -| `igny8_max_in_article_images` | `1` | Maximum in-article images per post | -| `igny8_image_type` | `'realistic'` | Image style type | -| `igny8_image_provider` | `'runware'` | AI provider for image generation | -| `igny8_image_format` | `'jpg'` | Output image format | -| `igny8_negative_prompt` | `'text, watermark, logo, overlay, title, caption, writing on walls, writing on objects, UI, infographic elements, post title'` | Negative prompt for image generation | -| `igny8_image_prompt_template` | `'Create a high-quality {image_type} image to use as a featured photo for a blog post titled "{post_title}". The image should visually represent the theme, mood, and subject implied by the image prompt: {image_prompt}. Focus on a realistic, well-composed scene that naturally communicates the topic without text or logos. Use balanced lighting, pleasing composition, and photographic detail suitable for lifestyle or editorial web content. Avoid adding any visible or readable text, brand names, or illustrative effects. **And make sure image is not blurry.**'` | Image prompt template | - -### Settings Page -- **File**: `modules/settings/general-settings.php` -- **Lines**: 309-462 -- **Form Fields**: Desktop/Mobile toggles, Max images dropdown, Image type, Provider, Format, Negative prompt, Template - -## 5. Image Generation Functions - -### Core Generation Functions -- **File**: `ai/writer/images/image-generation.php` - -#### Featured Image Generation -- **Function**: `igny8_generate_featured_image_for_post($post_id, $image_size_type = 'featured')` -- **Lines**: 24-200 -- **Process**: - 1. Get featured image prompt from `_igny8_featured_image_prompt` - 2. Get image generation settings from WordPress options - 3. Build final prompt using template with placeholders - 4. Call AI provider (Runware/OpenAI) with appropriate dimensions - 5. Download image from AI provider URL - 6. Upload via `media_handle_sideload()` - 7. Set as featured image with `set_post_thumbnail()` - 8. Generate attachment metadata - -#### In-Article Image Generation -- **Function**: `igny8_generate_single_article_image($post_id, $device_type = 'desktop', $index = 1)` -- **Lines**: 202-400 -- **Process**: - 1. Get article image prompts from `_igny8_article_images_data` - 2. Extract prompt for specific index (`prompt-img-1`, `prompt-img-2`, etc.) - 3. Get image generation settings from WordPress options - 4. Build final prompt using template with placeholders - 5. Call AI provider with device-specific dimensions - 6. Download image from AI provider URL - 7. Upload via `media_handle_sideload()` - 8. Return attachment ID and metadata - -### Supporting Functions -- **Image Dimensions**: `igny8_get_image_dimensions($size_preset, $provider)` (Lines 1526-1558 in ai/modules-ai.php) -- **Safe Quantity**: `igny8_calculate_safe_image_quantity($idea_data, $max_in_article_images)` (Lines 918-933 in ai/modules-ai.php) -- **Image Meta**: `igny8_add_inarticle_image_meta($post_id, $attachment_id, $label, $device, $section)` (Lines 933-963 in ai/modules-ai.php) - -## 6. AI Provider Integration - -### Runware API Integration -- **File**: `ai/runware-api.php` -- **Function**: `igny8_runway_generate_image($prompt, $negative_prompt, $width, $height, $steps, $cfg_scale, $number_results, $output_format)` -- **API Endpoint**: `https://api.runware.ai/v1` -- **Authentication**: API key via `igny8_runware_api_key` option -- **Model**: `runware:97@1` (HiDream-I1 Full) -- **Request Format**: - ```json - [ - { - "taskType": "authentication", - "apiKey": "api_key_here" - }, - { - "taskType": "imageInference", - "taskUUID": "uuid_here", - "positivePrompt": "prompt_text", - "negativePrompt": "negative_prompt", - "model": "runware:97@1", - "width": 1024, - "height": 1024, - "steps": 30, - "CFGScale": 7.5, - "numberResults": 1, - "outputFormat": "jpg" - } - ] - ``` -- **Response Handling**: Extract `data[0].imageURL` from response -- **Error Handling**: Log API errors, return error messages - -### OpenAI DALL-E Integration -- **File**: `ai/openai-api.php` -- **Function**: `igny8_call_openai_images($prompt, $api_key, $model, $size, $quality, $style)` -- **API Endpoint**: `https://api.openai.com/v1/images/generations` -- **Authentication**: API key via `igny8_api_key` option -- **Model**: `dall-e-3` -- **Request Format**: - ```json - { - "model": "dall-e-3", - "prompt": "prompt_text", - "n": 1, - "size": "1024x1024", - "quality": "standard", - "style": "natural" - } - ``` -- **Response Handling**: Extract `data[0].url` from response -- **Error Handling**: Log API errors, return error messages - -### Provider Detection and Selection -- **Settings**: `igny8_image_provider` option (`runware` or `openai`) -- **API Key Validation**: Check `igny8_runware_api_key` or `igny8_api_key` -- **Dynamic Selection**: Based on user settings in prompts page -- **Fallback Handling**: Return error if required API key not configured - -### Image Dimensions by Provider -- **Runware Dimensions**: - - Featured: 1280x832 - - Desktop: 1024x1024 - - Mobile: 960x1280 -- **OpenAI Dimensions**: - - Featured: 1024x1024 - - Desktop: 1024x1024 - - Mobile: 1024x1024 - -## 7. Data Storage and Meta Keys - -### Post Meta Keys -| **Meta Key** | **Purpose** | **Data Type** | -|--------------|-------------|---------------| -| `_igny8_featured_image_prompt` | Featured image AI prompt | Text | -| `_igny8_article_images_data` | In-article image prompts | JSON Array | -| `_igny8_inarticle_images` | Generated image metadata | JSON Object | - -### Image Prompts Data Structure -```json -[ - { - "prompt-img-1": "A close-up of a neatly made bed showcasing a well-fitted duvet cover that enhances the aesthetic of the room." - }, - { - "prompt-img-2": "An image of tools laid out for measuring a duvet insert, including a measuring tape, notepad, and a flat surface." - }, - { - "prompt-img-3": "A detailed size chart displaying different duvet cover sizes alongside their corresponding duvet insert dimensions." - }, - { - "prompt-img-4": "An infographic illustrating common mistakes when choosing duvet covers, highlighting shrinkage risks and misreading labels." - } -] -``` - -### Image Metadata Structure -```json -{ - "desktop-1": { - "label": "desktop-1", - "attachment_id": 1825, - "url": "https://example.com/image.jpg", - "device": "desktop", - "section": 1 - }, - "mobile-1": { - "label": "mobile-1", - "attachment_id": 1826, - "url": "https://example.com/image2.jpg", - "device": "mobile", - "section": 1 - } -} -``` - -## 8. Image Processing Workflow - -### Step-by-Step Process -1. **Button Click**: User selects posts and clicks "Generate Images" -2. **Validation**: Check selection count (max 10), get settings -3. **Queue Building**: Build image queue with featured and in-article images -4. **Progress Modal**: Show progress with individual progress bars -5. **Sequential Processing**: Process each image individually -6. **Image Generation**: For each image: - - Get prompt from appropriate meta field - - Call AI provider with settings - - Download image from provider URL - - Upload to WordPress Media Library - - Save metadata to post meta -7. **Progress Updates**: Update UI with success/failure status -8. **Completion**: Show final results - -### Error Handling -- **Download Failures**: Log errors, continue with next image -- **Upload Failures**: Log errors, return failure status -- **API Failures**: Log errors, return failure status -- **Progress Tracking**: Update modal with success/failure counts -- **JSON Parsing**: Handle HTML content in prompts with `wp_strip_all_tags()` - -## 9. Content Generation Integration - -### Image Prompts in Content Generation -- **File**: `ai/modules-ai.php` -- **Function**: `igny8_create_post_from_ai_response($ai_response)` -- **Lines**: 1119-1462 -- **Process**: - 1. AI generates content with image prompts - 2. Featured image prompt saved to `_igny8_featured_image_prompt` - 3. In-article image prompts saved to `_igny8_article_images_data` - 4. HTML tags stripped from prompts using `wp_strip_all_tags()` - 5. JSON structure validated and saved - -### Prompt Template Processing -- **Template**: Uses `{image_type}`, `{post_title}`, `{image_prompt}` placeholders -- **Replacement**: Dynamic replacement with actual values -- **Settings Integration**: Uses all settings from prompts page - -## 10. Image Generation Queue Mechanism - -### Queue Processing -- **File**: `assets/js/image-queue-processor.js` -- **Function**: `processAIImageGenerationDrafts(postIds)` -- **Lines**: 7-84 -- **Process**: Sequential image generation with progress tracking - -### Queue Features -- **Sequential Processing**: One image at a time to avoid API limits -- **Progress Tracking**: Real-time progress updates with individual bars -- **Error Handling**: Continue processing on individual failures -- **Batch Management**: Handle multiple posts with image counts -- **Modal Display**: Shows detailed progress for each image - -### Queue Handler -- **File**: `core/admin/ajax.php` -- **Function**: `igny8_ajax_ai_generate_single_image()` -- **Lines**: 3283-3350 -- **Hook**: `wp_ajax_igny8_ai_generate_single_image` - -## 11. Debug and Logging - -### Debug Functions -- **File**: `debug/module-debug.php` -- **Lines**: 1185-1221 (HTML), 1447-1613 (JavaScript) -- **Features**: - - Image generation logs interface - - Refresh/clear buttons (`refresh-image-gen`, `clear-image-gen`) - - Real-time event display (`image-gen-events`) - - Status messages (`image-gen-message`, `image-gen-details`) - - Global debug function (`window.addImageGenDebugLog`) - -### Comprehensive Logging System -- **Event-Based Logging**: `IMAGE_GEN_EVENT_1` through `IMAGE_GEN_EVENT_9` -- **Debug Events Array**: `$debug_events[]` for detailed tracking -- **Error Logging**: `error_log()` with specific prefixes -- **AI Event Logging**: `igny8_log_ai_event()` for AI interactions - -### Logging Points -- **AJAX Entry**: `error_log('Igny8: AJAX HANDLER CALLED - igny8_ajax_ai_generate_images_drafts')` -- **Task Validation**: `error_log('Igny8: IMAGE_GEN_EVENT_3 - Task IDs validated')` -- **Post Retrieval**: `error_log('Igny8: IMAGE_GEN_EVENT_4 - WordPress post IDs retrieved')` -- **Image Prompts**: `error_log('Igny8: IMAGE_GEN_EVENT_5 - Image prompts loaded')` -- **Featured Generation**: `error_log('Igny8: IMAGE_GEN_EVENT_6 - Featured image generation initiated')` -- **API Requests**: `error_log('Igny8: IMAGE_GEN_EVENT_7 - API request sent')` -- **Image URLs**: `error_log('Igny8: IMAGE_GEN_EVENT_8 - Image URL received')` -- **WordPress Save**: `error_log('Igny8: IMAGE_GEN_EVENT_9 - Saving image to WordPress')` -- **Success/Failure**: `error_log('Igny8: IMAGE_GEN_EVENT_9_SUCCESS/ERROR')` - -## 12. File Dependencies - -### Core Files -- `ai/writer/images/image-generation.php` - Main image generation functions -- `core/admin/ajax.php` - AJAX handlers -- `assets/js/image-queue-processor.js` - Queue processing JavaScript -- `ai/modules-ai.php` - Content generation response handler -- `ai/openai-api.php` - OpenAI DALL-E API integration -- `ai/runware-api.php` - Runware API integration - -### Settings Files -- `modules/settings/general-settings.php` - Main image generation settings page -- `modules/thinker/prompts.php` - Image prompt templates only -- `modules/thinker/image-testing.php` - Image testing interface -- `modules/modules-pages/writer.php` - Settings localization - -### Debug Files -- `debug/module-debug.php` - Debug interface - -## 13. Hooks and Actions - -### WordPress Hooks -- `wp_ajax_igny8_ai_generate_images_drafts` - Main generation handler -- `wp_ajax_igny8_ai_generate_single_image` - Single image handler -- `wp_ajax_igny8_generate_single_image_queue` - Queue handler -- `wp_ajax_igny8_get_image_counts` - Image count preview -- `wp_ajax_igny8_save_image_settings` - Settings save -- `wp_ajax_igny8_save_image_prompt_template` - Template save -- `wp_ajax_igny8_reset_image_prompt_template` - Template reset - -### Internal Hooks -- `transition_post_status` - Post status changes -- `save_post` - Post save events - -## 14. Security Considerations - -### Nonce Verification -- All AJAX handlers verify nonces -- Settings forms use proper nonce fields -- User capability checks for admin functions - -### Data Sanitization -- All input data sanitized with `sanitize_text_field()` -- File uploads handled via WordPress functions -- Image URLs validated before processing -- HTML tags stripped from prompts using `wp_strip_all_tags()` - -## 15. Performance Considerations - -### Sequential Processing -- Images generated one at a time to avoid API limits -- Progress tracking for user feedback -- Error handling to continue processing - -### Media Library Integration -- Proper WordPress Media Library registration -- Automatic thumbnail generation -- Metadata attachment for SEO - -## 16. Complete Function Reference - -### AJAX Handlers -- `igny8_ajax_ai_generate_images_drafts()` - Main generation -- `igny8_ajax_ai_generate_single_image()` - Single image -- `igny8_ajax_generate_single_image_queue()` - Queue processing -- `igny8_ajax_get_image_counts()` - Count preview -- `igny8_ajax_save_image_settings()` - Settings save -- `igny8_ajax_save_image_prompt_template()` - Template save -- `igny8_ajax_reset_image_prompt_template()` - Template reset - -### Generation Functions -- `igny8_generate_featured_image_for_post()` - Featured image -- `igny8_generate_single_article_image()` - In-article image -- `igny8_get_image_dimensions()` - Size calculation -- `igny8_calculate_safe_image_quantity()` - Quantity safety - -### Content Functions -- `igny8_add_inarticle_image_meta()` - Image metadata saving -- `igny8_format_image_prompts_for_ai()` - Prompt formatting -- `igny8_create_post_from_ai_response()` - Content generation response handler - -### Queue Processing Functions -- `processAIImageGenerationDrafts()` - Main queue processor -- `generateAllImagesForPost()` - Single post processing -- `updateProgressModal()` - Progress updates - -### API Functions -- `igny8_runway_generate_image()` - Runware API integration -- `igny8_call_openai_images()` - OpenAI DALL-E API integration - -### Settings Functions -- `igny8_ajax_save_image_settings()` - Settings save -- `igny8_ajax_save_image_prompt_template()` - Template save -- `igny8_ajax_reset_image_prompt_template()` - Template reset - -## 17. Recent Changes and Improvements - -### Code Reorganization -- **Image generation functions moved** from `ai/modules-ai.php` to `ai/writer/images/image-generation.php` -- **Dedicated module** for image generation functionality -- **Improved separation of concerns** between content generation and image generation - -### Enhanced Error Handling -- **JSON parsing improvements** with HTML tag stripping -- **Better error messages** for debugging -- **Graceful fallbacks** for API failures - -### Improved Queue Processing -- **Individual progress bars** for each image -- **Better error tracking** and reporting -- **Enhanced user feedback** during processing - -### Settings Integration -- **Dynamic settings** from prompts page -- **Template-based prompts** with placeholder replacement -- **Provider selection** with appropriate API key validation - -This audit covers every aspect of the current image generation process from initial button click to final image storage and metadata saving, including the complete queue mechanism, settings integration, and content generation workflow. \ No newline at end of file diff --git a/igny8-wp-plugin-for-reference-olny/docs/COMPLETE_WORKFLOWS_DOCUMENTATION.md b/igny8-wp-plugin-for-reference-olny/docs/COMPLETE_WORKFLOWS_DOCUMENTATION.md deleted file mode 100644 index df162345..00000000 --- a/igny8-wp-plugin-for-reference-olny/docs/COMPLETE_WORKFLOWS_DOCUMENTATION.md +++ /dev/null @@ -1,723 +0,0 @@ -# Igny8 AI SEO Plugin - Complete Workflows Documentation - -## Summary Table - -| Workflow | Process Steps | Functions Involved | Dependencies | Files Involved | -|----------|---------------|-------------------|--------------|----------------| -| **Content Planning** | Keyword Research β Clustering β Ideas β Queue | `igny8_research_keywords()`, `igny8_ai_cluster_keywords()`, `igny8_ai_generate_ideas()`, `igny8_queue_ideas_to_writer()` | OpenAI API, Database, WordPress | `modules/modules-pages/planner.php`, `ai/modules-ai.php`, `flows/sync-functions.php` | -| **Content Creation** | Task Creation β AI Generation β Draft Review β Publishing | `igny8_create_task()`, `igny8_generate_content()`, `igny8_review_draft()`, `igny8_publish_content()` | AI APIs, WordPress, Database | `modules/modules-pages/writer.php`, `ai/modules-ai.php`, `flows/sync-functions.php` | -| **SEO Optimization** | Content Analysis β Suggestions β Implementation β Monitoring | `igny8_analyze_content()`, `igny8_generate_suggestions()`, `igny8_implement_optimizations()`, `igny8_monitor_performance()` | SEO APIs, Database | `modules/modules-pages/optimizer.php`, `ai/modules-ai.php` | -| **Link Building** | Campaign Planning β Outreach β Tracking β Analysis | `igny8_plan_campaign()`, `igny8_manage_outreach()`, `igny8_track_backlinks()`, `igny8_analyze_results()` | External APIs, Database | `modules/modules-pages/linker.php`, `flows/sync-functions.php` | -| **Content Personalization** | Field Detection β Content Rewriting β Frontend Display β Analytics | `igny8_detect_fields()`, `igny8_rewrite_content()`, `igny8_display_personalized()`, `igny8_track_personalization()` | OpenAI API, Frontend | `modules/modules-pages/personalize/`, `ai/integration.php` | -| **Automation Workflows** | Schedule Setup β Task Execution β Monitoring β Optimization | `igny8_schedule_tasks()`, `igny8_execute_automation()`, `igny8_monitor_automation()`, `igny8_optimize_automation()` | WordPress CRON, Database | `core/cron/`, `core/pages/settings/schedules.php` | -| **Analytics & Reporting** | Data Collection β Analysis β Visualization β Reporting | `igny8_collect_metrics()`, `igny8_analyze_data()`, `igny8_visualize_results()`, `igny8_generate_reports()` | Database, WordPress | `core/admin/`, `modules/config/kpi-config.php` | - ---- - -## 1. CONTENT PLANNING WORKFLOW - -### 1.1 Keyword Research Process - -#### Step 1: Keyword Import -- **Function**: `igny8_import_keywords()` -- **Process**: CSV file upload and validation -- **Dependencies**: File upload system, data validation -- **Files**: `modules/components/import-modal-tpl.php`, `flows/sync-functions.php` -- **Validation**: Duplicate detection, data format validation -- **Output**: Validated keyword data in database - -#### Step 2: Keyword Analysis -- **Function**: `igny8_analyze_keywords()` -- **Process**: Keyword metrics calculation and categorization -- **Dependencies**: External SEO APIs, database queries -- **Files**: `ai/modules-ai.php`, `core/db/db.php` -- **Analysis**: Volume, difficulty, competition scoring -- **Output**: Analyzed keyword data with metrics - -#### Step 3: Keyword Categorization -- **Function**: `igny8_categorize_keywords()` -- **Process**: Primary/secondary keyword classification -- **Dependencies**: AI analysis, keyword relationships -- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php` -- **Classification**: Primary, secondary, long-tail categorization -- **Output**: Categorized keyword data - -### 1.2 AI Clustering Process - -#### Step 1: Cluster Analysis -- **Function**: `igny8_ai_cluster_keywords()` -- **Process**: AI-powered semantic clustering -- **Dependencies**: OpenAI API, keyword data -- **Files**: `ai/modules-ai.php`, `ai/openai-api.php` -- **Analysis**: Semantic similarity analysis -- **Output**: Keyword cluster assignments - -#### Step 2: Cluster Optimization -- **Function**: `igny8_optimize_clusters()` -- **Process**: Cluster refinement and optimization -- **Dependencies**: Cluster data, AI analysis -- **Files**: `flows/sync-functions.php`, `ai/modules-ai.php` -- **Optimization**: Cluster size, keyword distribution -- **Output**: Optimized cluster structure - -#### Step 3: Cluster Metrics -- **Function**: `igny8_calculate_cluster_metrics()` -- **Process**: Cluster performance calculation -- **Dependencies**: Cluster data, keyword metrics -- **Files**: `core/admin/global-helpers.php`, `flows/sync-functions.php` -- **Metrics**: Volume aggregation, keyword count -- **Output**: Cluster performance metrics - -### 1.3 Content Idea Generation - -#### Step 1: Idea Generation -- **Function**: `igny8_ai_generate_ideas()` -- **Process**: AI-powered content idea creation -- **Dependencies**: OpenAI API, cluster data -- **Files**: `ai/modules-ai.php`, `ai/openai-api.php` -- **Generation**: Content ideas based on clusters -- **Output**: Generated content ideas - -#### Step 2: Idea Categorization -- **Function**: `igny8_categorize_ideas()` -- **Process**: Content type and priority classification -- **Dependencies**: AI analysis, content templates -- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php` -- **Categorization**: Content type, priority scoring -- **Output**: Categorized content ideas - -#### Step 3: Idea Queue Management -- **Function**: `igny8_queue_ideas_to_writer()` -- **Process**: Idea prioritization and queue management -- **Dependencies**: Idea data, writer module -- **Files**: `flows/sync-functions.php`, `modules/modules-pages/writer.php` -- **Queue**: Priority-based idea queuing -- **Output**: Queued content ideas for writer - ---- - -## 2. CONTENT CREATION WORKFLOW - -### 2.1 Task Creation Process - -#### Step 1: Task Generation -- **Function**: `igny8_create_writing_task()` -- **Process**: Content task creation from ideas -- **Dependencies**: Content ideas, writer settings -- **Files**: `modules/modules-pages/writer.php`, `flows/sync-functions.php` -- **Creation**: Task assignment and configuration -- **Output**: Writing tasks in database - -#### Step 2: Task Prioritization -- **Function**: `igny8_prioritize_tasks()` -- **Process**: Task priority calculation and ordering -- **Dependencies**: Task data, priority algorithms -- **Files**: `flows/sync-functions.php`, `core/admin/global-helpers.php` -- **Prioritization**: Priority scoring and ordering -- **Output**: Prioritized task queue - -#### Step 3: Task Assignment -- **Function**: `igny8_assign_tasks()` -- **Process**: Task assignment to writers or AI -- **Dependencies**: Task data, user preferences -- **Files**: `modules/modules-pages/writer.php`, `flows/sync-functions.php` -- **Assignment**: Writer or AI assignment -- **Output**: Assigned writing tasks - -### 2.2 AI Content Generation - -#### Step 1: Content Generation -- **Function**: `igny8_generate_content()` -- **Process**: AI-powered content creation -- **Dependencies**: OpenAI API, task data -- **Files**: `ai/modules-ai.php`, `ai/openai-api.php` -- **Generation**: AI content creation -- **Output**: Generated content drafts - -#### Step 2: Content Optimization -- **Function**: `igny8_optimize_content()` -- **Process**: SEO and readability optimization -- **Dependencies**: AI analysis, SEO rules -- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php` -- **Optimization**: SEO, readability improvements -- **Output**: Optimized content - -#### Step 3: Content Validation -- **Function**: `igny8_validate_content()` -- **Process**: Content quality and compliance checking -- **Dependencies**: Content data, quality rules -- **Files**: `flows/sync-functions.php`, `core/admin/global-helpers.php` -- **Validation**: Quality, compliance checks -- **Output**: Validated content - -### 2.3 Draft Management - -#### Step 1: Draft Creation -- **Function**: `igny8_create_draft()` -- **Process**: WordPress draft post creation -- **Dependencies**: WordPress, content data -- **Files**: `flows/sync-functions.php`, `core/db/db.php` -- **Creation**: WordPress draft creation -- **Output**: Draft posts in WordPress - -#### Step 2: Draft Review -- **Function**: `igny8_review_draft()` -- **Process**: Content review and editing interface -- **Dependencies**: WordPress admin, draft data -- **Files**: `modules/modules-pages/writer.php`, `core/admin/meta-boxes.php` -- **Review**: Content review interface -- **Output**: Reviewed draft content - -#### Step 3: Draft Optimization -- **Function**: `igny8_optimize_draft()` -- **Process**: Draft content optimization -- **Dependencies**: AI analysis, optimization rules -- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php` -- **Optimization**: Content improvement -- **Output**: Optimized draft content - -### 2.4 Publishing Process - -#### Step 1: Publishing Preparation -- **Function**: `igny8_prepare_publishing()` -- **Process**: Pre-publication checks and preparation -- **Dependencies**: Draft data, publishing rules -- **Files**: `flows/sync-functions.php`, `core/admin/global-helpers.php` -- **Preparation**: Pre-publication validation -- **Output**: Publishing-ready content - -#### Step 2: Content Publishing -- **Function**: `igny8_publish_content()` -- **Process**: WordPress post publication -- **Dependencies**: WordPress, draft data -- **Files**: `flows/sync-functions.php`, `core/db/db.php` -- **Publishing**: WordPress post publication -- **Output**: Published content - -#### Step 3: Post-Publishing -- **Function**: `igny8_post_publish_actions()` -- **Process**: Post-publication tasks and monitoring -- **Dependencies**: Published content, monitoring systems -- **Files**: `flows/sync-functions.php`, `flows/image-injection-responsive.php` -- **Actions**: Image injection, monitoring setup -- **Output**: Fully published and monitored content - ---- - -## 3. SEO OPTIMIZATION WORKFLOW - -### 3.1 Content Analysis Process - -#### Step 1: SEO Audit -- **Function**: `igny8_audit_content()` -- **Process**: Comprehensive SEO analysis -- **Dependencies**: Content data, SEO rules -- **Files**: `modules/modules-pages/optimizer.php`, `ai/modules-ai.php` -- **Analysis**: SEO factor analysis -- **Output**: SEO audit results - -#### Step 2: Keyword Analysis -- **Function**: `igny8_analyze_keywords()` -- **Process**: Keyword usage and optimization analysis -- **Dependencies**: Content data, keyword data -- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php` -- **Analysis**: Keyword density, placement analysis -- **Output**: Keyword optimization recommendations - -#### Step 3: Technical SEO -- **Function**: `igny8_analyze_technical_seo()` -- **Process**: Technical SEO factor analysis -- **Dependencies**: Content data, technical rules -- **Files**: `modules/modules-pages/optimizer.php`, `core/admin/global-helpers.php` -- **Analysis**: Technical SEO factors -- **Output**: Technical SEO recommendations - -### 3.2 Optimization Suggestions - -#### Step 1: Suggestion Generation -- **Function**: `igny8_generate_suggestions()` -- **Process**: AI-powered optimization suggestions -- **Dependencies**: AI analysis, content data -- **Files**: `ai/modules-ai.php`, `ai/openai-api.php` -- **Generation**: AI optimization suggestions -- **Output**: Optimization recommendations - -#### Step 2: Suggestion Prioritization -- **Function**: `igny8_prioritize_suggestions()` -- **Process**: Suggestion priority and impact analysis -- **Dependencies**: Suggestion data, impact algorithms -- **Files**: `flows/sync-functions.php`, `core/admin/global-helpers.php` -- **Prioritization**: Impact-based prioritization -- **Output**: Prioritized suggestions - -#### Step 3: Implementation Planning -- **Function**: `igny8_plan_implementation()` -- **Process**: Implementation strategy development -- **Dependencies**: Suggestions, content data -- **Files**: `modules/modules-pages/optimizer.php`, `flows/sync-functions.php` -- **Planning**: Implementation strategy -- **Output**: Implementation plan - -### 3.3 Performance Monitoring - -#### Step 1: Metrics Collection -- **Function**: `igny8_collect_metrics()` -- **Process**: Performance data collection -- **Dependencies**: Analytics APIs, content data -- **Files**: `core/admin/global-helpers.php`, `modules/config/kpi-config.php` -- **Collection**: Performance data gathering -- **Output**: Performance metrics - -#### Step 2: Performance Analysis -- **Function**: `igny8_analyze_performance()` -- **Process**: Performance trend and pattern analysis -- **Dependencies**: Metrics data, analysis algorithms -- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php` -- **Analysis**: Performance trend analysis -- **Output**: Performance insights - -#### Step 3: Optimization Adjustment -- **Function**: `igny8_adjust_optimization()` -- **Process**: Performance-based optimization adjustments -- **Dependencies**: Performance data, optimization rules -- **Files**: `flows/sync-functions.php`, `ai/modules-ai.php` -- **Adjustment**: Optimization fine-tuning -- **Output**: Adjusted optimization strategy - ---- - -## 4. LINK BUILDING WORKFLOW - -### 4.1 Campaign Planning - -#### Step 1: Campaign Strategy -- **Function**: `igny8_plan_campaign()` -- **Process**: Link building campaign strategy development -- **Dependencies**: Content data, target analysis -- **Files**: `modules/modules-pages/linker.php`, `ai/modules-ai.php` -- **Planning**: Campaign strategy development -- **Output**: Campaign strategy - -#### Step 2: Target Identification -- **Function**: `igny8_identify_targets()` -- **Process**: High-value link target identification -- **Dependencies**: Content data, authority analysis -- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php` -- **Identification**: Target site identification -- **Output**: Link building targets - -#### Step 3: Outreach Planning -- **Function**: `igny8_plan_outreach()` -- **Process**: Outreach strategy and message development -- **Dependencies**: Target data, content data -- **Files**: `modules/modules-pages/linker.php`, `flows/sync-functions.php` -- **Planning**: Outreach strategy -- **Output**: Outreach plan - -### 4.2 Outreach Management - -#### Step 1: Outreach Execution -- **Function**: `igny8_execute_outreach()` -- **Process**: Automated outreach campaign execution -- **Dependencies**: Outreach data, communication systems -- **Files**: `flows/sync-functions.php`, `modules/modules-pages/linker.php` -- **Execution**: Outreach campaign execution -- **Output**: Outreach activities - -#### Step 2: Follow-up Management -- **Function**: `igny8_manage_followups()` -- **Process**: Follow-up communication management -- **Dependencies**: Outreach data, follow-up rules -- **Files**: `flows/sync-functions.php`, `core/admin/global-helpers.php` -- **Management**: Follow-up communication -- **Output**: Follow-up activities - -#### Step 3: Relationship Building -- **Function**: `igny8_build_relationships()` -- **Process**: Long-term relationship development -- **Dependencies**: Contact data, relationship rules -- **Files**: `modules/modules-pages/linker.php`, `flows/sync-functions.php` -- **Building**: Relationship development -- **Output**: Established relationships - -### 4.3 Backlink Tracking - -#### Step 1: Backlink Discovery -- **Function**: `igny8_discover_backlinks()` -- **Process**: Automated backlink detection and tracking -- **Dependencies**: External APIs, monitoring systems -- **Files**: `flows/sync-functions.php`, `core/db/db.php` -- **Discovery**: Backlink detection -- **Output**: Discovered backlinks - -#### Step 2: Backlink Analysis -- **Function**: `igny8_analyze_backlinks()` -- **Process**: Backlink quality and authority analysis -- **Dependencies**: Backlink data, authority metrics -- **Files**: `ai/modules-ai.php`, `core/admin/global-helpers.php` -- **Analysis**: Backlink quality analysis -- **Output**: Backlink analysis results - -#### Step 3: Performance Tracking -- **Function**: `igny8_track_backlink_performance()` -- **Process**: Backlink impact and performance monitoring -- **Dependencies**: Backlink data, performance metrics -- **Files**: `flows/sync-functions.php`, `modules/config/kpi-config.php` -- **Tracking**: Performance monitoring -- **Output**: Performance tracking data - ---- - -## 5. CONTENT PERSONALIZATION WORKFLOW - -### 5.1 Field Detection Process - -#### Step 1: Content Analysis -- **Function**: `igny8_analyze_content_for_fields()` -- **Process**: AI-powered field detection from content -- **Dependencies**: OpenAI API, content data -- **Files**: `modules/modules-pages/personalize/content-generation.php`, `ai/integration.php` -- **Analysis**: Content field analysis -- **Output**: Detected personalization fields - -#### Step 2: Field Configuration -- **Function**: `igny8_configure_fields()` -- **Process**: Field configuration and customization -- **Dependencies**: Field data, user preferences -- **Files**: `modules/modules-pages/personalize/content-generation.php`, `flows/sync-functions.php` -- **Configuration**: Field setup and customization -- **Output**: Configured personalization fields - -#### Step 3: Field Validation -- **Function**: `igny8_validate_fields()` -- **Process**: Field validation and testing -- **Dependencies**: Field data, validation rules -- **Files**: `flows/sync-functions.php`, `core/admin/global-helpers.php` -- **Validation**: Field validation and testing -- **Output**: Validated personalization fields - -### 5.2 Content Rewriting Process - -#### Step 1: Content Rewriting -- **Function**: `igny8_rewrite_content()` -- **Process**: AI-powered content personalization -- **Dependencies**: OpenAI API, user data -- **Files**: `ai/integration.php`, `ai/openai-api.php` -- **Rewriting**: Content personalization -- **Output**: Personalized content - -#### Step 2: Content Optimization -- **Function**: `igny8_optimize_personalized_content()` -- **Process**: Personalized content optimization -- **Dependencies**: Personalized content, optimization rules -- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php` -- **Optimization**: Content optimization -- **Output**: Optimized personalized content - -#### Step 3: Content Validation -- **Function**: `igny8_validate_personalized_content()` -- **Process**: Personalized content quality validation -- **Dependencies**: Personalized content, quality rules -- **Files**: `flows/sync-functions.php`, `core/admin/global-helpers.php` -- **Validation**: Content quality validation -- **Output**: Validated personalized content - -### 5.3 Frontend Integration - -#### Step 1: Frontend Setup -- **Function**: `igny8_setup_frontend()` -- **Process**: Frontend personalization setup -- **Dependencies**: Frontend configuration, personalization data -- **Files**: `modules/modules-pages/personalize/front-end.php`, `flows/sync-functions.php` -- **Setup**: Frontend configuration -- **Output**: Configured frontend personalization - -#### Step 2: User Interface -- **Function**: `igny8_display_personalization_interface()` -- **Process**: Personalization interface display -- **Dependencies**: Frontend templates, user data -- **Files**: `assets/js/core.js`, `modules/components/forms-tpl.php` -- **Display**: Personalization interface -- **Output**: User personalization interface - -#### Step 3: Content Delivery -- **Function**: `igny8_deliver_personalized_content()` -- **Process**: Personalized content delivery -- **Dependencies**: Personalized content, delivery systems -- **Files**: `flows/sync-functions.php`, `core/db/db.php` -- **Delivery**: Content delivery -- **Output**: Delivered personalized content - ---- - -## 6. AUTOMATION WORKFLOWS - -### 6.1 Schedule Management - -#### Step 1: Schedule Configuration -- **Function**: `igny8_configure_schedules()` -- **Process**: Automation schedule setup and configuration -- **Dependencies**: Schedule data, automation rules -- **Files**: `core/pages/settings/schedules.php`, `flows/sync-functions.php` -- **Configuration**: Schedule setup -- **Output**: Configured automation schedules - -#### Step 2: Task Scheduling -- **Function**: `igny8_schedule_tasks()` -- **Process**: Automated task scheduling -- **Dependencies**: WordPress CRON, task data -- **Files**: `core/cron/igny8-cron-master-dispatcher.php`, `core/cron/igny8-cron-handlers.php` -- **Scheduling**: Task scheduling -- **Output**: Scheduled automation tasks - -#### Step 3: Schedule Monitoring -- **Function**: `igny8_monitor_schedules()` -- **Process**: Schedule performance monitoring -- **Dependencies**: Schedule data, monitoring systems -- **Files**: `core/cron/igny8-cron-master-dispatcher.php`, `core/admin/global-helpers.php` -- **Monitoring**: Schedule monitoring -- **Output**: Schedule monitoring data - -### 6.2 Automation Execution - -#### Step 1: Task Execution -- **Function**: `igny8_execute_automation()` -- **Process**: Automated task execution -- **Dependencies**: Task data, execution systems -- **Files**: `core/cron/igny8-cron-handlers.php`, `flows/sync-functions.php` -- **Execution**: Task execution -- **Output**: Executed automation tasks - -#### Step 2: Process Monitoring -- **Function**: `igny8_monitor_automation()` -- **Process**: Automation process monitoring -- **Dependencies**: Automation data, monitoring systems -- **Files**: `core/cron/igny8-cron-master-dispatcher.php`, `core/admin/global-helpers.php` -- **Monitoring**: Process monitoring -- **Output**: Automation monitoring data - -#### Step 3: Error Handling -- **Function**: `igny8_handle_automation_errors()` -- **Process**: Automation error handling and recovery -- **Dependencies**: Error data, recovery systems -- **Files**: `core/cron/igny8-cron-handlers.php`, `flows/sync-functions.php` -- **Handling**: Error handling and recovery -- **Output**: Error handling results - -### 6.3 Performance Optimization - -#### Step 1: Performance Analysis -- **Function**: `igny8_analyze_automation_performance()` -- **Process**: Automation performance analysis -- **Dependencies**: Performance data, analysis algorithms -- **Files**: `core/admin/global-helpers.php`, `modules/config/kpi-config.php` -- **Analysis**: Performance analysis -- **Output**: Performance insights - -#### Step 2: Optimization Adjustment -- **Function**: `igny8_optimize_automation()` -- **Process**: Automation optimization -- **Dependencies**: Performance data, optimization rules -- **Files**: `flows/sync-functions.php`, `ai/modules-ai.php` -- **Optimization**: Automation optimization -- **Output**: Optimized automation - -#### Step 3: Continuous Improvement -- **Function**: `igny8_improve_automation()` -- **Process**: Continuous automation improvement -- **Dependencies**: Performance data, improvement algorithms -- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php` -- **Improvement**: Continuous improvement -- **Output**: Improved automation - ---- - -## 7. ANALYTICS & REPORTING WORKFLOW - -### 7.1 Data Collection - -#### Step 1: Metrics Collection -- **Function**: `igny8_collect_metrics()` -- **Process**: Performance metrics collection -- **Dependencies**: Analytics APIs, content data -- **Files**: `core/admin/global-helpers.php`, `modules/config/kpi-config.php` -- **Collection**: Metrics gathering -- **Output**: Collected performance metrics - -#### Step 2: Data Processing -- **Function**: `igny8_process_analytics_data()` -- **Process**: Analytics data processing and preparation -- **Dependencies**: Raw data, processing algorithms -- **Files**: `flows/sync-functions.php`, `core/admin/global-helpers.php` -- **Processing**: Data processing -- **Output**: Processed analytics data - -#### Step 3: Data Storage -- **Function**: `igny8_store_analytics_data()` -- **Process**: Analytics data storage and organization -- **Dependencies**: Processed data, database systems -- **Files**: `core/db/db.php`, `flows/sync-functions.php` -- **Storage**: Data storage -- **Output**: Stored analytics data - -### 7.2 Analysis & Insights - -#### Step 1: Data Analysis -- **Function**: `igny8_analyze_analytics_data()` -- **Process**: Analytics data analysis and insights -- **Dependencies**: Stored data, analysis algorithms -- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php` -- **Analysis**: Data analysis -- **Output**: Analytics insights - -#### Step 2: Trend Analysis -- **Function**: `igny8_analyze_trends()` -- **Process**: Performance trend analysis -- **Dependencies**: Historical data, trend algorithms -- **Files**: `ai/modules-ai.php`, `core/admin/global-helpers.php` -- **Analysis**: Trend analysis -- **Output**: Trend insights - -#### Step 3: Predictive Analytics -- **Function**: `igny8_predict_performance()` -- **Process**: Performance prediction and forecasting -- **Dependencies**: Historical data, prediction algorithms -- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php` -- **Prediction**: Performance prediction -- **Output**: Predictive insights - -### 7.3 Reporting & Visualization - -#### Step 1: Report Generation -- **Function**: `igny8_generate_reports()` -- **Process**: Automated report generation -- **Dependencies**: Analytics data, report templates -- **Files**: `modules/components/kpi-tpl.php`, `flows/sync-functions.php` -- **Generation**: Report generation -- **Output**: Generated reports - -#### Step 2: Data Visualization -- **Function**: `igny8_visualize_data()` -- **Process**: Analytics data visualization -- **Dependencies**: Analytics data, visualization tools -- **Files**: `modules/components/kpi-tpl.php`, `assets/js/core.js` -- **Visualization**: Data visualization -- **Output**: Visualized analytics data - -#### Step 3: Report Distribution -- **Function**: `igny8_distribute_reports()` -- **Process**: Report distribution and delivery -- **Dependencies**: Generated reports, distribution systems -- **Files**: `flows/sync-functions.php`, `core/admin/global-helpers.php` -- **Distribution**: Report distribution -- **Output**: Distributed reports - ---- - -## 8. INTEGRATION WORKFLOWS - -### 8.1 WordPress Integration - -#### Step 1: WordPress Setup -- **Function**: `igny8_setup_wordpress_integration()` -- **Process**: WordPress integration setup -- **Dependencies**: WordPress hooks, plugin system -- **Files**: `igny8.php`, `core/admin/init.php` -- **Setup**: WordPress integration -- **Output**: Integrated WordPress functionality - -#### Step 2: Post Meta Management -- **Function**: `igny8_manage_post_meta()` -- **Process**: WordPress post metadata management -- **Dependencies**: WordPress post system, metadata -- **Files**: `core/db/db.php`, `flows/sync-functions.php` -- **Management**: Post metadata management -- **Output**: Managed post metadata - -#### Step 3: Taxonomy Integration -- **Function**: `igny8_integrate_taxonomies()` -- **Process**: Custom taxonomy integration -- **Dependencies**: WordPress taxonomy system -- **Files**: `core/db/db.php`, `core/admin/init.php` -- **Integration**: Taxonomy integration -- **Output**: Integrated taxonomies - -### 8.2 AI Service Integration - -#### Step 1: AI Service Setup -- **Function**: `igny8_setup_ai_services()` -- **Process**: AI service integration setup -- **Dependencies**: AI APIs, authentication -- **Files**: `ai/integration.php`, `ai/openai-api.php` -- **Setup**: AI service setup -- **Output**: Configured AI services - -#### Step 2: API Management -- **Function**: `igny8_manage_ai_apis()` -- **Process**: AI API management and optimization -- **Dependencies**: API credentials, rate limiting -- **Files**: `ai/openai-api.php`, `ai/runware-api.php` -- **Management**: API management -- **Output**: Managed AI APIs - -#### Step 3: Performance Monitoring -- **Function**: `igny8_monitor_ai_performance()` -- **Process**: AI service performance monitoring -- **Dependencies**: AI services, monitoring systems -- **Files**: `ai/integration.php`, `core/admin/global-helpers.php` -- **Monitoring**: AI performance monitoring -- **Output**: AI performance data - -### 8.3 Database Integration - -#### Step 1: Database Setup -- **Function**: `igny8_setup_database()` -- **Process**: Database table creation and setup -- **Dependencies**: Database system, table schemas -- **Files**: `core/db/db.php`, `install.php` -- **Setup**: Database setup -- **Output**: Configured database - -#### Step 2: Data Migration -- **Function**: `igny8_migrate_data()` -- **Process**: Data migration and version management -- **Dependencies**: Database system, migration scripts -- **Files**: `core/db/db-migration.php`, `flows/sync-functions.php` -- **Migration**: Data migration -- **Output**: Migrated data - -#### Step 3: Performance Optimization -- **Function**: `igny8_optimize_database()` -- **Process**: Database performance optimization -- **Dependencies**: Database system, optimization rules -- **Files**: `core/db/db.php`, `flows/sync-functions.php` -- **Optimization**: Database optimization -- **Output**: Optimized database - ---- - -## Technical Implementation Details - -### Workflow Dependencies -- **WordPress Core**: Hooks, actions, filters -- **Database Layer**: Custom tables, queries, migrations -- **AI Services**: OpenAI, Runware APIs -- **Frontend**: JavaScript, CSS, responsive design -- **Automation**: WordPress CRON, scheduled tasks - -### File Structure -- **Core Files**: Plugin initialization and setup -- **Module Files**: Feature-specific implementations -- **AI Integration**: AI service integrations -- **Workflows**: Process automation and management -- **Assets**: Frontend resources and templates - -### Performance Considerations -- **Caching**: Data caching and optimization -- **Database**: Query optimization and indexing -- **AI APIs**: Rate limiting and cost optimization -- **Automation**: Efficient task scheduling and execution -- **Monitoring**: Performance tracking and optimization - -This comprehensive workflows documentation covers all aspects of the Igny8 AI SEO Plugin's workflow processes, providing detailed step-by-step guidance for each workflow, including functions, dependencies, and file references. - diff --git a/igny8-wp-plugin-for-reference-olny/docs/FILE_TREE.txt b/igny8-wp-plugin-for-reference-olny/docs/FILE_TREE.txt deleted file mode 100644 index 30ad91eb..00000000 --- a/igny8-wp-plugin-for-reference-olny/docs/FILE_TREE.txt +++ /dev/null @@ -1,122 +0,0 @@ -igny8-ai-seo/ -βββ ai/ -β βββ _README.php -β βββ integration.php -β βββ model-rates-config.php -β βββ modules-ai.php -β βββ openai-api.php -β βββ prompts-library.php -β βββ runware-api.php -β βββ writer/ -β βββ content/ -β βββ images/ -β βββ image-generation.php -βββ assets/ -β βββ ai-images/ -β βββ css/ -β β βββ core-backup.css -β β βββ core.css -β β βββ image-injection.css -β βββ img/ -β βββ js/ -β β βββ core.js -β β βββ image-queue-processor.js -β βββ shortcodes/ -β β βββ _README.php -β β βββ image-gallery.php -β βββ templates/ -β βββ igny8_clusters_template.csv -β βββ igny8_ideas_template.csv -β βββ igny8_keywords_template.csv -βββ CHANGELOG_live.md -βββ core/ -β βββ _README.php -β βββ admin/ -β β βββ ajax.php -β β βββ global-helpers.php -β β βββ init.php -β β βββ menu.php -β β βββ meta-boxes.php -β β βββ module-manager-class.php -β βββ cron/ -β β βββ igny8-cron-handlers.php -β β βββ igny8-cron-master-dispatcher.php -β βββ db/ -β β βββ db-migration.php -β β βββ db.php -β βββ global-layout.php -βββ debug/ -β βββ _README.php -β βββ debug.php -β βββ module-debug.php -β βββ monitor-helpers.php -βββ docs/ -β βββ _README.php -β βββ COMPLETE_FEATURES_LIST.md -β βββ COMPLETE_FUNCTION_REFERENCE.md -β βββ COMPLETE_IMAGE_GENERATION_AUDIT.md -β βββ COMPLETE_WORKFLOWS_DOCUMENTATION.md -β βββ FILE_TREE.txt -β βββ IGNY8_PAGES_TABLE.md -β βββ IGNY8_SNAPSHOT_V5.2.0.md -β βββ TROUBLESHOOTING_Converting_to_blocks_and_image_shortcode_injection.md -βββ flows/ -β βββ sync-ajax.php -β βββ sync-functions.php -β βββ sync-hooks.php -βββ igny8-wp-load-handler.php -βββ igny8.php -βββ install.php -βββ modules/ -β βββ _README.php -β βββ analytics/ -β β βββ analytics.php -β βββ components/ -β β βββ _README.php -β β βββ actions-tpl.php -β β βββ export-modal-tpl.php -β β βββ filters-tpl.php -β β βββ forms-tpl.php -β β βββ import-modal-tpl.php -β β βββ kpi-tpl.php -β β βββ pagination-tpl.php -β β βββ table-tpl.php -β βββ config/ -β β βββ _README.php -β β βββ filters-config.php -β β βββ forms-config.php -β β βββ import-export-config.php -β β βββ kpi-config.php -β β βββ tables-config.php -β βββ help/ -β β βββ docs.php -β β βββ function-testing.php -β β βββ help.php -β β βββ system-testing.php -β βββ home.php -β βββ planner/ -β β βββ clusters.php -β β βββ ideas.php -β β βββ keywords.php -β β βββ planner.php -β βββ settings/ -β β βββ general-settings.php -β β βββ import-export.php -β β βββ integration.php -β β βββ schedules.php -β β βββ status.php -β βββ thinker/ -β β βββ image-testing.php -β β βββ profile.php -β β βββ prompts.php -β β βββ strategies.php -β β βββ thinker.php -β βββ writer/ -β βββ drafts.php -β βββ published.php -β βββ tasks.php -β βββ writer.php -βββ shortcodes/ -β βββ ai-shortcodes.php -β βββ writer-shortcodes.php -βββ uninstall.php \ No newline at end of file diff --git a/igny8-wp-plugin-for-reference-olny/docs/IGNY8_PAGES_TABLE.md b/igny8-wp-plugin-for-reference-olny/docs/IGNY8_PAGES_TABLE.md deleted file mode 100644 index 0fe85ba9..00000000 --- a/igny8-wp-plugin-for-reference-olny/docs/IGNY8_PAGES_TABLE.md +++ /dev/null @@ -1,162 +0,0 @@ -# Igny8 AI SEO Plugin - Complete Pages Table - -## Overview -This table provides a comprehensive list of all pages in the Igny8 AI SEO Plugin, including their URLs, purposes, and functionality. - ---- - -## Main Navigation Pages - -| Page Name | URL | Purpose | Module | Subpages | -|-----------|-----|---------|--------|----------| -| **Dashboard** | `admin.php?page=igny8-home` | Main dashboard with complete AI workflow guide | Core | None | -| **Planner** | `admin.php?page=igny8-planner` | Content planning and keyword research | Planner | 4 subpages | -| **Writer** | `admin.php?page=igny8-writer` | Content creation and writing tools | Writer | 3 subpages | -| **Optimizer** | `admin.php?page=igny8-optimizer` | SEO optimization and performance tools | Optimizer | 2 subpages | -| **Linker** | `admin.php?page=igny8-linker` | Link building and backlink management | Linker | 2 subpages | -| **Personalize** | `admin.php?page=igny8-personalize` | Content personalization and targeting | Personalize | 4 subpages | -| **Thinker** | `admin.php?page=igny8-thinker` | AI thinker and strategy tools | Thinker | 4 subpages | -| **Analytics** | `admin.php?page=igny8-analytics` | Performance analytics and reporting | Analytics | None | -| **Schedules** | `admin.php?page=igny8-schedules` | Smart automation schedules | Schedules | None | -| **Settings** | `admin.php?page=igny8-settings` | Plugin configuration and settings | Core | 3 subpages | -| **Help** | `admin.php?page=igny8-help` | Documentation and support resources | Core | 3 subpages | - ---- - -## Planner Module Pages - -| Page Name | URL | Purpose | Description | -|-----------|-----|---------|-------------| -| **Planner Dashboard** | `admin.php?page=igny8-planner` | Main planner overview | Overview of keywords, clusters, and ideas | -| **Keywords** | `admin.php?page=igny8-planner&sm=keywords` | Keyword management | Manage keywords, track search volumes, organize by intent and difficulty | -| **Clusters** | `admin.php?page=igny8-planner&sm=clusters` | Keyword clustering | Group related keywords into content clusters for better topical authority | -| **Ideas** | `admin.php?page=igny8-planner&sm=ideas` | Content ideas generation | Generate and organize content ideas based on keyword research | -| **Mapping** | `admin.php?page=igny8-planner&sm=mapping` | Content mapping | Map keywords and clusters to existing pages and content | - ---- - -## Writer Module Pages - -| Page Name | URL | Purpose | Description | -|-----------|-----|---------|-------------| -| **Writer Dashboard** | `admin.php?page=igny8-writer` | Main writer overview | Overview of content tasks and workflow | -| **Tasks** | `admin.php?page=igny8-writer&sm=tasks` | Content queue management | Manage content tasks and work queue | -| **Drafts** | `admin.php?page=igny8-writer&sm=drafts` | Draft content management | Manage content drafts and work in progress | -| **Published** | `admin.php?page=igny8-writer&sm=published` | Published content | View and manage published content | - ---- - -## Optimizer Module Pages - -| Page Name | URL | Purpose | Description | -|-----------|-----|---------|-------------| -| **Optimizer Dashboard** | `admin.php?page=igny8-optimizer` | Main optimizer overview | Overview of optimization tools and performance | -| **Audits** | `admin.php?page=igny8-optimizer&sm=audits` | SEO audits | Run comprehensive SEO audits on content and pages | -| **Suggestions** | `admin.php?page=igny8-optimizer&sm=suggestions` | Optimization suggestions | Get AI-powered optimization suggestions for better rankings | - ---- - -## Linker Module Pages - -| Page Name | URL | Purpose | Description | -|-----------|-----|---------|-------------| -| **Linker Dashboard** | `admin.php?page=igny8-linker` | Main linker overview | Overview of link building tools and campaigns | -| **Backlinks** | `admin.php?page=igny8-linker&sm=backlinks` | Backlink management | Track and manage backlink profile and authority | -| **Campaigns** | `admin.php?page=igny8-linker&sm=campaigns` | Link building campaigns | Plan and execute link building campaigns effectively | - ---- - -## Personalize Module Pages - -| Page Name | URL | Purpose | Description | -|-----------|-----|---------|-------------| -| **Personalize Dashboard** | `admin.php?page=igny8-personalize` | Main personalization overview | Overview of personalization tools and settings | -| **Settings** | `admin.php?page=igny8-personalize&sm=settings` | Personalization settings | Configure global settings, display options, and advanced personalization settings | -| **Content Generation** | `admin.php?page=igny8-personalize&sm=content-generation` | AI content generation | Configure AI prompts, field detection, and content generation parameters | -| **Rewrites** | `admin.php?page=igny8-personalize&sm=rewrites` | Content variations | View and manage personalized content variations and rewrites | -| **Front-end** | `admin.php?page=igny8-personalize&sm=front-end` | Frontend implementation | Manage front-end display settings, shortcode usage, and implementation guides | - ---- - -## Thinker Module Pages - -| Page Name | URL | Purpose | Description | -|-----------|-----|---------|-------------| -| **Thinker Dashboard** | `admin.php?page=igny8-thinker&sp=main` | Main AI thinker overview | Overview of AI tools and strategies | -| **Prompts** | `admin.php?page=igny8-thinker&sp=prompts` | AI prompts management | Manage and configure AI prompts for content generation | -| **Profile** | `admin.php?page=igny8-thinker&sp=profile` | AI profile settings | Configure AI personality and writing style | -| **Strategies** | `admin.php?page=igny8-thinker&sp=strategies` | Content strategies | Plan and manage content strategies and approaches | -| **Image Testing** | `admin.php?page=igny8-thinker&sp=image-testing` | AI image testing | Test and configure AI image generation capabilities | - ---- - -## Settings Pages - -| Page Name | URL | Purpose | Description | -|-----------|-----|---------|-------------| -| **General Settings** | `admin.php?page=igny8-settings&sp=general` | Plugin configuration | Configure plugin settings, automation, and table preferences | -| **System Status** | `admin.php?page=igny8-settings&sp=status` | System monitoring | Monitor system health, database status, and module performance | -| **API Integration** | `admin.php?page=igny8-settings&sp=integration` | External integrations | Configure API keys and integrate with external services | -| **Import/Export** | `admin.php?page=igny8-settings&sp=import-export` | Data management | Import and export data, manage backups, and transfer content | - ---- - -## Help Pages - -| Page Name | URL | Purpose | Description | -|-----------|-----|---------|-------------| -| **Help & Support** | `admin.php?page=igny8-help&sp=help` | Main help page | Documentation and support resources for getting started | -| **Documentation** | `admin.php?page=igny8-help&sp=docs` | Complete documentation | Comprehensive documentation and guides | -| **System Testing** | `admin.php?page=igny8-help&sp=system-testing` | System diagnostics | Test system functionality and diagnose issues | -| **Function Testing** | `admin.php?page=igny8-help&sp=function-testing` | Function testing | Test individual functions and components | - ---- - -## Special Pages - -| Page Name | URL | Purpose | Description | -|-----------|-----|---------|-------------| -| **Analytics** | `admin.php?page=igny8-analytics` | Performance analytics | Performance analytics and reporting for data-driven decisions | -| **Schedules** | `admin.php?page=igny8-schedules` | Automation schedules | Content scheduling and automation for consistent publishing | - ---- - -## Page Access Requirements - -| Requirement | Description | -|-------------|-------------| -| **Capability** | All pages require `manage_options` capability | -| **Module Status** | Module pages only accessible if corresponding module is enabled | -| **User Context** | All pages require authenticated WordPress user | - ---- - -## Page Structure Notes - -### URL Parameters -- **`page`**: Main page identifier (e.g., `igny8-planner`) -- **`sm`**: Submodule parameter for module subpages (e.g., `keywords`, `clusters`) -- **`sp`**: Subpage parameter for settings/help pages (e.g., `general`, `docs`) - -### Page Rendering -- All pages use `core/global-layout.php` as the master layout template -- Module pages use `modules/modules-pages/{module}.php` for content -- Settings/Help pages use `core/pages/{category}/{page}.php` for content -- All pages include breadcrumb navigation and submenu systems - -### Dynamic Content -- Pages show different content based on module enablement status -- Subpages are conditionally rendered based on URL parameters -- All pages include workflow guides and progress tracking - ---- - -## Summary - -**Total Pages**: 25+ individual pages across 8 modules -**Main Modules**: Planner, Writer, Optimizer, Linker, Personalize, Thinker, Analytics, Schedules -**Core Pages**: Dashboard, Settings, Help -**Subpages**: 20+ subpages with specialized functionality -**Access Control**: All pages require admin privileges and module enablement - -This comprehensive page structure provides a complete SEO management platform with specialized tools for each aspect of content creation, optimization, and performance tracking. diff --git a/igny8-wp-plugin-for-reference-olny/docs/IGNY8_SNAPSHOT_V5.2.0.md b/igny8-wp-plugin-for-reference-olny/docs/IGNY8_SNAPSHOT_V5.2.0.md deleted file mode 100644 index c0fc9414..00000000 --- a/igny8-wp-plugin-for-reference-olny/docs/IGNY8_SNAPSHOT_V5.2.0.md +++ /dev/null @@ -1,523 +0,0 @@ -# Igny8 AI SEO Plugin - Complete System Snapshot v0.1 - -## Summary Table - -| System Component | Current State | Modules | Functions | Dependencies | Files Involved | -|------------------|---------------|---------|-----------|--------------|----------------| -| **Core System** | Fully Operational | 8 Active Modules | 200+ Functions | WordPress, Database | `igny8.php`, `core/`, `install.php`, `uninstall.php` | -| **AI Integration** | Advanced AI Processing | OpenAI, Runware | 25+ AI Functions | OpenAI API, Runware API | `ai/integration.php`, `ai/openai-api.php`, `ai/runware-api.php` | -| **Database Layer** | 15 Custom Tables | Data Management | 30+ DB Functions | MySQL, WordPress | `core/db/db.php`, `core/db/db-migration.php` | -| **Workflow Automation** | β οΈ CRITICAL ISSUES IDENTIFIED | 7 Workflow Types | 40+ Automation Functions | WordPress CRON, Database | `flows/`, `core/cron/` | -| **Admin Interface** | Complete UI System | 8 Module Interfaces | 35+ UI Functions | WordPress Admin, JavaScript | `core/admin/`, `modules/` | -| **Frontend Integration** | Responsive Design | Personalization, Shortcodes | 15+ Frontend Functions | JavaScript, CSS | `assets/`, `modules/modules-pages/personalize/` | -| **Analytics & Reporting** | Advanced Analytics | KPI Tracking, Reporting | 25+ Analytics Functions | Database, WordPress | `core/admin/global-helpers.php`, `modules/config/` | - ---- - -## β οΈ CRITICAL SYSTEM ISSUES IDENTIFIED - -### Cron vs Manual Function Discrepancies -- **HIGH RISK**: Cron functions have significant architectural differences from manual counterparts -- **Function Dependencies**: Cron handlers include extensive fallback logic suggesting unreliable function loading -- **User Context**: Cron handlers manually set admin user context while manual handlers rely on authenticated users -- **Error Handling**: Cron handlers suppress PHP warnings that manual handlers don't, potentially masking critical issues -- **Database Access**: Inconsistent database connection handling between cron and manual functions - -### Affected Automation Functions -1. **Auto Cluster**: `igny8_auto_cluster_cron_handler()` vs `igny8_ajax_ai_cluster_keywords()` -2. **Auto Ideas**: `igny8_auto_generate_ideas_cron_handler()` vs `igny8_ajax_ai_generate_ideas()` -3. **Auto Queue**: `igny8_auto_queue_cron_handler()` vs `igny8_ajax_queue_ideas_to_writer()` -4. **Auto Content**: `igny8_auto_generate_content_cron_handler()` vs `igny8_ajax_ai_generate_content()` -5. **Auto Image**: `igny8_auto_generate_images_cron_handler()` vs `igny8_ajax_ai_generate_images_drafts()` -6. **Auto Publish**: `igny8_auto_publish_drafts_cron_handler()` vs `igny8_ajax_bulk_publish_drafts()` - -### Impact Assessment -- **Manual Functions**: β Healthy and functioning correctly -- **Cron Functions**: β High risk of failure due to architectural differences -- **Recommendation**: π΄ IMMEDIATE review and alignment required -- **Priority**: CRITICAL - Automation system reliability compromised - ---- - -## 1. SYSTEM ARCHITECTURE OVERVIEW - -### 1.1 Core System Components -- **Plugin Initialization**: Complete WordPress integration with hooks, actions, and filters -- **Database Management**: 15 custom tables with full migration and version control -- **Module System**: 8 active modules with dynamic loading and configuration -- **AI Integration**: Advanced OpenAI and Runware API integration -- **Automation System**: Comprehensive CRON-based workflow automation -- **Admin Interface**: Complete WordPress admin integration with responsive design -- **Frontend Integration**: Personalization and shortcode system -- **Analytics System**: Advanced KPI tracking and reporting - -### 1.2 Current Version Status -- **Version**: 0.1 -- **WordPress Compatibility**: 5.0+ -- **PHP Requirements**: 7.4+ -- **Database**: MySQL 5.7+ -- **Status**: β οΈ Production Ready with Critical Automation Issues -- **Last Updated**: January 15, 2025 -- **Stability**: Stable (Manual Functions) / Unstable (Cron Functions) -- **Performance**: Optimized -- **Critical Issues**: Cron vs Manual function discrepancies identified - ---- - -## 2. MODULE SYSTEM STATUS - -### 2.1 Active Modules - -#### **Planner Module** - Content Planning & Strategy -- **Status**: Fully Operational -- **Features**: Keyword research, AI clustering, content idea generation -- **Functions**: 25+ planning functions -- **Dependencies**: OpenAI API, Database, WordPress -- **Files**: `modules/modules-pages/planner.php`, `ai/modules-ai.php` -- **Workflow**: Keyword Research β Clustering β Ideas β Queue - -#### **Writer Module** - Content Creation & Management -- **Status**: Fully Operational -- **Features**: AI content generation, draft management, publishing workflow -- **Functions**: 30+ writing functions -- **Dependencies**: AI APIs, WordPress, Database -- **Files**: `modules/modules-pages/writer.php`, `ai/modules-ai.php` -- **Workflow**: Task Creation β AI Generation β Draft Review β Publishing - -#### **Optimizer Module** - SEO Analysis & Optimization -- **Status**: Fully Operational -- **Features**: Content audits, optimization suggestions, performance monitoring -- **Functions**: 20+ optimization functions -- **Dependencies**: SEO APIs, Database -- **Files**: `modules/modules-pages/optimizer.php`, `ai/modules-ai.php` -- **Workflow**: Content Analysis β Suggestions β Implementation β Monitoring - -#### **Linker Module** - Backlink Management & Campaigns -- **Status**: Fully Operational -- **Features**: Backlink tracking, campaign management, authority building -- **Functions**: 25+ linking functions -- **Dependencies**: External APIs, Database -- **Files**: `modules/modules-pages/linker.php`, `flows/sync-functions.php` -- **Workflow**: Campaign Planning β Outreach β Tracking β Analysis - -#### **Personalize Module** - AI Content Personalization -- **Status**: Fully Operational -- **Features**: AI personalization, user targeting, frontend integration -- **Functions**: 20+ personalization functions -- **Dependencies**: OpenAI API, Frontend -- **Files**: `modules/modules-pages/personalize/`, `ai/integration.php` -- **Workflow**: Field Detection β Content Rewriting β Frontend Display β Analytics - -#### **Thinker Module** - AI Strategy & Prompt Management -- **Status**: Fully Operational -- **Features**: AI strategy, prompt management, content planning -- **Functions**: 15+ thinking functions -- **Dependencies**: AI APIs, Database -- **Files**: `core/pages/thinker/`, `ai/prompts-library.php` -- **Workflow**: Strategy Development β Prompt Management β Content Planning - -#### **Analytics Module** - Performance Tracking & Reporting -- **Status**: Fully Operational -- **Features**: KPI tracking, performance monitoring, report generation -- **Functions**: 25+ analytics functions -- **Dependencies**: Database, WordPress -- **Files**: `core/admin/`, `modules/config/kpi-config.php` -- **Workflow**: Data Collection β Analysis β Visualization β Reporting - -#### **Schedules Module** - Automation Management -- **Status**: Fully Operational -- **Features**: CRON management, workflow automation, task scheduling -- **Functions**: 15+ automation functions -- **Dependencies**: WordPress CRON, Database -- **Files**: `core/cron/`, `core/pages/settings/schedules.php` -- **Workflow**: Schedule Setup β Task Execution β Monitoring β Optimization - -### 2.2 Module Dependencies -- **Core Dependencies**: WordPress, Database, PHP -- **AI Dependencies**: OpenAI API, Runware API -- **External Dependencies**: cURL, JSON, CSV -- **Internal Dependencies**: Module Manager, CRON System, Admin Interface - ---- - -## 3. DATABASE ARCHITECTURE - -### 3.1 Custom Tables (15 Tables) - -#### **Core Data Tables** -- `igny8_keywords` - Keyword research and analysis data -- `igny8_tasks` - Content creation and writing tasks -- `igny8_data` - General plugin data and configurations -- `igny8_variations` - Content variations and personalization data -- `igny8_rankings` - SEO ranking and performance data -- `igny8_suggestions` - Optimization suggestions and recommendations -- `igny8_campaigns` - Link building and marketing campaigns -- `igny8_content_ideas` - AI-generated content ideas and concepts -- `igny8_clusters` - Keyword clusters and semantic groupings -- `igny8_sites` - Target sites and domain information -- `igny8_backlinks` - Backlink tracking and analysis data -- `igny8_mapping` - Data relationships and mappings -- `igny8_prompts` - AI prompts and templates -- `igny8_logs` - System logs and debugging information -- `igny8_ai_queue` - AI processing queue and task management - -### 3.2 Database Features -- **Migration System**: Complete version control and data migration -- **Data Validation**: Comprehensive data integrity and validation -- **Performance Optimization**: Indexed queries and optimized operations -- **Backup & Recovery**: Automated backup and disaster recovery -- **Data Relationships**: Foreign key constraints and data integrity - -### 3.3 WordPress Integration -- **Post Meta**: Advanced post metadata management -- **Taxonomies**: Custom taxonomies (sectors, clusters) -- **User Management**: Role-based access control -- **Options API**: Plugin settings and configuration -- **Transients**: Caching and performance optimization - ---- - -## 4. AI INTEGRATION STATUS - -### 4.1 OpenAI Integration -- **API Client**: Complete OpenAI API integration -- **Models Supported**: GPT-4, GPT-3.5-turbo, GPT-4-turbo -- **Features**: Content generation, keyword clustering, idea generation -- **Functions**: 15+ OpenAI functions -- **Cost Tracking**: API usage monitoring and cost optimization -- **Error Handling**: Robust error handling and recovery - -### 4.2 Runware Integration -- **API Client**: Complete Runware API integration -- **Image Generation**: AI-powered image creation -- **Features**: Multi-size image generation, responsive images -- **Functions**: 10+ Runware functions -- **Image Processing**: Automated image processing and optimization -- **Integration**: WordPress media library integration - -### 4.3 AI Workflow Integration -- **Content Generation**: Automated AI content creation -- **Image Generation**: Automated AI image creation -- **Personalization**: AI-powered content personalization -- **Optimization**: AI-driven content optimization -- **Analytics**: AI-powered performance analysis - ---- - -## 5. WORKFLOW AUTOMATION STATUS β οΈ CRITICAL ISSUES - -### 5.1 CRON System -- **Master Dispatcher**: Centralized CRON job management -- **Job Handlers**: 10+ specialized CRON handlers -- **Scheduling**: Flexible task scheduling and management -- **Monitoring**: Health monitoring and performance tracking -- **Error Handling**: β οΈ INCONSISTENT error handling between cron and manual functions -- **Status**: π΄ HIGH RISK - Cron functions may fail due to architectural differences - -### 5.2 Automation Workflows -- **Content Planning**: Automated keyword research and clustering -- **Content Creation**: Automated content generation and publishing -- **SEO Optimization**: Automated content optimization and monitoring -- **Link Building**: Automated outreach and backlink tracking -- **Personalization**: Automated content personalization -- **Analytics**: Automated reporting and performance tracking - -### 5.3 Process Automation -- **Task Management**: Automated task creation and assignment -- **Content Processing**: Automated content generation and optimization -- **Publishing**: Automated content publishing and distribution -- **Monitoring**: Automated performance monitoring and alerting -- **Maintenance**: Automated system maintenance and optimization - ---- - -## 6. ADMIN INTERFACE STATUS - -### 6.1 User Interface Components -- **Dashboard**: Comprehensive dashboard with KPI tracking -- **Data Tables**: Advanced data tables with sorting and filtering -- **Forms**: Dynamic forms with validation and AJAX -- **Modals**: Import/export modals with progress tracking -- **Navigation**: Intuitive navigation with breadcrumbs -- **Responsive Design**: Mobile-optimized responsive design - -### 6.2 Module Interfaces -- **Planner Interface**: Keyword research and clustering interface -- **Writer Interface**: Content creation and management interface -- **Optimizer Interface**: SEO analysis and optimization interface -- **Linker Interface**: Backlink management and campaign interface -- **Personalize Interface**: Content personalization interface -- **Thinker Interface**: AI strategy and prompt management interface -- **Analytics Interface**: Performance tracking and reporting interface -- **Schedules Interface**: Automation management interface - -### 6.3 Component System -- **Reusable Components**: Table, form, filter, pagination components -- **Configuration System**: Dynamic configuration management -- **Template System**: Flexible template rendering system -- **Asset Management**: Optimized asset loading and caching - ---- - -## 7. FRONTEND INTEGRATION STATUS - -### 7.1 Personalization System -- **Shortcode Integration**: `[igny8]` shortcode for content personalization -- **Display Modes**: Button, inline, and automatic personalization modes -- **User Interface**: Customizable personalization forms and interfaces -- **Responsive Design**: Mobile-optimized personalization experience -- **Performance**: Fast-loading personalization features - -### 7.2 Frontend Assets -- **JavaScript**: Core functionality and AJAX handling -- **CSS**: Responsive design and styling -- **Templates**: Frontend template system -- **Media**: Image and media handling -- **Performance**: Optimized asset delivery - -### 7.3 User Experience -- **Personalization**: AI-powered content personalization -- **Responsive Design**: Mobile-first responsive design -- **Performance**: Optimized loading and performance -- **Accessibility**: WCAG compliant accessibility features -- **Integration**: Seamless WordPress theme integration - ---- - -## 8. ANALYTICS & REPORTING STATUS - -### 8.1 KPI Tracking -- **Performance Metrics**: Comprehensive performance tracking -- **Dashboard Analytics**: Real-time dashboard analytics -- **Trend Analysis**: Performance trend identification -- **Comparative Analysis**: Period-over-period comparison -- **Predictive Analytics**: AI-powered performance prediction - -### 8.2 Reporting System -- **Automated Reports**: Scheduled performance reports -- **Custom Reports**: User-defined report creation -- **Export Functionality**: Multiple export formats -- **Report Scheduling**: Automated report delivery -- **Report Analytics**: Report usage and effectiveness tracking - -### 8.3 Data Visualization -- **Interactive Charts**: Dynamic performance charts -- **Dashboard Customization**: Personalized dashboard configuration -- **Real-time Updates**: Live performance data updates -- **Visual Analytics**: Advanced data visualization -- **Performance Insights**: AI-powered performance insights - ---- - -## 9. SECURITY & PERFORMANCE STATUS - -### 9.1 Security Features -- **Data Sanitization**: Comprehensive input sanitization -- **Nonce Verification**: WordPress nonce security -- **User Permissions**: Role-based access control -- **API Security**: Secure API communication -- **Data Encryption**: Sensitive data encryption - -### 9.2 Performance Optimization -- **Database Optimization**: Optimized queries and indexing -- **Caching System**: Advanced caching and performance optimization -- **Asset Optimization**: Minified and optimized assets -- **API Optimization**: Efficient API usage and rate limiting -- **Memory Management**: Optimized memory usage and garbage collection - -### 9.3 Error Handling -- **Robust Error Handling**: Comprehensive error handling and recovery -- **Logging System**: Advanced logging and debugging -- **Monitoring**: System health monitoring and alerting -- **Recovery**: Automated error recovery and system restoration -- **Debugging**: Advanced debugging and troubleshooting tools - ---- - -## 10. INTEGRATION STATUS - -### 10.1 WordPress Integration -- **Core Integration**: Complete WordPress core integration -- **Hook System**: WordPress hooks, actions, and filters -- **Post System**: Advanced post metadata management -- **User System**: User role and permission management -- **Theme Integration**: Seamless theme integration - -### 10.2 External Integrations -- **OpenAI API**: Advanced AI content generation -- **Runware API**: AI-powered image generation -- **SEO APIs**: External SEO service integration -- **Analytics APIs**: Performance tracking integration -- **Social APIs**: Social media integration - -### 10.3 Data Integration -- **CSV Import/Export**: Comprehensive data portability -- **API Integration**: RESTful API integration -- **Webhook Support**: Real-time data synchronization -- **Data Synchronization**: Multi-platform data consistency -- **Custom Integrations**: Flexible integration development - ---- - -## 11. CURRENT CAPABILITIES - -### 11.1 Content Management -- **AI Content Generation**: Automated content creation using OpenAI -- **Image Generation**: AI-powered image creation using Runware -- **Content Optimization**: SEO-optimized content generation -- **Content Personalization**: AI-powered content personalization -- **Content Publishing**: Automated content publishing and distribution - -### 11.2 SEO Optimization -- **Keyword Research**: Advanced keyword research and analysis -- **Content Audits**: Comprehensive SEO content audits -- **Optimization Suggestions**: AI-powered optimization recommendations -- **Performance Monitoring**: Real-time SEO performance tracking -- **Ranking Tracking**: Keyword ranking monitoring and analysis - -### 11.3 Link Building -- **Backlink Tracking**: Automated backlink detection and analysis -- **Campaign Management**: Strategic link building campaign management -- **Outreach Automation**: Automated outreach and follow-up systems -- **Authority Building**: Long-term domain authority building -- **Relationship Management**: Influencer and industry relationship management - -### 11.4 Analytics & Reporting -- **Performance Tracking**: Comprehensive performance metrics tracking -- **KPI Monitoring**: Key performance indicator monitoring -- **Trend Analysis**: Performance trend identification and analysis -- **Predictive Analytics**: AI-powered performance prediction -- **Custom Reporting**: User-defined report creation and scheduling - ---- - -## 12. TECHNICAL SPECIFICATIONS - -### 12.1 System Requirements -- **WordPress**: 5.0+ (Core platform) -- **PHP**: 7.4+ (Server-side language) -- **MySQL**: 5.7+ (Database system) -- **JavaScript**: ES6+ (Client-side functionality) -- **cURL**: HTTP client for API communication -- **JSON**: Data format for AI communication - -### 12.2 Performance Specifications -- **Database**: 15 custom tables with optimized queries -- **Memory Usage**: Optimized memory usage and garbage collection -- **API Limits**: Efficient API usage and rate limiting -- **Caching**: Advanced caching and performance optimization -- **Asset Delivery**: Optimized asset loading and delivery - -### 12.3 Security Specifications -- **Data Sanitization**: Comprehensive input sanitization -- **Nonce Verification**: WordPress nonce security -- **User Permissions**: Role-based access control -- **API Security**: Secure API communication -- **Data Encryption**: Sensitive data encryption - ---- - -## 13. FUTURE ROADMAP - -### 13.1 Planned Features -- **Advanced AI Models**: Integration with additional AI models -- **Enhanced Analytics**: Advanced analytics and reporting features -- **Mobile App**: Mobile application for content management -- **API Expansion**: Extended API capabilities -- **Third-party Integrations**: Additional third-party service integrations - -### 13.2 Performance Improvements -- **Database Optimization**: Further database performance optimization -- **Caching Enhancement**: Advanced caching and performance optimization -- **API Optimization**: Further API usage optimization -- **Asset Optimization**: Enhanced asset optimization -- **Memory Optimization**: Advanced memory usage optimization - -### 13.3 Security Enhancements -- **Advanced Security**: Enhanced security features -- **Data Protection**: Advanced data protection and privacy -- **Compliance**: Industry standard compliance and security -- **Audit Logging**: Enhanced audit logging and monitoring -- **Access Control**: Advanced access control and permissions - ---- - -## 14. SUPPORT & MAINTENANCE - -### 14.1 Support System -- **Documentation**: Comprehensive documentation and guides -- **Help System**: Integrated help system and tutorials -- **Community**: Community support and forums -- **Professional Support**: Enterprise-level support and maintenance -- **Training**: User training and onboarding - -### 14.2 Maintenance -- **Regular Updates**: Regular plugin updates and improvements -- **Security Updates**: Security updates and patches -- **Performance Optimization**: Continuous performance optimization -- **Bug Fixes**: Bug fixes and issue resolution -- **Feature Enhancements**: New feature development and enhancement - -### 14.3 Monitoring -- **System Health**: Continuous system health monitoring -- **Performance Tracking**: Performance monitoring and optimization -- **Error Tracking**: Error tracking and resolution -- **Usage Analytics**: Usage analytics and optimization -- **User Feedback**: User feedback collection and implementation - ---- - -## 15. CONCLUSION - -The Igny8 AI SEO Plugin v0.1 represents a comprehensive, AI-powered content management and SEO optimization platform with **COMPLETE REFACTOR IMPLEMENTED**. With 8 active modules, 200+ functions, and advanced AI integration, the system provides: - -- **Complete Content Management**: From planning to publishing β -- **Advanced AI Integration**: OpenAI and Runware API integration β -- **Comprehensive SEO Tools**: Keyword research to performance monitoring β -- **Automated Workflows**: β οΈ END-TO-END PROCESS AUTOMATION AT RISK -- **Advanced Analytics**: Performance tracking and reporting β -- **Scalable Architecture**: Modular, extensible design β -- **Production Ready**: β οΈ MANUAL FUNCTIONS STABLE, CRON FUNCTIONS UNSTABLE - -### Critical Status Summary -- **Manual Functions**: β Fully operational and healthy -- **Cron Functions**: β High risk of failure due to architectural discrepancies -- **Recommendation**: π΄ IMMEDIATE action required to align cron functions with manual counterparts -- **Priority**: CRITICAL - Automation system reliability compromised - -The system requires immediate attention to resolve cron vs manual function discrepancies before automation can be considered reliable for production use. - ---- - -## File Structure Summary - -### Core Files -- `igny8.php` - Main plugin file -- `install.php` - Installation script -- `uninstall.php` - Uninstallation script -- `igny8-wp-load-handler.php` - WordPress load handler - -### Module Files -- `modules/modules-pages/` - Module interfaces -- `modules/components/` - Reusable components -- `modules/config/` - Configuration files - -### AI Integration -- `ai/integration.php` - AI service integration -- `ai/openai-api.php` - OpenAI API client -- `ai/runware-api.php` - Runware API client -- `ai/modules-ai.php` - AI module functions - -### Core System -- `core/admin/` - Admin interface -- `core/db/` - Database management -- `core/cron/` - Automation system -- `core/pages/` - Page templates - -### Workflows -- `flows/` - Workflow automation -- `assets/` - Frontend assets -- `docs/` - Documentation - -This snapshot provides a complete overview of the Igny8 AI SEO Plugin's current state, capabilities, and technical specifications, including critical automation issues that require immediate attention. - diff --git a/igny8-wp-plugin-for-reference-olny/docs/Igny8 WP Plugin to Igny8 App Migration Plan.docx b/igny8-wp-plugin-for-reference-olny/docs/Igny8 WP Plugin to Igny8 App Migration Plan.docx deleted file mode 100644 index 0bf54a181a6e5114ccbb0b72ad7bb1c0884b5ad5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 130736 zcmeFX^K)fi)UO+-W7{@+$F}W`I<{?VCmq{P$F^;E)Uj>*q`#+b)qU&U|KObcRE=NO zs--- π¨ - Image Generation Logs --- - ------ - -- Status: Ready for image generation --- Waiting for image generation process ----- --
No image generation events yet -0tNC71O$Wxgf-4i!xR(*qzDoO1P$aHn6`+Wt+R=(v%ac_ zy@``9qq~hYaXthXbuI|l*Zu$Z_kXYl8WP7X`?Y1 zi0%>yQAD!8>4w2w+mPDQe`6RMq0G0}{2A2Xr>*MH(NxAPu|c$dDVsz6;d73epW-j+ zW-B+VUNK`k?U&fkaE4Aw!-O>V!%g_-U+kW7G(gw-bu21O6=;pqg{Wrl-K`~h_XLj+ z?O0ZLX~4Kq!(tY4#3Be_^J4{HJ3}Sz4_7olab?m|Ozoprf%+l9-aqy3SYwGY{(j|F zs52h@H~5NoyK$JnS7d#Df`cgj4|x*C;Wl4>U6KD%AKaHb^&L&DotPN^+5aoc|AV#o z|N8Wb_}(vB!V6voy$8*9D*f%nE|6z3npwqOf&Qi~C5yJYY`OID&bzz}rh9B4F+Mk+ zFyrZzDekhBsC$K*tcC#Wgjsyn>(_qma0f{W=_q1xU$ozW&p3E6eIFx}q8ST{)WQHx z<3hzfg{MyTpzYI&KJ1skS&-69|1l!3FUWRKTY60O{hph~vao0=sqF<_#wXSlO3*fi z;|pslP@D3dAvUIOqfU1~^(3K%^_L~hJw2{9GZRH-oM8nl?p4R^iDyA~$zVhXJxm@< z1k Tx`DPuz!=+na`^2u4L2xE66PpD2072+kS%w)joe3Ux%p&HxnUGJuDwn1Tsdv zW-;~|u}R767Raw#W+LXJ6wm{K10VjPE@M(j=_YzD!{SPGsq>0Jia71OZ$rRF*EQF- z)0}R3BWF2H%-KB`1^#5uaB>m?@zE!j%iEHzqy-VJ=me5z3t_!STdBp@+l Q3tqDXGt{#6|R}r>?}rg~W+V87W=4(U-17^(}xdB&VEI zpItO{#!jZ6@ooZ*sYd-Bh63lH2OxZhx_jS3dWaVXpF(3Zj_jjS3|7VEX_6+DN`;%e zmOHaVSpU(Ko~yF!)AHIU?tcE}bzyu5kG$%!{_XIEYR`($?D?&|Vyd35`$BC%nYR&5 z-0%b6B3>@vxfgN%QtsDg<^)>n#vMpi$@SU=Ws((p $0A)HE*`c(cvK;QXmu61{%c3NN+wHy8_>865h$qIWYY4MQ zag}1uGAW2#^hPh5WROUUmaXq`l+R6J#Z0IjSsUg3&TkE&FUhl7o+&@>8vaaOP``?E zDs0B8FKk^VC_Xey=*ZiTrFr9KXa7PQyCOc|+L^?<#taAIBKvX04$537doxtQy{X!X z*SdetBoT_} 7_-WFlDVV^! z`}l3jktg!%bfd&vn)NPVEfO zB=qn&c$t1nSM}8Sy5#ONFpGWoeaKJKo7R?ctre?Tp==_XKAS>Y`};Zl!lcE=!JL~w zmd~5N@!hb%N5oo8_T_R4{|gqc+-Gn{J)H0B?x6nceo5r?MaeW4zo*B;L%%+QaZb)B zIqKN>-k3xK;&a`px$S37>?g0HX60K`KqEi=rZRpG _P>*vQCqGp`n#}JtoKpy!M zG;#h;O}0uZ%AQrU!>*S_Nf&$8T@pK8G}r)B-u4+%-J5Fr`ObHEG&MhG7P8kMTJHmQ zKiKajxFFT5p+9HF9CNt34nIuex230rXRva%SYH)n%f1TE^~2Ds+7aa#mDt#J6blRH zHQrK(lWlq)Ap2KA;TSg4Iyn#q7@v6!Cx!=O6o_NDWF{d=?D6A{H5xd#Y-TQKHX8f+ zZ8X{n;I*(wp_2T_ ktln<%FS1jF4XN7K1Tn+Inv1HwoUv>I`Ml zr}(&(A(sI%ici?WUDDznB&ZxKprQVvdM@>@DCrQ8RiJ{u)mPAa6h_qQ8 7m$imfg^>wm_ywqaOk31np ;FyH@3AuW~}Uss$N4;AYQUT(sqT^JEwAlWW`&bFqy_r_4L`t%pOt$ ztV@&hKT|^tEf-DU7ML3*G1G0;5U-3d+ZH$DRYVe+_x`9RF>p1a+sMd2gRjP`Y$Q+G zAgP@^Ht(q=zs@C3jKNM{V4^MjX)@9Hen;94HjeQFx47ofODvM^(NoN3cKeV%jF_;h zta*_xvzUr4TyhS6#f_mIS` smQj|GE=fd9G#|A!Q+ zPYYYD+Ut^ORy$oGU(xup%33CcDjy%IDr7~ZfO?z3J-aVvxBRnyKx6Y7LI+<09Frl% zPi|dZVLQ9k9qKklprYe*>(kFI4v&_{u&Ty9=WVzgRAtU92#T|Vbo2^>@8NRLTeYc9 zlu2Yvl8gOE1@7oYOx$7`w9;$9_N5&mL?S|>O(YDpc8O#(&sye4c9}u$PVSDJ+O?te zX(aLioA3u%=dcoMibH1c5e1pW#JuK2Fwkx>&X&Pmb!q02Ed^!{VXku!qOd(^4gm}i zY{Lfh^M*n?|7|>F_pV7E>93ov!c7I?$Q2Y^>^25k>~>azRjk1m+lV9f;y0O2szWAE z21I`qs`R( x?8aK|3 OVFZI07JoAcz7XHjQ_GXG3HFNnY#9?gaxm zH2oo}Q~e<>3+3D-15^Gy#Q1&G8~+Tpa!R0;@*%s*^#WOg_~OobUvsCJn?HW4cI!kB z$^D!A(G*#$fO<%=;Q_r27b6{kYkXen|A9wHWiG43n`~XsG(LBC`u;#4}YkyAwgXhDiI|@ z1)&C^x^*GRom4d>to#0aW~X=EGzV4gFa%TXNKi*m*6Cg&9wZ(_NZC0JU;CqKsHV(S z!u<;Sgd`0w-7yG}i2WZeNzxPgWFPLI@|tpd> `!`^%SVRJuTW9Pr3Lorb-vcQ*afWmntf-p|ON*;$nUgzb!O*?F zyzfQzt6K!vDW3VMX|3}b+Jc#V3|(Au@1N(NcOm=FW~)ZyX?nwUMkL)3D(k>u?%ywF zm%0Ab#KljjSpg$=AA}%xr?SB%$Nr)P9CacuNoQgH=ZXe6N9-G+`B$XOY<&%KD+_Rf zXmLeWTNE7=Gdn&j>-~v72&qIsl729&TJeojm>enQbRaaPsAo@5&8tVJN5_rwpH-k8 zMs-@)B2igX9j`U0WxhiGVx~T67m8G&_i4}|TA!ENbc~wdSvHZqTdvh2Ii9m?f@PfU zhvlo2d(ixE^}KiE X-Z8p@D9!BPY~fq^zP?ANwWX!QZ4DT{D!Yn#SR-ngmTc z=gZRCjF(yAUu_ydN?ZmZG8eM|SKtmmdR-uw8dt063hFbBT}(G*)s*?>gB=QfVG{S! zQ4;s^Sj |XLSBhT_ z&vsaB0l**HgZi! -{UPe97P ztkGSP*eIp$CrE%3b}ZxW{G7OIP(eZ5a0(;#OiF;D^2q9Q rs?LuK )E$K=S%{U_^@Gtg<0zr0Q}OeXYR^Z2-Sc6_4RJTkhv@Ws9ZdQ z9_01^o-87s@T2HrYO#MfX3aN1R6Oj+jeCo51-*pkliAQ2IOonDjk>4j{iZm~HZQjj z=QDU5VAa$Mrc;lBW XE DQ9D7ReB3)fwTPXk*pw+sks!b0X7qCceL&7kWJ9|Zlr;^3ny)&Jw7P>3k; zBM>$S)=fKPn1vwxNZBBH0okaZoo``2#5@o?=szx#a|EMOa!|t^!g_uE6A)Rtit67u zr@Izuz1SYF!ktK0iw*lF-%a7{<<(_-xK^ZIp$-duTYI@c4-2=aq=3W56fN^` KFbS`jtwL0;oXIzVDbF z{aDU|91OjJ+N(O!S|S4GR&hnDe@0ybJEARY3(Z!sf5x06$3B{!n8x&RiFq-(6%fjI zOdRe?tJqG|>zUnl 5%`M^23+pxu&Ty`e82d7jehf ~8;64TaWM y;x>d1j3Y?y=Meiab7 z9SOw *8X8 Q+H8 z0{;&;(CE+*@Q0JDw`x*DC-G>zlIv#opQi+9V&Bht{xlMDQhv=3EMH0(Hl{+r$49f5 z?P0NKG(rUm1#({ts1^`O`lwxDm^1yyrEMup16D)x>fekIdg;@_?nH03Id6%}fEU{u zkb7Mg(ED|%0;qe)FKilL5J&HYqKC2IYI00CH?G(sBf~bqON@gcIVrx9OyXIgkJ|{1 z){3mt_UeY$r+XW|^f8KFyqY$gcRO?kM8{Vj@MXVR1l11Jpa5gtCj3zc&mO@Z!LMRQ zu;vy#l-X5hqyLARrqx5wH^At%PxyJ`EZE++y>IwCW(Jh0d?3f?O?O=aUS0-%+^ c*!^6GrM5F+L?cR zRm{{llJ4S2FU9LIUJN?DM)5jJH12M~Gd_o<*jeT8XwDs#6OF>XZq%G4c5uTYb&Y(^ z^ps?P@Jn0dEdMuy+0P6ky&e&s=chPIy^!sF>~L(kV)9Y_>%g#CYFMRKvT}JHbzL~) z#?4KQZ^@z0%af1Ur}>n>`Z`)-66}SwYE^scT@-5hvb#@J#Gl^7_1|yQc0RjA>k6^? z68D>Tq&7wSy&Y-aErj#0$pw;P?h_xAj*f#3K2AqZ^+vYEH9cs?Jy;jccP6QUyRk!w zex3mo*Ey1@vvhq35^DTnP#hvE^J>~GYA%;E_^~J^g-7$i%S0j@#G$d^O_U`%2o3eY z619TLnc{jK9bY$sX*xY0`3a=4ZrjCp%q!k!3;;C|-4W9srU&^hviMGGHPwhWvIf(F z2K2Svn}i*$BQ4uORo|111}7$5i@k^z#KLd-F#U3i ;l3^p59&!p4~ny&_JqfdPi^D%2QigzDI-x zFI}l6xhDM$a#@q!dVJmJYQpd-0l)HUsp=4nQz($kK 13M}zVo zgUVCp-8aoub|sg)y-#*Y21#lc+<%TL0<3~e2v5STisf~Ug2&nP&+gguDsgxa|GMW= zRZ(Zt`nyEp%6xPc^moLI+q*EO5&DLmL81{{1bl`K#aY3B<>gw5C=3mdZ!L}e@~du} zt2-gjB0%yER}ANugM8@7D*=@e&^|ps{Loag%g@G31$t89f49zs^zVPqJH5 5!g`^7sap_(&iEn!R!orVCGqi6#O49f;%|I)(~7u7qP$j#A*tQb zZilLA#YT_!KO+xkrAwp{@ki?tea?ma4;om)f^III;O8=P^4y*tdXE#lj2v`XRCVD% zTy>4E?`; PYQI43uhJ)D(4=dB6?W~ePQD<2q+?_^q=q}}*cx$v1Czr#?>IHZIttIj2 D&CW>aN@y9gxo@xV zh`v4U;w>C=@Hfe-eD-XmmZtt~7qO3AgpqMGj X z67C~@u>0r~q(mVaoC7Pyf-I}V_6ql4Y%*+M>!*8bt=pXOoz0bhW}?{->=i$JpqOGG zvo^%QLsx9RKWQh>FFY7{<}tjzpB0!ElxaKq2NmMLWn5tJd%1);r{A28_ScId<@cF| zaZrEc_6T%|Hr~FB5OaPlZd9ABXJo%rKjQ7upY(W#91#O@)J~RWsPG4yj*h+IY|bW= z5)g)y)J#6{=z5d9O!2!4NTOpD#dl`QV-EvoIVnY0kP_BSxzaG5zB>a@rymFSMS`@@ zkvJw$fhnxVVx`RmJA`xaT1qc%jYai;$9!9GH0$<8wJFncQO-+H5XQQvXOJF8?uQ#l z(2na4Ak+V1F@P{HQS9hlQmNISdbvGjujc%UN9m^$!M(}h9@(ucF+5yu&Q4hu1}PxH zaS$Fs{uBbF53-Co;_I;m$-2lTMG1xm+*QRL;|cTzrQ%&{ufb*C EbGQw$ULXm;<2DCNUMo|!Q(wD^u8E|Tc_tq|YxmJL%z-Dvr+AUoR8*f5UO{4b z4P{bqbg&(UIW`x)I_kTCdY*4xWHBrsBDp#CK?ugo??=q}px>5W%uOG$nkh*u4rKJF zVnmu|%xLoLNaX>`-9^c#d0cd+Jba7o$6li`ONvMCVStO;JJ4ofx@=zb4m z2%4#MjzV8yb4J6obn`Wj4Mo7g{$rxWLXOci*BqSf#^<&9?!LK``obMzd%d+1J|xoD z!t?};OJol+Z|EY|E(V=jM9nYV%>59S93$xced0>YVov#RDp#wyl%=L;00 HZZ1kUF3}V*-OuHN}6D$ryozR za?FRBwq~tv?5O3UrdIpb8H7oi;Fd>3LR2mZU)SYmKXm+u@pY4l34iq+22$)#p@ev# z`koexoLbYP0Qsj#0E~49fl&_iP8G$l6wT%W=kD}4nSfYk6`5*ONj~0GlDS}P_#d%> zYf}7l&Wz!4yzksHAxiu-;=q~2z0_UwoT?C-fj@S#vOXTZExV3aZy#@GqebIoeUWXF z&k-#5E-LD31)srNRtyoPc4N^C8f(dw2*I4Q?dg0z9d37IN~JPgsOC1>wXna}AZe#z zGQ>MR |ylOfREPYcd2UZ-*Kk*OFeQCxegco+Py$imT<35ORj7R*q1-_B_G @I}|IQAjvRskkrp*Boe{cC2mJ3lJICt +cyz*kwz+QMNn=gtFX#+wU?5S3beDIY@j?B) 3B~$0-#Ccs`gZJe<{YhMiDcc+ItueX7_Fv-P^2t5AT|udvo|c|&T2u^j z*J{>MS2o$a2^@<7Np*v}zil_0e_&zYyYUqFA(COxB!Ktn9Js8B ij2dU}PkjV9PwR?yXzB5Q?C=u7(+8cGG1B zu+2pBWVo;1pR}jm;14XyMD~vz+F+Y3N4Mk5R!bN2QzRCilZ{q<`vJ3ak`sAV4nXh0 zLPWUL$_F^16L3TPnW;YDi0Rtx^6F=)Y@ZX&-{ts6iyuwa$A7zA!~GvuI-}n`y@8r3 z1bfjtI+cq~!|XU4UJ_dokZ=EXj{fl75hNIM!CK$GuqudAKI0D6li;|1%eT{WMG9ZF zEb+=paY&Rf`Y0IxKCa!)TxpYI=Sv&9_Z@XgGm$Chm253O-(IGw$lynr$=E9_4Cw9! zf(rxm{^sGbg !V4>L)HdF*x&3Pg&QjNdlsKP) zLVHtW@fFqJhvQ5l`~TgAyx0eKx~PSPlEY~MkS8m75 zFWzDDuV5*N$3=Ao~WRrCZ?xV m&T`i%P|D; zeod(Sb5nz)B{K2TbGVbLO+KwmUavpwpriZqI1!U_YjBoQkesQf;5oY|sQ@&tmqs7I zwL+`N*7%?eHD_Wug*r5qzRS#d`=VFowA<;fCl7NB7$zxFm$^!t@%p)RK&&^p4AaR2 zK$t*}%%J6aD6(o}bH`W7MY|Q-*zre1RB6i$w9@}jZ(@knF62KQ*d!K2^bnCs4NYD~ zkBndB#8DlbD<$u0Cprwsw smMys)ZG%O5 ja+0d{xGFUfSU@kamtLdo{NMIt z;!wRqVy=#LKaa3d7pLFN?%=DJ8Au`;32)d=#ffSSB3cGKPz`KHQF?%6%AVRaJ{mhK zf0G(7`@2KUV2yF(4VEQ>Pb#o<>>!We1CCJS7fED9swAJtT0as@*bjNEhO*q?jki{) zHh`Sl*+&!SoUX{y3IEGU)ErSk(zMjv xD+l+vNlq3j0%e`;Uh=6&uFyd@>J(A0I(D7dT^M z*|`;|Rs-c7i>6Y(mZh*U^Z)J0_N 6OYC+v+@ob?Jx(5MfDp_MeVfk6-dTRYIZQ0+DkfInQ-BvOx0z|N1}v) zPD!$vD@O6f(vwLMZnxmuxigOuM^9(Q=)6TuStQ5cw7y+lVxDqEwu%M3WJanGD9!1} zu>+cw;(pLZYp0(uita)bx2<@s=!rh&gpIKRQB~H} zb!hat2La6`fH=~MPq0)cC<4&dk*L?KqcH4R<5C#%?eN-akC-QSjsZFU4E~Mc-cs|C zQ~#5D9k~VCaSnQE(yC}Kx|_N|KWP*UXvLfg(o`mo9?g6u#(6n^!!U_U1#Km5-ggd7 z9VT{19#rzxh8_#0Y^_?sQftY2cIgE1aJRsnDR;}7?@$`|ZKLgJQ0PlOBG6>9{7nLx) z?p+TT =GiZ@k`MiKwqyJdkSav_L7p-5lfiWMsT@Bq|vv;lS0MMB3?{ z;Ogeq59(HR4RW8qW@JNJ{ej&zY*GeT|B08N)b+0eHf*?d0H^eBmbi>mW->EH?k>2q zyvA=dd|f=I-a*`e#E3xOAs%uWENRbR{AD)3xAFcQ(np@x&B=+ f!(M757J<#)(~WU6AkdX}-6Y?RAR zF!2D_n~Bo}$wj(*d->U-(xfIAFWYVsx#Wkml!VoBSTeh3cUUys5y}Af_V&~GZ}DG_ zrmQsh5nc#mb^ax )ZQ}w*+@@TLEHrMOW^OJOWTJ1) }S`ayH#UZ=IYE+B*9epR@U$ec=~g!jhmf;5+hY$1pk zOLIX`^hi)3AF+%C(Lnc|b!~-|!TSAr{~ww>gOp3qA)NmFnrvVX6zFlYRv3O*@60N3 zVAC8;74w!R0 BgswTRi%rtU;<7Se47=T>m$;{hHZbBdV{ zDfX(o!Dr3X{YYXB=jEYipbJgGmZu`MJ;b;P!QoPIB+@V$3e*2ODyi!~UC{Fbx xS5#V s ^uyK$F5E#`N(*CHh&_Hk(0W7A5+yuj>&R3mF~Z%>Y-I>W@xa97j?3#x`hLnS<+f~Wf5MR*gTB1spBo0mVOL*% z*m4bMPuxhI%AAqvja4&AH+8yf8o{f$p${&z&(BQR7(`oIgs#?&$Zt{c+g*m-_5y5x z{HH!rb3QN++xXPv(-{!r^BStFSGk*WsLZ}1-9?14lnkiaQP|3;_ wMlM1)PzzOe9P1CffY;Q zg+b`@0J*rb3}X>_t2Q!8JG1z~BsngqeC=ULSF(QJomobjT2H!POsbh~md|A#ydMDH z-S)TACn5Xrm@zns=ZxQ_R^c~Ir=?VaT!}f6{|LC~o>oxJZ>~Bc=w8enXnJ {X z7vT;*5ymO+_`K#o-{LSRwceO2Cgd8T|LZ%-xH7t*Z=*IHzpg$2J5|L(%4 5tP6~~9d>Now0Aa `(J-Jus-?;+iC#(xdH-K+R zP-w0Kd!b;waU5%Iwak(BX7x*^Xn~}28S2HNx>+|9Xe}HBQG9C6ss?x c8Dj6r2JnQR fzF1l012VPcR+z (qdgwgRtE zFfx1wAtRs6R)CZw^`gnU_{vr0su}4GX|+K%^`#kmB!=4X62)|-4qA`FFi)ygemcHj zbqEL725k4<9gw_HdUHo_d>Xn{&SAiYa#OEYrh!gx3<68RnCE=GK)F8|eH^Yywy?25 zU?~M3XGfXP)lZ)O(Pm5>LnWSVB}j}58g|5OG9cgDJe1+l09Z_+Sb !wt3zTk@_ zU2NE%eQpg?_DA}20iHa?499f^|989MOSMXTJ+(D(wFfVwg3F%wC_wneC!y!JRuerP zlsk& I|68~{K)>Ch0QeM7zmTI zN&FRweDH+Z0$xCSDx|LB?@fL9mE}1iQI?!CVWiY!DM?v2$O|>km5hZ*I}h|UY3?-D z`#~+g=S;30*fx>iHMbU6uB8f?nJ%2nY=yOyO1t*U%1*D|baPnyE+yVhQmRouA1UG~ z)Gm@ PaZo{}IVZc=F1g*r;j6rH@?3`SmI2dUC+YkrMVZ`yka-e$^kU^CDDChF9u z9ekgF-*HRSW5{kd#S$YB>)6* O_Rf6uKGicL zQY6(|vY5!V>x8}suG%6-X^oG&7DqW$y|dQw={sAB%P8}uul)zlCKpz&NAd3IDk+Ff zI^0vp7Ky=TV5aa!`B#}lSbmPJr*FF=AF|o+vJd&g2e Jy`8|d8F=G*6)kG9>eWc-D`lslHn zg>K5^YZ0z4bcecEiOxPldwK2QR!;`$JmxC;C<#q_M-b?NMK)HDht}3x+Xn?zLZNCh zeMCG%8kX37lzBW2ZNjtI09IUD+<=6wpl~+>iG>InM%?MLJfHuJF7cgrCZ&Tqk%3{( zDfljnP1ElLCdY*ZUpdee4{bC+rM6=O0YsOM_yh5kZ;noF+|sW(Fn}qBs$8rI7W*v} zfaxA`ng`r;v<_b=48BMg=V_T;UE!Q7waYhQv5FpERl)NY&UiWR$l;D0n=}-6Gf&p( zthBNk;`-|Jg~{#3kk52rV|RKQJK{@Ij?cJhxXoNMl^l!L#twY%(T}S-t(N*JOW;a% zj0lJPEQnXLor OW* z+@U^xbM=2eXKX4PhLc*+#)&kT`u>9Hw)iS06H7Zeg=kW00e-@EN@l}ASN0Rz1rOU5 z5WD}k&OlHv*$gNDJ=K|`IK7%I;evA#9RZGV9W*^ GY!cF&6Kfv AbObVezjnU^>6@8v1J7BlD8pp4asVfRFg`v z{+>-S(^YckF8v4$?Fe_qaa_VTiT2D56366TI9sB6E+^cQ-%Yc%V#78l>F-y--C&BY z<8XazhiLk1=5eH}jtQoZH4;vlWQ)}{*wF{jsmN`elY?JUo@jE=L}#s?UQ21SzP+)s zy4wa$?Pka2nB#lSKLuchIfQ~cINK0y6~z!{xAD2#nLHN+9cq4Jsy(Wi>9-Bbl)AzP zDP57b3ucHNe~Vk=sUjLQ3lkenGf|4Sk+R${^IL##$t^sMGYQ@ 4G$O-$QfYlrjs6{(9kttFS6+b*5$r4)K}z+|!kqt{g70$IXer=Wc% zpPj7E7JHM)Elm$Cy{u3w^tT(m50#)3)eJm#Y0+W_!OWiSSR+HbQ3ezkzzuW~r%dX` zfVX|7nI%fPn2f6pay1x4e3NJMidw5t<{M9 FdYtcY=4%4Mc>hzkx-Zxa4EU1UFIS78r97GD}=j z!OsevB7d 0&Y`4V^S9MEG-&AcaaV+qD2 oy_QhJLrt7m%MrfGRR F$_Oxi=ntL>~LwFX^y}tF7dIJeXAnYV%^g@`Zm|ZcX(wC75b=9@^iD${ZIX% z{wbifmkn~&_G-25;lb`#uieVp) f8WOH$~FjFA%EwO8}@7j2LB}F8LVC+&MLw)l;X4|+0?9!YqVw(Hd;ih 2^s2*W_IK)d-BMf>dmU~ P%{Q-zz;Z6 =Lx|poK*I=ju?mS_X#;12K;bc zi&T)%&lfUgqFp?8qSL57i>=BL@3(aG1T_&iNKhyv&oFi}DPX41c~9EU3`{rzkg1o@ z`bb{+h-p@pTAyS)^XFt@d1QQ>~8q-NKEm3RO y34$K+ma%DQ{&t~p2|Ku5~7pALO1!hR6G#Vq{F1*;9-FfEbcUSfU z{&hRYcqp<=e){EKC6AG6er>YD#stF|Sk_bO0C9kS4@dwg7&wHfwzN4{Kq;y@Xr$JZ z;Tk7JuiV|RoSeqA#e|_OL04KKuTOmnkFU4}`gdbSq8|Pld_^LeKVc?1nxW_Z7rz$> zUVk$yqt=NgXDx$zo^tQXO+o2yR_x-JW>RQ&to@l9*uoERQdNP-EFctjgMpg<## NRA9d5UE3*ZY3%qOZZe)z{kbayLF4^SZbzL5&+`z|!Ir3`G)c zyF_@Tgz&3Cw6ip1W^3G^Nks+1J8k2c_0=p_)BYI9hWJF?5AswyE^;T5*~d$fS|SP> z(5)Ln-ZmQMb=W(yhH?|?ur B39K-gc!d(Fi6GH z1-{sLk7`b4>d0peFdkF}KaNv(nS2{bZmPbui6n|1ZBJ#0j-srQb3Jz%t_XVwr|-S` ze-R52H+3-m!C2vqu@j+Vx6Kj^1~C~H>I#EQN9@PV e#Xi?%TamP(1~5HLAND?l#qWoJb*sk_Bowe2AW&uLvG@V}I-!d_)T*qe(05y7i;a z>&N(VUrCC#mv+G-6_*Ny2VWt4LAk0H}cy=?~Za;Uu!_Fc=El^oO zhFX*L!pcUCKRz<_C#Mfb$P9Gh-UC_U2+2M1Eu4{%6e+8uR-$gD;~CBTAgG30bs8_Y zdoF!V1Gl_^XwxfhqbZdp+b|q+Ps?$vtnd?I8lAU)UuD-=OzVKWs)aB7mY1-SI+oQy z&oigdvxzn6$_Rf`ezVyaU9J=H_kdr-iU=(H?#bD!93A3t!O`sdXh^;!`lt0y_&x{S zbXT(3+atH^=2aNrY9)u`PBKSHT^Ey9z~`W>@Z= Qsb*6dA(SvfqGi`#;KWn*!(`^+(L`PDtj>!bl$5e zb!_r~WF@xm76ebowbA6(Ss@p|aE>#gzMU)fWmZ^bj=0c+!GL&CV$}fO*hfAPBsf@T zhwsYc(zw#=q35;5pOT{}cU2nOIR&!kX7J7C+~v>-t~R{PkZ9xH-uA8CM5R=@*8l9| z4y{Y&lOJUk%CO5;AE*69FW4Zzk&=FM@)nV`?7>D(4G%xhRSdM^Lib3j0KI!DNKlaY zIvqXCCGHV5wM*Y^px*|^?8qpPKOqxj%swh@M7_tRrUubiy7t4rPdCq=B~HL^B+TLN zTL`s0Rf8{7tXJ<|KlZDv^pfQ>DdMNHX;DjYDNuN_Y9z~8J#(}?6PD!nM;b>dL2_h0 z+sluE^eYCC3NMgX>A9^J(Diuey}`Q(EqT&QP#5C*#TT2qJ{B8wPmBis^HVq+2`tQ> z%1s+prfvRvoO8l>ui;2_QntJ~HthodnmC4M$sE(iJJ|~ZVm|!#h(v?tR?0ZfiMN3( zbq OM;<*0rn-7-87w5`If z?X0s-hfojgCva;lu&Bq~^%CU41V4#TN)W{%0qPliOe(p+8h^qB*9zXd?B`fCSs!kb zSwQtXIQStFe3a8basMuuofdBPaNJ*lIfC8=F|E?LblXsK0vRP$#c%KR;+8T;(M7Oj zyKNvfU7!;Y%QxDIv4HUR9ub^ze^+y#=Qo)5kLKb6Giebe;?SAZ$+! zVd%oz4450&lVMLWF|;-kCae ;MxXX_b{3k9oD{AQKw7 z{p2D5o9;&A)4|fHko5ck`A?pElQAVub;UI3k4lFamB6XxF)ibSC{oXWkIXPKJG#v6 zvWne@Z_&9HIb8JHYjJmZI$6I0fBW@IP=TMV$Bzi4S*_?9&z1-)^Wfx}JzpQbwmN}H z75y)^-Z?mv=KUUyZQHhO+qSW>lZ~y7lRL)7_QtktZ){`Z_kKR#s`sy7byZhQRnK%! zPfuUxI;XoYXPHlKy%IAlL@7x}JLVzXXGg>*=G(#v-g?xPb2MO - I;#nOD++5vkt@lztX57$uEu^$yhJSAO%6fLQy`rZv#2=nQWKh2c~A zop<+k1;&Kx)>YB;^bQ1EGzB_rZ@2%4-q7fETj*r9)M;^phrptN5CdpY?N^P5-(B{> zqao4YftQz7ig`dATQ3h&U0D_LJ4%tU4Fn7xqNQ{!0Bvn}fv&2h;J(WRWC3pBts6#$ zzAc;i$LJ@WS5ppopp
@)X8qbn+TXUGcRyYPHi^P&8s5TOysnyKJ!oA5z`J+7gJ7e}09Vh{+*5`i zrX>-QZu-KPI5v(kNj`2Md{MKo=B=rK*>ve1t}xAox;<0BcAwPV%J;^yK{z&D8`t zxlLsOK7%jfsa(*rx#4TH8K|KO1bc}$%}foz&TSjo*5eItnIJV^f2_gWUx@$vWBk=U z`YT9!JmEB5-Q6lDKR@6tXhs+>pYP!<{jMFz1|V|`$POIlnm9!T#HoMh?98G(pUoCQ z>?V@0TvkhLtD45%96Ubr{kloQPC$DzU(z{YeG|)1%=bfd9bi*a5tNnxd6z+}P`Pn` z8TF-sa`gCpc`{K=?k2Uv _umG{>L zKC?&g9bKKrIz?}@CPFKAyob-*D>@oH@eg!i7uF?6-82O8b@uX`*!4Q`lee~al!JPH z(|k*pNM;KbBr)Q7Vm}O5nSnb+l+Ul#?@S5eH7kWP&QCTz0c019q(_*~EGS-nBc#w% zz^AG`Yl8DL&sX077OuSdWWe74=&-~jiY8DP&sab6A{`g81v02$ajZH*-(ir!za9 7LjI zTms%!ltcN&h*hX^Bu)L$y+`d%VU##MhL^HdQAMs==_uhx7`$T_c}Dk;0CBqIS9nb- z2J1;2?XM2lB(46$)j8f%MYE_r*`rQp!Imw+RcX85&tC3Iocw(B;wu;B3hC^UqZ{l7 zArt;otS(lgB9*K$zLn(bEU@?Br7?rhTpPj0{E%PI#Z@G7kkaP@Nd(fV7EjLXA>$x= zSB?;$`qHT-J9=@~i$)MjdAS%u@{yjD-oh7+&F!_rm!ZpY`Q4ujyTu)>^!_|8tzv&9 zaXmJP5-f{4_m-c8Z3yT1Hm@8bzWa*gPG%|RUnjdHZbW-e{=DzFTk9XKU-MTG6#mGT z_*p)IIH2k4QQLlS*Sv?zl#fSEKhdZOO7DogF93uPaK#MG4Ua;0Pxh8je@A(Ga(kr5 zX|jG27VCadS|OX3k=D-i>^AQ^3&ap@=Nr((NZid*#b;>i=J_+u{=4zW==qCmyVoS_ zbebeoRNYZ7yF52IWod+3)&FOGl0aJ$-HHjmPz4cs8nE(I=&qYEJ}d0DXg`CBsN}4} zuI)Wt#WnqvX(Bhp4T@3q@9@2@ sp$TlgUtr#UPA-WT0p`J5)0c@G$`ON+b9j8*6S#yIs4A8p zL-ProeWSRp-$w0Edx3%M0o!= yd=a}c3f zl}^;rt~)3 w%R7n5*!+{ zvoQkL`S7emt{${uDEigoRHQ@ig;mciAv# SS}OPONn`A6v;4}eE})DGT%s1iS bSkA`0?G%f$wje|4Ae#I&eG^vrdKPM zh6b$6zD~&Cv6_>ljcu_szsBT4j=1vE@X2DYFnT6z-07Ojf8^BYf`u{XL7rcmdwz?> zrbQc+Z7af;+2~Z7$WNE`K>XceE1}(vDR!qJlE%=TI72z2nA}9y)PUIe`zN094;1>{ z4=S+27!=K%wk>`H`umt6*&J1N)!r0S$vk#~nK{$F(lk0xl>kT5>VLfBK+f8jq260) z2v<~0-VDFe?jnAYv)7Gmh-;%_uWal}!(CieLDka|0FP#Zxa5JHVJncd=%KgM?Clz1 zf7S)rTt>d%E`-3GGfVTZpIRU=yu;`}DgBu9g(T&D3GGIkjSo)ZG#)~72O?6B9Q%hN zjEkh1m9wG~(XlO_o`(~@j~{mn#bXtf0rFVkkJ>oW#4IBOw~&^4=?3Py3Whc|A>znf zUU-jSlznRbm4;3zI>An(Hz>=tR2-aX+*K>j (J&8lIeKU4I2C%JovK zi5N({mN>ls^-ZaZs4EvLyLcq=fOpVf3j>hL9cObV+ &8`-L4OLCFJ_nT0t=8uC{W|PyfgioGa7MxB<`urs&d5@SHCrzFM zRrP!tpoy{3c#1=1uK*2fUF9Wl_9jpAe5lb(jC&z d(+5!Jt3Ibbar_ )e&@m!VmF3;Ny-5#$hzliQv-_E1|-pQcX zR)RR%7Q%zqM?=mbG)(}v0xP?cdXolF(0$7|_q-eRK#4FS7 r8Beo$^TIBv9Mf7_ ?H8bk+rwi0P&)=Ly_qic2m%!q)y+W@*wCmlQ_7OmRn#!mwy~`?@$% z-{Tcm35#b`c%Rl^GGbX^RxeTy?b`b7qi7@dsCbsM(=Z+mGHodGKyWe$C|FQ)-(KQx z(L+&bPXS8qKDLQ0S?vTl;kwKd%J?m)n5u@-E{LrOO ?<8(($xdKvC2c@8=Mqc z?5psrhY4e8^Yh{&?Cfyn)-`%jQhUx+%TxOCz7!!?u?)u&lXlc=NP5`nsOlNcB=R12 z@p#$m+dTd}s?hAB`r%EgS5j1i*D%O}ZZ;VpH19;GY2A>}DcZv;tBN=tE{RO2dN0xw z?AQCtPyMMma>;jy!_~T+yDp~7?13{=K$N~!lD;ls@PWsNUnwyiEhhziADUMlEPT++ z@qy8&EkRwFUkvD}o&}3R-Xz99 |D&y!z1aZ8U{G@^>z2~yxW(oe(S}!cxAe}R z6PeOCQYbO1IJJ=YH*=V0pwu@HG4cQjJ`rhl7)5^WB_FsfkZa&~a22E=LI)Y1^O?Us z*h%f`bBn(aA^ZHV1vb1j?s>Wye4X2KAMw)_g>Y^fe1)&UDd5zBx&V^W{O81EUj<69 zh(x!Rq6^1ucO#Wa2i3`ogWH6YvXq?QQ)e`6X(NI7d-B8ZgI_&2vN?W|*A~D!c^vEo zafm<_I2Cjk+q3@ODXF>iYa*;`ihAZz@(UL3r?b}dmGL0gg4gN9&m+ps@VAiD(S?ok z7rqB4gHuL& qVM8PSWM2}XX>{IYsj3L zT%x}x2nGv{W<*^KWF5sMuSg+Z;A^&Uv6v>i=;7%Y%5I{pb}}ea`L)$< +~dRe@<8?;b}uAFnJo?E?%NqP`Shd^e}Cg%t#N|U0--^ad_yhIe^i@L1O1CM91 z>$2!`)NlS`di9PX-ggN|#-`a39{^~EY~oIgWOwa%5a8s+!`!^72o!yybTLw*ZjYWZ z`mX+uSR$Iu0!VVi5n?%fRQ+hQ!7GG_I>R=@vj3oujo)93ajCaIFzz O3n7S7R8qpeySC=%B z{$*uV7OJSLD<=NdPaHLjBGydfUU|MJ>sflR3idsS6>!PcUlpV+!2> z%USnFOwi JEI{6C!f9q>*kmZ+!e%-KhDEXb2{2MD$q)?3yfjLqpi;v~Z>g()qpjEu$LBvDk zz_V%YUb4|lyz1IAL^ZSb#j?p9T1#&HCLL yav4K^o~>nD)j<>Gw8J8KB8+ 5~`j5T;-Nw^FH#A(az(4k?WdOB8pQ6f`uE|im zm^@LYd58!*;`bIn@z^hM(U_4JRw9bw_rcrR60k 7Kr46JLFXatXE# z*#Ymqy0Xy_d`(w|Z7(;)|NV1E*_^??W!tK`vu=%s4RWr|>R=wG4D>S=NLhl?Zt1ls zc)ABKW_)ntUxg90>sSB%93W_K+0y~g6Ech^O&WP+yB=1S&Ya1?FK~;;1A)y2ww>~w zj+i3~+GE2S5=h*-!F5Xs`}Uhg{6#Yk)q_5_+sU6@3Bb=QGSoUG$Q%l4*dsMJ{3XWu zvE(Z`Fb}uBJD}!9uuCo3cefo4!7VG%S{uExB33wb)?zC153w1==6C!L(i|Vr+r|EM z$TyUe!9Mfl@12&+N%58Vb~__3W16UP2<5z6J-&;Vz>i+*PPk?K`f_nWnb7rT=F`n{ zMgh8+o&Lke<&Agkw_fe%_^g*v|MYe_?Ynl7Q{g{Myg=;Pbp+3RbFJr zCRP1fp*7dRHA4TfNAA7=Jy*`kcFGG7hNl=blZt!{Q2B0jQfPTZ03L$}GQ(3#E33ML zcq5}sQ&$HB&N=X9RBj|j`c*=ILy2#Li~I9h;DWc y iO5+*Pn**%KjlqEcB+S_rEGW{$<3Z0e$ zo%-;N3A8@xJPc5wMs!eUAuvCPe=?DOy950Wtwa19Df!4^8>_QJ{6X9;Bw-84fx0H) zdbA%nAt)c1d-34yjxe7Yg)A>Y6QR@qeaHb!t=k*|xh;+Eb}e7$r`Ab0*a?uJ)EU9Y z_?664qB$AC!`Kp5a)N4_Hk_0&f_pX~wRaJ_{Y_-e>##-zw%UI!sd0+pV55Zc>98?P zzEL#F9%oc2Wgzcj* $gUIc|zY_17Jx|+*;I)L~zCv85hepW+WPF$Ghu>n!O z6~d+T3eh_qDaW)(cPnz?gAa_{zZAc7n UI+y-r27d&pY2+TAAW*=-s^_Pc9E Wa1NK-Mv1Z9v3Qn gFfz$ z4tiKGJz!cG CTO9kc~fG(KTHXc#gh&6muc8RW`O3x82~gFO_vg-!A+03O%fEtybUqDi| +;k%f0QA`AW}bxV7o!Ilu7g_O!)ud@X>&*0F3x{JoF2IFXm z?;sy(z_&2=QG@tM%Z+0CNB_wsMf*F88{7+W3)5M<&k)qh@^_~it?aE 2TFF%4eOp_B2sRFB= z_>f>xJajM=vKi5|#XYkZ&NaXWfk*3$iKne%09|Q>s2A%*>#JmA*2!YxLQ<3g+5F$S z9tFghQMk|U#4b)3DZj@_+BZCleiE3_4^u?R3Z01RY*%%D3h{wX0Nn;9f?w8;HGH%i zF7xOo^;ETgmZ$Y&Q91cxEn|hSzalr(ZJhjz%D~vPr%A 0-`y{~X-EM$PctN}{swx9n|vQtCM-Lrav`kA8tSb<0RIO(&+lo~;&Li_zEp&N!& zHQjL6@DL;vO21HJ$B?OhG|3vjJYL%Qj0DyVJ|YInfaibuh$lSaYA(OZqIexQ(zvF* z=5$w2+`mvO6J;oy>eUSs>q5=%8EkXe(L5dr+&{pAL7TQ~Zd0L{V&?np(hE~tSAlq2 zv%GPgb;b-~dPM{)+aT|1&DQ2|aaaGAjTz}wv`qFXt^1#V-dAc=%88qzR{%WJq>G78 zaz2eW5?lj}ZWljK`W2ebdZm-_G*wYlol+dk9gHuI<=0Kcg{?Pl$rwF|p3ciY|Kf yuR%MI`hYV7^AAHdmjwW z0gpNxPeu{H#LCy8@Aj-oe*jV=Jl&!nYV1i-H9wN4F!cfEQ*SDRS1f&yq{A}M0B8}I zyBR?tw^a}|Z38q2P}(dZIu2MY)V gyG#NN!X9aK9g3kzUcO5C&% z>-l6cEF`rFBy9pNi7c!lC4S?$Q6wp=?Ki>H%T{oQLZahDF5I+H-a%;obys9FnSJVU zi}LJKRFzP5Yr}&^*;^58^DkdGA~|p$z@HESzgK^2^y2C3lg%2~^qVf%qHYhNz=<%s zop!Q^-)~$j7+4M3YL%3Hh^KuJBLxj=H>$9~w;mMzrA|Do+GdmG0&asYp{T*Tm;Wig zi49!sS{3}PHpvVV<51-{Eu%@z*jsQHV H7A){=Tw3l4LQhEqe;G4BQdSc9Tx$esiy9=ytyUM{v>i*~CP} zLeDIdY?;C`y>-6NBsdp3V=)lRcgOturf4a}o=SEc;6Z8#n9|qaW{8EG&zu@#?uq*K z%O1uWMMr80L+8g;PO>fa%{;s{pKt81#o7GC6ze%HGSo-PU62Qo{bJo;16fZer~O*( z5wx4HL&{imfa1HYl3me5Jf!itwl(p9C@EFG-(@o=? p4htEES7PE_M2BT+;*+}lhG07 zdQVkUm!3Cb-YLWrV;Q5e7{qorNzrvKDAT1JO?_}@NZN09kbS8h*+;xZFkk|mS`iPn zU$MknwlZHdr`9*!m^HjTFOVdlij<()FO&N5{NtX6>H?IhB<`W@($~#}MIMiKH=BnE z^MT`77d@p;KW?_rp%E1u<0fIr$d5+8qLkWn38s_D%|M%)ttU3hBUzOjf5+|n)htZn zY-gCv0pgyJ(fxhq)oMD(+=9di-6gB*hR!HuQmSsxHMXk55vb2&Yz8x<`6&x3Z?a%C zo&=*fYtXIpN@7m@E9VB$@_f(;rw^-_US_bL{NogaGa+Xj#AG!**O*^<3p>YPtf>;{ z#$UCP_>97JSfQ*lv$WEEp@GZL^rB%hnz{HpW~Kr| 0NI@0c6d^K>MXg*SGmfA4q9d){g>6pXc+Sir zj(TTPmSF$N MAP@8O0V7_x z7M=atiVtv>#fv*Ih3pM!r4(h0184?9alYmKQW8~%J*5eCz|9qFV&lV|#^BKBd<#!Z zocF2!r39HfUrqNo51Z=(=#M^KRL=5RJNf##ErV?pxb3UiKhzL0^rK5fZ5lsA3)yYW zX5o^fFwG( |cC}a!B`v(%J~|3Sb=^877rgqLnA!90 z$yt*ndfO@qV2yJ(b^YjE{jfa8zVZGyw9$@ouxFhCzKb!cP(#}uaf&VD2o05GGFK1d zuB-{TRZm-Lq=Eek!}&X6>(Vctj_~FK?ChrbbO7UXPWb&3c!s_K_4iq1E4|>(lq8Nm z7ehY_bf2WB3<$O^Iw+HCVl=!#akhcQ!{EP~$aOmNoV-Ovhy?c0?qmCI@@rv)$j|*t zVZuey7sDKk_LsD=Y!bN~6M7HK8IXIZ!y1X={6mKa`!tI*bBGa~KKt +LLhu#an;R^NLuNosG;X^xJ)NI_Fj6M1-+W@6wskb9%&tdvMB}<|lXQ zoE2qPc=MY2eqN=5TtKeCSFZ-t)71q(LVa!;C(!Mo*G0$J)2Z|z-@*cut<&dPu<~co zQaX ^nl%@Fu@R((Ua5BCrh2-E62pxd{VfB{W=(L5t}&FZ zS`L}cLD|rnSE^rb$iuS2sM8|W68W%)ztab*o^!N?Vq#mbe(pUMKQ7J&3boG6{cmb+ z7S0d+D$nix{(#NR?+JXF;S;o*#sNk|>HWMyKK#2x3IGIw^o-mw;_+T%GR4PG*EL1y z*ghsVJ6uDY=Bw^2u`s%xck>k y>Em64L{aWOZl`S5ERVqn ( T&DwZ6{7%VAwdrQ@`+Fda4A;828Zcv|+Wom|qqIe_TcshOx1t$~UeOU8xk$?Z zKdz&t3IbrqRlnB#up}HE%5wh2_;ve ;ElkWAwoXM*4A7ngpNNJ$oV5S4U` zmzP75&7`8Vtduk(O+mF(Tf=D0!nWU6Ft}y;!biVhM#(?D1+&U%U+)z$j18gY6 3x5!gUf<^Xdo^m!Fx|L z!$LD>3mKvRGi$SV=DU1`NRQfOI$r(zF4Tb@Rjo;j%v+0FN1yx6(ta ? z(#zMoCxQKe-lIeQ$f{}i&*5K_R>wRmgsw4A*F0af>)A%jNC7^V>s*Xz`77M4d@ksX z1yz+U4A-JP?0alSIBe2<9j}%cKYJAmUA>8H29~ze#~wcV5aQgA+Ijm(21VTMyE_^( zqfPfn5ry@Km08<5ZG3h(A&L3rdtNxbm1X&SmCMDb9g3zUjsV%+ZGRhyem}gOcRv?W zbl9SNkHXVQufXx$1}_I4p}$@YH}l4$hiO~nP90>}2<}I8lXUBZLK|Wm zPQL2D%{}{WpdhB3Pkf%#8tsAZe=;o0&voc-f$94?;QSTk2v=D>R;~Lz0eNb5)#1aI zV6Cc-k;3jSdeR;;8D#dX%lgNMPqfV4fqJA08fm30ucP3+W4`f;RApAY{KO|o)?bOk zzSDCd{R4h7FU@XLFb>^EwS}_#P^Zw)(ZHOLJGco`6L4-U6*Rmui1w=wR-AwvykBx2 z12+O) V(!q>Co#0OH-YKn$;()ll(FAN zN3LT2+=$~FAiLVozi}tvJ>h#9BR5rN{g~vS(QWRZO9N$Q>5HQ_*IRO+`FiGke@~f6 zSE)|RSUSe1T}dbdJKkiJ0J2+Aw3~VA_f}|VPJZ edRAQ~u-d)STk@ zmYS;;y44pj=(oxJ@ZR1@f_l0At#^(^JNLz;|XRgFRrgStX;@^1JSXHu!JO%ivdn{z$ z(7ZdK5QF+maFnNRm(N8kL15K|-CYF>QBp0nV@3z;2B+(QGxrc;-i#A}C6PZew%x1C zaPm~1spC@em1cL5Ko-ELXwi9!6$@s4&!+8;LnW!bz0aPbHdFr-#oj@MAv&wFia=!v zAj0}@SozwO_;bG)B)61jgm2a+2O(OGQ8ZT?rEdx{o^T(R_;+9a8?8_GK tOjW%d>ju z=A;xe;Kzux^UE&w-oS9}bIG9)k2jYO!XY@I_G(X$cY)$nT=$%E&MIyY`O*vFO&|%( z;OS-Zh%RzTCe9UofIfrGvn9AP37`FOPhqsAx#o!Q#Ex|PAmD_@UF>Ji;hOTou#oxBQ}S&`0?0Pf zan !L3Vbq?3!Hkg`?8! o>x_+W`s#M5h%!0xLjEuC ~t9JX%DfysVs~x?j$x>lt(~ zJ|wEoi!ze rNdRt1EhAP1yIjnw HPsTry6@V=3+n$r`i_Q61`K zOp@dz`p7)sa(X`Rgi*zm{|K7+_rGy1zZo4#Gf l;I2OA1Z`B?p&U`EmRg z>LIKEjfKs`Vs~)NLb6qNIRa;0>Hk&Gx42uL59${J&Uz6}CQWo#0geY@!GG7f(J(BH zNNkNxfgefwwi1yi9;jMlQ#fmN;h%*%U)uN8Utb9Ux{Zv=62AHLlRWXq?12XrZFVb) zCWs@cG-N>@|7pUA)tfycF?fx1FhdV`MGt*-pur%)B+7!=Av7cspuJ7{`fYcLm+IdY zA)?8UgA`P-2n$NEEHWmEL1;IS*(R_^znog*oX#n<>mi`!onHWZl_g~lF$HcOY53q& zPzu!&5E(cutR`lCLwbWeBRoS5Df Tp3WPGX-P3TfOi zT`O&4N}@%SyyQT54beeee yWCtqiWq~)#18)ZWOngOTz|^6uU(&+g*L9I0tk*E9=u;y`2n-Qt z7M806 z$TF|SIK6P{6CyGY6b1?lj^?Su5TLV5AsqN=$^(gjt--sVI}n9-SsH+W1*g%!#2WJh zmJT}Bl%KM1DYyb${Y9t4ux1UmPCmv&E=c6R#3)G;1tx}3{}&^(T+3UtW&^e?#Rwly z7$mk?9E<@?hhlHY7nE8j6#QcI4x)i4Yi7asNr~tYe`U{8D7r_B-_&7J17{uw1bq`o z2iODYfHhGnmjCPV4MOOC*|(o~K0GvIz>hE%u^~HPp}LPFs|GDH0CZPj_0d7mJQLeD z(b1kAG7*oYhAc`N4i^3iW>g12BUkD$^s6AsvrK{NR1>0^H1wGT5C3F<&{X)bfTMKG zX$t}a0pm^$1_L3hh%&|R9O#QNCGVV-jGL`7{=>{&OIw0)#(9GllnPb>EpflQ0KK{- z`1Pvgl@NM mSu-NFdq3uHypb68(Dp`!n|d6XLxiJ1_ufZ}n*3bJRT~IZ59A z${=tkSY$*1FCmKc3s57in tC%q@M$~T5$Ps1b)$Z _E`9h{wYz30ji zf$O&;BqW(yeUzG1zqhWLy*y~7a~Xt6Jtt~`33O(sNPkcWD5PD(&r$D7YFn2^P8F5{ zlM;+ye{_}5I6Xly>?8jlWy^TnWjal tS5fGWU}e$`siG z0XWh{AV|cbGVX9dJ>gVGh$=sbmQ+Gb`EgGRd5L+U4X>-nQ_*h# fp* z=ly5_8H^i_?EX5RAmU5fi9lLA1I7sJsu*#rCW~P9HrXuZPu1~%Mtw#>@KfpkY62I}e8uH 4#LKFK0pqsJz{>nS}d%sfA?R`ut}#9E=Eo1*M%Lr@(LMg&pz> zde4&z;_$Hyo&X(4I!N_252dSgA+RMt72v21UE-^63pVk ;)$=P`bq13uY&lz7I`?#ANj zxEIfHD9jxLgE@SU=3n?nbaqI`p~b!9TVevD-jyw{Pc(_Vq8{m)KO M_egCgxQbf)&M2n?u`u9Thf?X0J7R#W3AEK&||*TBNeVU&v0iaavV*D z@}O@%Gm{0G>0{G%m^-@R*ERDi|8p(kB=rZ=v*6oBMpv>ghN5=r07tD6;NL)sQTtSd zg69czI%LFj&iMy2)0a|*#*`~ZuQjQ+;+~>vvZsF3Lu5xDle;6v$1g(U(S&(|joaau z?T?fFZKm$76`CY{1Y6OQMrEp=$nn0#gYDDq*iON*_H+r^rnU~a*1)pW+u`jc<2_B~ z!Ev_`DsuuSkVysqJbhNmBoGn9GS5&mE*aL{$R?i+1YnFfz!72!^dE>nOKi};Nu9@I z8;4f3dO#i !eD`SJuJ7K+F@b z4|GfOIhy4=X5by5j4H(>7(<-t@A|ST?Q(?$;#4xR$QefFwit-|s`T-DBv--j>d&t@ z5Hf4Y+NoQKT3f7RU2NPU+xRA8sSP)|oGYQscbqW@ioqfYrb^iWjfD+V{6wV{YnH*P zl(b5>c*V|fR}%XP-pegV 3}sq)?jj &*%JrkFRl$>06CRnCH7RsX5JN)3;<<*P@})oTq8_&_C_S~ z_szE+2fDu`GCuA=wNSs($=^71VLBS!&8Y(gSxroMfFVEh1FoVqkgePGNmWI7JXu5@ z_iQU;e&tTjr6YcPN0|Y}|I^uUl>9-dZmpZi%2{NI+u67DGO}d-0CY}Lj$PcfQ0)QW z8%w;mLea_1wgiTI-jdTSxT!Vz3cm{u{zuUjqj$iz kPx_AcM+rp`wDJ}8($>T~}}wvX0o^pa}QR85IN0uOsk=!RCp+5eXz zw6*HUQU)E#G1$?-XO2h5_yfFhol)+opZY_vLqhxCb3}b5p-cM761Q%Xy=JHhP#L{4 zB$&3g$(tK}2Xb_>uxEv1a#@99EV>Gf@pG3mS=2#f3Z_v0JWT8}Yb`%l|qm z7j|r6HEMr^7L`MS4#eKuz=HPtQACNnUI2QAk%rT~Gj{~O|EWo<0WoDXw`Bnxd}(?- z5;lhvYvS`+z-?uM>z(pRdmNOD0=z{3p$5)o2Ld`WA?jT}*;nx(W9v?3z#={W?S^E! z3x{NHPJx}mn^Y}R&$UB^*mKZNwun+2vGIG7`k R5mJT=5GUnBO% srKX15UmK+UFYjnm&jZ`7p8#JXbM=) ziF9=PK?q+;+K{~7$oRJ)GjeW9Sv3Z^%}^dd5!$exEU0*;@Q;H+7h_W@>ZK@9IiJ)Y zrY12mK-Hqgz +q{_zk z%*^kaLf0?u(bQi;^fr{34V${+3hM47YUi_Akd>Pzm7P2f2O&18i%5l&4;_VfR2rjK z1Yf@K#2geT3>j>*Kd1=O=6ES5y-HE6 Jn%lDoUP6MyY!!ePR)$Te>1{ z)jB;=elIi%A07BBM_MRiC>Gfd7vyfOa7hNE#vlzof;hgL0BT$}6-&yqCmeb68aSsR zT5H_KgrkpL)K3}9nkxG?plozkn=cJPf5J|@#FJh5dzxv8M==hvlhui6R1OXS9JQpH z{rp?TZ96xBbV28&V;b_ipk}hS)Eq@m><1tZbpn>`cJn5S@-ueZYE+_ns=0JQf80bv zU(=+{>*Tgq_EC!>A^Z=UKN#Z$MYp87u J$_itWk}*r(w4{aZ}hF?v_YlSr zq&C0~=>!Q#<1k{7-M2d-VwXkH5iRzLum$_@^lz~|F`u=@7UggpUtt?$!I7os1%=?~ z@b3!@z0*`LXRLFHA*I<9_E4PUN_XUsJ|TTI!v%>4y4A@|{#ARtS4#L34G#3J*xq5p zrRdZW0-*vYEwsL1no?RYKS4a*r^WoqR$DG`{KxAY1b9O@F`_N;RA!*%4q8BW7)Ems z!zr0#H-wve{2O53KpTYvPoA$LP};gRxp+yh*yStO;aEEE7GW8&V*GAScTgc|Z8uO| z#gN-olaaWC#>LlA7>)kmV#QOQuQl(wlYowvXIY$wGD4%xu_&b=Q6$=LFWI+PV<93@ z@QC<2-K+KSP^1$1QqSOAuzJ!=mx9nDNIn|dLh-W6=pWFC5TB|tYtE@R`G}fd)M-E$ zSD6~*39am(!vBaL&(GoqmBTJGp+Kh|@q+AY`h*{{O0i?+m+py|j`x);-C_MpBkG#- z^c2DoJ2l|VqaQcM>8oPgT@eHeM!nuewfcuj?4PMx7gL9 @PyUya4UR0 z-0T%#&*C?Yb&q05I)!O#7ztgtqeBV` >}CeIJml+0$V1 zE&o*sXil@#EG=$ekj~O48<-T+b%T^Oz{+3-(li2o`6wD?-D~Wrj-IfBe(QswqOR;} zD>obB=kM2C+q*ip_5ALhv*L0ZmD`H?F+5>t26K5&3g}!1yQ;|Z%!?qq7x-uz 4h?>r^0 zW+BDuZVEDG9tpvcz=>)?1fAK|Z9?>D{A-+0>~WJscDy^eAD=^}_G2BvHxw8YN;bbR z(P(NlGsGSqp dUI|fVh_@p}rh67O%d1 zeo!(F4#L2Sa>eQge>4&d56UeXD#>0h`5t~DoX>9uJdInydm?)cWqXY@k|de0hGd<< z?ouPF!552)2K#-F8P145%U9sry3_^nM1pB7ZRnRqlw&UH-+0z1&LrGfi(E$3;v7hY zN$7|2$guafl)-Aqucj~0c*N8^iqW95@ee|+)+T{?TJ`r`q+^AB_yh$=XaY; zb+CB#O{e->5C`p>xDc8oKqVIeKeQFsSPXU%)|7o|5#YpOb4wb82Tui6cU+99(YlwE zcC=yFqbqG>Dm7PyMAq=PblCN2G}(wLnO+$eE2GhWf)X-w!-<3a3FCZ4V*AgyiSayI zB`Ifh^1{}XLrWON^s>NtrsY@asJPSqn;*ZyoAw-AFxiVwkXFr-S|kKx{e!PBT-I&m zFh%nZMN)eb6{WelDqV<`sSLGSwzNR$?)qAs9PW7WcIPA(K2rwgvpOYMr{`V%E56%q z@E*1|)<5BkkE-sF$Y+sPl-8_;KHVn(H64iD@BKV^dmr{Ac6BWQO%g_$naRl^ot0Bx z|HJ93sQ=;g{D!Yg8rvV6NL{Z$d51aUw2ZVu6XXC&XX^39hckuKJ)&e%x*)TARRNvZ z!IwQXMcrXi<= p3F9ta(!!*U0GWM(o8fN^m zeYVr8#d)1m%L~rUMD= ;45VS2& zm{HFe5$YMCiJA91o^KMi4qik7qirg*ug4=8T~^QbiWj#|FZ q&enC)pofJ{$8B=3y) z8x?M##uO DC?D0l^5MlE6A#8%4$x@W@KqJi%Tq+X<$Ud4bOqB z!B*CWD~{+2qZbdTbiG}0oJJZ (|_ujvXQW64l%v!ZH?Ji zYhwTKR-dO$E#)^r;WYi*{8=)D3K>$8T=BhO_(bb*F8 jmnAOq& z@D-Ac7vj?Oi_?vZ+0`2sLV~Jx=;EtGkr5~&@%C=Te-{XndAx>lP?Y)eT f>v kzyR?G(|=Xl45LEN%vI?^2NwI%A6zR2=W*5KbY&kG}YpM6!^y(unrb3^5{GK zg-~S@$|1~*(M)>*BZrbf%qTd?=-^`%+A$32@aGT6uBHA(?`d=99v-tKie7SAXo(+V zerE0HpfW6(;v$67FS$tvjP4~1M*ARS#;9@Xr)A-P9wUvw2L8T@^nS@D^cTDLm?qOg z5ko^UX&N}=6u<`-t%~%C5SSo84~m?GJIr(HBLsJZ_WCdl-ipTf=N^ y=&;3B >caO=r%L8fdPYLjo9JEkp> zH^Xb0Jx_n6epuI(R=_Z 5gU~BOFR?twD=^JTC-=>F{lz#;59E!CG3?_ zKmAd_+%`I=PKJ2Gx^#|&T05zuV^j?w`@^gnS$kE08bAg>solVW{PLY0)RY7a-L*30 zmtbm<`AP!?Hru2lMTLZEv8V|FC+<}~%P2>Kyvw;qj4&?GnQ(lSE2tR4)AcrCDT)B8 z|1l?-Rfc-J^(UuiTOrG%E(MgGA|jWbT0K^~Vvg#@^xJRR@6!@LRCt;K9ttLG^D&1i zq0u={;ZQS0Bn`b5K*)!g_GzMKm2RzAi!peL_7Ds`25okyselm%M;k`p;cY{sy;$G2 z-4Fg+&SQJpL@00+b;;KS#aCGUzl=NF;%B*2seaMRIdg;^H6vl5v5D;Lln|a7#J)A6 zJ`Gafix$t;TtM?VYB+vegZq9=1HN%M{(~q{Bwms7B_h0EJ7>_z#?E)39DjZ;38j5+ z%c3r|{r4Pt6Rv*0)@{dFG`G4sD|!u@Ml~{0y&7F;$+Suq4)J|7N?h@W!S5-V$63M8 zq`(#-Ei7@NfP=lXQ~#6cl}UB97*vw%4(Lo3gOWapz{GVHpj+VwA}8Jy?r~MK7@M39 z6fc-{mo)rB8Xs?zVLhi5h6q(48np(_NJud_18@KyVPmu~i#*REA9OnRE?dh(xcdv+ zDm+{h#V}0e3!FO@yxOhrOj8Z775u9?r9lSkv&s~wAijV OvthZt0%&MXNC5n4H6(=( zM-)9|4915gotOY51+^w;q8+N<0q3+KU+i3`XVf54+$d#w^?;~mWGbyJE|3SX2f()k zIR58bIwazDuDb_6bjI$==e|XIg0h*vyh! ca4Vtc;ye|%4@!r`X+@Oe~7R>`fIc7OLerlC1g2q|EVOT*aUPpJUskf@Bbm> z*99b)$|9$!Zi(x|#oFwt|C03+<0Z3FVzeuR{4BC2MnvX=qF){_%_qwp8%*b$!YR7G zr22Bb9$mY@QKXyRzbfEC2AhX_qBml`td2zG!@N;f^$4eAV*bVAI*b(P4s=6_1iU>@ zwJjvyR=7DhXot#UiTXT8tNZ?PyzhkHVUWa*hS*}FrqYMDP4}N}kx|l}SrV#Ay*0ni zXYGQ*(}d-iG~u<~-rcnX`#cTuY)fO`p#BNPr}KU${6K>LOn)i_(GgDK>$*7FP SSyPkXrxsb1al(`im^3#YD15S{f%MdP%fTBc8h|;U#jRY zszA@762{whTi= %&WX%9< zh-65Ds@c_5`gmUO@~K@`YA0OU$7cqsg(8Sf!3o*`=PFT~IY@@@``vd+zQYR{_PjG( zwFay5F6@>91dtezm^<;mqx|{kbSvkEDXuILR42sd;uy(#7VLd-(?s6vxA~2BI*w># ztv*@&>`)5L2*!2#dBt0hb!z8D(X3mW{HRDcsE}4sj3DS3eLf1xeD5(Z`SD3k`UM47 zW`K!w8W_DaM1NggBCSNBj7Iy@L~f>cn+@6?QtssS`dgK%x1;z0<@LVWBQXhXk4&x! z({rCYtqmesY9i*!I;de&85mTyxTxToO=+}z==9w-^+i}84lJy2LEBIJP{Xm2b;lo; z9QanWFawjBb5+a}7OMBUN&>iLcsLd=cFJ0%9wEG=8?7b%GpQ|Pf{wh@AW_CNcx{hS z!Sp^oQAQ$LI6EkgwV%hYIh&cq72jx`mQF55kZ8Dtxl!BCcD+zfcjxvfyj1czE8GEH zUZ1ORl9?Qr_Nk5GmT4AI)FJ2E+8{$y#l(bWtqZO^L}sVG +?v~e&VmK*x`W}Lo(}0HZ%>(HKl^tpixao(KR50@f~rFT_viu@O|;jl#r+35 zUN9imQAQqw1%#BTiTuHn0||=w@wqwtP>VlpP{MIMIX8k6xg`Afh2&06-}&_jRDtph zEW(HxAA=FxwCqrMrQNiml4w~VtgmX T}dfKkiRf{^4nvnVB0ualZJhbn|20Xz+TvFbDNr@@tWE zKc=d|PXE4r?@b6-9z{S#)h#r1iI&^;|56oN_mH`~v|xqyO^4MVANDn}zvU2+q!QH; zX;>vmmsHqaY;w6FZPz~eW4G;Vl{T8}w@V?c^a4cYPDB 3 zMG~26Q;@YyN;Bk8Qw<1+#<{UH(^VFWEezlm^@ySKdohe>h`VBp=j&8OZ5x@B+ TZ8~Dw>$2RnkmzpT5yO29V7- EA$N z9ERKf2%0Frxgo&jvYji1Wf|Kv8_dv@yvb%!|C^kw&xZYrN*ZIRmP-~&AXdy8W~lk5 zj#plrPe0S|FH=;Vd7JuNW0~G-NiaZ$BgQhR6e-QWmd93P#0o{2kw-8Q9XJh^t2w${ zWO$``>Va?K`N+#Kd^-u~O13^!J+a_is^N1E*Oa3}ec3crG>CqT{DHB@LYDWv`{2HS z 1SZJ~Wu3Rk8o0dL<@h;3QY9#OyfLC;zAB3xQdj?S9T;DA5= s%^H$E zsKa ^-qqXDe)ZPH1$xJH8+K!h@lnrc@-Vr5+f@)GIsKB7V zyadZ_4z9W?)BSZ+6wnK|!YI7y5%cvjDljkl3rpg_Gji1iWo4=j-m+VgnbdSMqKu>f zMKk0w>bJPX_uy-J{3(M)i1MhjQuxPNd=9Xj-Xd1nX@Ab@;8DIf9Fe$DmgXe&5|m&o zU&3cfxh>aBnUs2PY|qh723y&Op#V?e{k`&GD#RhFEE z?lNfZ&L3N5f18I`0KLR_Z|(8qFM2q1C3eB4fwCzr!v>&`18Qvz-Va9|Q7J4?Jvj-v zT@TC0g<4&|uy@E_* 2eMvIe5SX_^YQw8C4KXOv;f&Sfa+ x2+gv5ii}vg5OJQ%Yzg|8-7%`C->d z_zAtUCD!0>1C~r$deMz@AcMyG=a36Or?M7MxkRVmVyjz^h3Nx$bcXMiSuTD!IR{BI zpIDsxL=RXTCOflYo{{!f_`BaQmkJG9+OcUBmem07ny4wL;ILY~3u1E`O2{yDTZglv zUt9Ifw|!_gL8J=&4F|=OW|Enq#PYp4E>hh_MAK_4@MmrC2izFh%hs$C;fHwyoP91d z7ex*cRq(Zz*fastcc`pDX0=7MMD>8p^{~dHL@56*PJv4v=%+FyFiR2YoD#V`@o9FI zX`-e$F+1+v7tx!$%fro%kMXybr&hju;37zx_`$ORVk-0+Jhouv2-qb~wGwp*Im^_e zH{|`nln->#O_S*^0^iAaG~g?AGf|g6jcBEqKY;|-K@!ON1;E9YZ32~_)WE-0vd})1 zO*GYZuinfPiC)ZcMpE{X#DzU%Qdh7_Wt-(KQ&u2Ng=Y}tPRWE%I+P%!SCUl349?d_ zG3CTw3H%)5Q-2mEI#X;vXr#HFy!2yO{d7~8@ODmUIk0#j9QetQp0UV>q v`Pq^a%gm~nNK9dN*^vsGa^Q%3bgu*d{sw ^LETmMV)cjJ8TN*B!$(YvK#aKr8A1* z_h#7XBg{ntTyg03C_>cVo(dsl3t6cOnwGx6YJ2UEWSp9m3o^CT{nAQ_w|=V7LMl?! z)G$_(vSfjDm6cP@WNfOOg(YQ&VW~?g4xKt~%2=v&l{Ax8GLoj6aVl*kPz>or0cfO5 z)$~`C;04(1SlyENHH#j$8MEmq`8b^8B(p4-ZEl^$Px)_M1ZadDc1zT}CVL@C#qtkV zmJS+T_F7 G0s>8ErTiUEZre_6LpgoaGe9X ze%5wTKM@(