970 lines
37 KiB
PHP
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)");
|
|
} |