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)"); }