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

970 lines
37 KiB
PHP

<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : db.php
* @location : /core/db/db.php
* @type : Function Library
* @scope : Global
* @allowed : Database operations, schema management, data queries
* @reusability : Globally Reusable
* @notes : Central database operations and schema management
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Clean up legacy database structures (if any exist from old installations)
*
* This function handles cleanup of any legacy structures that might exist
* from previous plugin versions, but all new installations use the correct schema.
*/
function igny8_cleanup_legacy_structures() {
global $wpdb;
$table_name = $wpdb->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 '<meta name="robots" content="noindex,follow" />' . "\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)");
}