HEX
Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.4.30
System: Linux iZj6c1151k3ad370bosnmsZ 3.10.0-1160.76.1.el7.x86_64 #1 SMP Wed Aug 10 16:21:17 UTC 2022 x86_64
User: root (0)
PHP: 7.4.30
Disabled: NONE
Upload Files
File: /var/www/html/www.winghung.com/wp-content/plugins/mxchat-basic/mxchat-basic.php
<?php
/**
 * Plugin Name: MxChat
 * Plugin URI: https://mxchat.ai/
 * Description: AI chatbot for WordPress with OpenAI, Claude, xAI, DeepSeek, live agent, PDF uploads, WooCommerce, and training on website data.
 * Version: 2.4.9
 * Author: MxChat
 * Author URI: https://mxchat.ai
 * License: GPLv2 or later
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain: mxchat
 * Domain Path: /languages
 */

if (!defined('ABSPATH')) {
    exit; // Exit if accessed directly.
}

// Define plugin version constant for asset versioning
define('MXCHAT_VERSION', '2.4.9');

function mxchat_load_textdomain() {
    load_plugin_textdomain('mxchat', false, dirname(plugin_basename(__FILE__)) . '/languages');
}
add_action('plugins_loaded', 'mxchat_load_textdomain');

// Include classes with error handling
function mxchat_include_classes() {
    $class_files = array(
        'includes/class-mxchat-integrator.php',
        'includes/class-mxchat-admin.php',
        'includes/class-mxchat-public.php',
        'includes/class-mxchat-utils.php',
        'includes/class-mxchat-user.php',
        'includes/class-mxchat-meta-box.php',
        'includes/pdf-parser/alt_autoload.php',
        'includes/class-mxchat-word-handler.php',
        'admin/class-ajax-handler.php',
        'admin/class-pinecone-manager.php',
        'admin/class-knowledge-manager.php'
    );

    foreach ($class_files as $file) {
        $file_path = plugin_dir_path(__FILE__) . $file;
        if (file_exists($file_path)) {
            require_once $file_path;
        } else {
            //error_log('MxChat: Missing class file - ' . $file);
        }
    }
}

/**
 * Create URL click tracking table
 */
function mxchat_create_url_clicks_table() {
    global $wpdb;
    
    $table_name = $wpdb->prefix . 'mxchat_url_clicks';
    
    $charset_collate = $wpdb->get_charset_collate();
    
    $sql = "CREATE TABLE $table_name (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        session_id varchar(100) NOT NULL,
        clicked_url text NOT NULL,
        message_context text,
        click_timestamp datetime DEFAULT CURRENT_TIMESTAMP,
        user_ip varchar(45),
        user_agent text,
        PRIMARY KEY (id),
        KEY session_id (session_id),
        KEY click_timestamp (click_timestamp)
    ) $charset_collate;";
    
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    dbDelta($sql);
}

/**
 * FIXED: Robust table creation and column management
 */
function mxchat_create_chat_transcripts_table() {
    global $wpdb;
    
    $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
    $charset_collate = $wpdb->get_charset_collate();
    
    // Create table with ALL columns including user_name from the start
    $sql = "CREATE TABLE $table_name (
        id MEDIUMINT(9) NOT NULL AUTO_INCREMENT,
        user_id MEDIUMINT(9) DEFAULT 0,
        session_id VARCHAR(255) NOT NULL,
        role VARCHAR(255) NOT NULL,
        message TEXT NOT NULL,
        user_email VARCHAR(255) DEFAULT NULL,
        user_name VARCHAR(100) DEFAULT NULL,
        user_identifier VARCHAR(255) DEFAULT NULL,
        originating_page_url TEXT DEFAULT NULL,
        originating_page_title VARCHAR(500) DEFAULT NULL,
        timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
        PRIMARY KEY (id),
        KEY session_id (session_id),
        KEY user_email (user_email),
        KEY timestamp (timestamp)
    ) $charset_collate;";
    
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    $result = dbDelta($sql);
    
    // Log the result for debugging
    if (empty($result)) {
        //error_log("MxChat: dbDelta returned empty result for chat transcripts table");
    } else {
        //error_log("MxChat: dbDelta result: " . print_r($result, true));
    }
    
    // IMPORTANT: Ensure all columns exist for existing installations
    mxchat_ensure_all_columns($table_name);
}

/**
 * Ensure all required columns exist (for upgrades)
 */
function mxchat_ensure_all_columns($table_name) {
    global $wpdb;
    
    // First check if table exists
    $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;
    if (!$table_exists) {
        //error_log("MxChat: Table $table_name does not exist, cannot add columns");
        return;
    }
    
    // Define all required columns and their types
    $required_columns = [
        'user_identifier' => 'VARCHAR(255) DEFAULT NULL',
        'user_email' => 'VARCHAR(255) DEFAULT NULL', 
        'user_name' => 'VARCHAR(100) DEFAULT NULL',
        'originating_page_url' => 'TEXT DEFAULT NULL',
        'originating_page_title' => 'VARCHAR(500) DEFAULT NULL'
    ];
    
    // Get existing columns
    $existing_columns = $wpdb->get_results("SHOW COLUMNS FROM $table_name");
    if (empty($existing_columns)) {
        //error_log("MxChat: Could not get columns for table $table_name");
        return;
    }
    
    $existing_column_names = array_column($existing_columns, 'Field');
    
    // Add missing columns
    foreach ($required_columns as $column_name => $column_definition) {
        if (!in_array($column_name, $existing_column_names)) {
            $alter_sql = "ALTER TABLE $table_name ADD COLUMN $column_name $column_definition";
            $result = $wpdb->query($alter_sql);
            
            if ($result === false) {
                //error_log("MxChat: Failed to add column $column_name to $table_name. Error: " . $wpdb->last_error);
            } else {
                //error_log("MxChat: Successfully added column $column_name to $table_name");
            }
        }
    }
}

/**
 * Add role restriction column to knowledge base table
 */
function mxchat_add_role_restriction_column() {
    global $wpdb;
    $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
    
    // Check if table exists first
    $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;
    if (!$table_exists) {
        //error_log("MxChat: System prompt content table does not exist, cannot add role_restriction column");
        return;
    }
    
    // Check if column already exists
    $column_exists = $wpdb->get_results(
        $wpdb->prepare(
            "SHOW COLUMNS FROM {$table_name} LIKE %s",
            'role_restriction'
        )
    );
    
    if (empty($column_exists)) {
        $alter_sql = "ALTER TABLE {$table_name} ADD COLUMN role_restriction VARCHAR(50) DEFAULT 'public' AFTER source_url";
        $result = $wpdb->query($alter_sql);
        
        if ($result === false) {
            //error_log("MxChat: Failed to add role_restriction column. Error: " . $wpdb->last_error);
        } else {
            //error_log("MxChat: Successfully added role_restriction column");
            
            // Set all existing records to 'public' (everyone can access)
            $update_result = $wpdb->query(
                "UPDATE {$table_name} 
                 SET role_restriction = 'public' 
                 WHERE role_restriction IS NULL OR role_restriction = ''"
            );
            
            if ($update_result !== false) {
                //error_log("MxChat: Updated {$update_result} existing records to public access");
            }
        }
    }
}

/**
 * NEW: Add enabled_bots column to intents table for multi-bot action filtering
 */
function mxchat_add_enabled_bots_column() {
    global $wpdb;
    $table_name = $wpdb->prefix . 'mxchat_intents';
    
    // Check if table exists first
    $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;
    if (!$table_exists) {
        //error_log("MxChat: Intents table does not exist, cannot add enabled_bots column");
        return;
    }
    
    // Check if column already exists
    $column_exists = $wpdb->get_results(
        $wpdb->prepare(
            "SHOW COLUMNS FROM {$table_name} LIKE %s",
            'enabled_bots'
        )
    );
    
    if (empty($column_exists)) {
        $alter_sql = "ALTER TABLE {$table_name} ADD COLUMN enabled_bots LONGTEXT DEFAULT NULL AFTER enabled";
        $result = $wpdb->query($alter_sql);
        
        if ($result === false) {
            //error_log("MxChat: Failed to add enabled_bots column. Error: " . $wpdb->last_error);
        } else {
            //error_log("MxChat: Successfully added enabled_bots column");
            
            // Set all existing actions to work with 'default' bot for backward compatibility
            $default_bots = json_encode(['default']);
            $update_result = $wpdb->query(
                $wpdb->prepare(
                    "UPDATE {$table_name} 
                     SET enabled_bots = %s 
                     WHERE enabled_bots IS NULL OR enabled_bots = ''",
                    $default_bots
                )
            );
            
            if ($update_result !== false) {
                //error_log("MxChat: Updated {$update_result} existing actions to work with default bot");
            }
        }
    }
}

/**
 * Create Pinecone role restrictions table
 */
function mxchat_create_pinecone_roles_table() {
    global $wpdb;
    
    $table_name = $wpdb->prefix . 'mxchat_pinecone_roles';
    $charset_collate = $wpdb->get_charset_collate();
    
    $sql = "CREATE TABLE $table_name (
        id mediumint(9) NOT NULL AUTO_INCREMENT,
        vector_id varchar(255) NOT NULL,
        source_url text,
        role_restriction varchar(50) DEFAULT 'public',
        updated_at timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        UNIQUE KEY vector_id (vector_id),
        KEY role_restriction (role_restriction)
    ) $charset_collate;";
    
    require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
    dbDelta($sql);
}

function mxchat_activate() {
    global $wpdb;
    $charset_collate = $wpdb->get_charset_collate();

    //error_log("MxChat: Running activation function");

    // Create chat transcripts table with improved function
    mxchat_create_chat_transcripts_table();

    // System Prompt Content Table
    $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
    $sql_system_prompt = "CREATE TABLE $system_prompt_table (
        id MEDIUMINT(9) NOT NULL AUTO_INCREMENT,
        url VARCHAR(255) NOT NULL,
        article_content LONGTEXT NOT NULL,
        embedding_vector LONGTEXT,
        source_url VARCHAR(255) DEFAULT NULL,
        role_restriction VARCHAR(50) DEFAULT 'public',
        timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
        PRIMARY KEY (id)
    ) $charset_collate;";

    // Intents Table - NOW INCLUDES enabled_bots column from the start
    $intents_table = $wpdb->prefix . 'mxchat_intents';
    $sql_intents_table = "CREATE TABLE $intents_table (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        intent_label VARCHAR(255) NOT NULL,
        phrases TEXT NOT NULL,
        embedding_vector LONGTEXT NOT NULL,
        callback_function VARCHAR(255) NOT NULL,
        similarity_threshold FLOAT DEFAULT 0.85,
        enabled TINYINT(1) NOT NULL DEFAULT 1,
        enabled_bots LONGTEXT DEFAULT NULL,
        PRIMARY KEY (id)
    ) $charset_collate;";

    require_once ABSPATH . 'wp-admin/includes/upgrade.php';

    // Create other tables
    dbDelta($sql_system_prompt);
    dbDelta($sql_intents_table);

    // Create URL click tracking table
    mxchat_create_url_clicks_table();
    
    //Create Pinecone roles table
    mxchat_create_pinecone_roles_table();

    // Ensure additional columns in system prompt table
    $existing_system_columns = $wpdb->get_results("SHOW COLUMNS FROM $system_prompt_table");
    if (!empty($existing_system_columns)) {
        $existing_system_column_names = array_column($existing_system_columns, 'Field');
        
        if (!in_array('embedding_vector', $existing_system_column_names)) {
            $wpdb->query("ALTER TABLE $system_prompt_table ADD COLUMN embedding_vector LONGTEXT");
        }
        if (!in_array('source_url', $existing_system_column_names)) {
            $wpdb->query("ALTER TABLE $system_prompt_table ADD COLUMN source_url VARCHAR(255) DEFAULT NULL");
        }
        if (!in_array('role_restriction', $existing_system_column_names)) {
            $wpdb->query("ALTER TABLE $system_prompt_table ADD COLUMN role_restriction VARCHAR(50) DEFAULT 'public' AFTER source_url");
        }
    }

    // Set default thresholds for existing intents
    $wpdb->query("UPDATE {$intents_table} SET similarity_threshold = 0.85 WHERE similarity_threshold IS NULL");
    
    // Ensure enabled column exists in intents table
    $existing_intent_columns = $wpdb->get_results("SHOW COLUMNS FROM $intents_table");
    if (!empty($existing_intent_columns)) {
        $existing_intent_column_names = array_column($existing_intent_columns, 'Field');
        
        if (!in_array('enabled', $existing_intent_column_names)) {
            $wpdb->query("ALTER TABLE $intents_table ADD COLUMN enabled TINYINT(1) NOT NULL DEFAULT 1");
        }
        
        // NEW: Ensure enabled_bots column exists for existing installations
        if (!in_array('enabled_bots', $existing_intent_column_names)) {
            $wpdb->query("ALTER TABLE $intents_table ADD COLUMN enabled_bots LONGTEXT DEFAULT NULL AFTER enabled");
            
            // Set existing actions to work with default bot
            $default_bots = json_encode(['default']);
            $wpdb->query($wpdb->prepare(
                "UPDATE {$intents_table} SET enabled_bots = %s WHERE enabled_bots IS NULL",
                $default_bots
            ));
        }
    }

    // Setup cron jobs
    mxchat_setup_cron_jobs();

    // Update version
    update_option('mxchat_plugin_version', MXCHAT_VERSION);
    
    //error_log("MxChat: Activation function completed");
}

/**
 * Setup cron jobs on plugin activation
 */
function mxchat_setup_cron_jobs() {
    // Clear any existing cron jobs first
    wp_clear_scheduled_hook('mxchat_reset_rate_limits');
    
    // Check if WordPress cron is disabled
    if (defined('DISABLE_WP_CRON') && DISABLE_WP_CRON) {
        // Set flag to use fallback system
        update_option('mxchat_use_fallback_rate_limits', true);
        update_option('mxchat_next_rate_limit_check', time() + 3600);
        return;
    }
    
    // Schedule the rate limit reset cron job
    $result = wp_schedule_event(time() + 300, 'hourly', 'mxchat_reset_rate_limits');
    
    if ($result === false) {
        // Fallback if scheduling fails
        update_option('mxchat_use_fallback_rate_limits', true);
        update_option('mxchat_next_rate_limit_check', time() + 3600);
    } else {
        // Clear fallback flags if cron scheduling succeeded
        delete_option('mxchat_use_fallback_rate_limits');
    }
}

/**
 * Clean up on plugin deactivation
 */
function mxchat_deactivate() {
    // Clear scheduled cron jobs
    wp_clear_scheduled_hook('mxchat_reset_rate_limits');
    
    // Clear fallback options
    delete_option('mxchat_use_fallback_rate_limits');
    delete_option('mxchat_next_rate_limit_check');
    delete_option('mxchat_fallback_check_interval');
}

/**
 * Check if fallback rate limit cleanup is needed
 */
function mxchat_check_fallback_rate_limits() {
    $use_fallback = get_option('mxchat_use_fallback_rate_limits', false);
    
    if (!$use_fallback) {
        return;
    }
    
    $next_check = get_option('mxchat_next_rate_limit_check', 0);
    
    if (time() >= $next_check) {
        // Only run reset if the MxChat_Integrator class exists
        if (class_exists('MxChat_Integrator')) {
            $integrator = new MxChat_Integrator();
            if (method_exists($integrator, 'mxchat_reset_rate_limits')) {
                $integrator->mxchat_reset_rate_limits();
                update_option('mxchat_next_rate_limit_check', time() + 3600);
            }
        }
    }
}

/**
 * Robust update checking with role restriction migration
 */
function mxchat_check_for_update() {
    global $wpdb; // CRITICAL: Declare this at the top
    
    try {
        $current_version = get_option('mxchat_plugin_version', '0.0.0');
        $plugin_version = MXCHAT_VERSION;

        // Always run activation to ensure tables exist (safe for existing installations)
        if ($current_version !== $plugin_version) {
            //error_log("MxChat: Version change detected: $current_version -> $plugin_version");
            
            // Run live agent update BEFORE updating the stored version
            mxchat_handle_live_agent_update();
            
            //Run role restriction migration for 2.4.1
            if (version_compare($current_version, '2.4.1', '<')) {
                mxchat_add_role_restriction_column();
            }
            
            // NEW: Run enabled_bots column migration for 2.4.4
            if (version_compare($current_version, '2.4.4', '<')) {
                mxchat_add_enabled_bots_column();
            }
            
            // Run activation (this will create/update all tables and columns)
            mxchat_activate();
            
            // Run migration functions
            mxchat_migrate_live_agent_status();

            // Add the cleanup function for version 2.1.8
            if (version_compare($current_version, '2.1.8', '<')) {
                $deleted = mxchat_cleanup_orphaned_chat_history();
            }

            // Update version LAST
            update_option('mxchat_plugin_version', $plugin_version);
            
            //error_log("MxChat: Updated from version $current_version to $plugin_version");
        }
        
        // CRITICAL: Always ensure tables exist, even if version matches
        // This handles cases where tables were manually deleted
        $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
        $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;
        
        if (!$table_exists) {
            //error_log("MxChat: Chat transcripts table missing, recreating...");
            mxchat_activate(); // Run full activation instead of just table creation
        }
        
    } catch (Exception $e) {
        //error_log('MxChat update error: ' . $e->getMessage());
        // Don't update version if there was an error
    }
}

/**
 * Ensure tables exist on every load for fresh installations
 */
function mxchat_ensure_tables_exist() {
    global $wpdb;
    
    // Only run for admin users to avoid performance impact
    if (!current_user_can('administrator')) {
        return;
    }
    
    $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
    $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;
    
    if (!$table_exists) {
        //error_log("MxChat: Tables missing on admin load, running activation");
        mxchat_activate();
    }
}

/**
 * Clean up orphaned chat history options from the wp_options table
 * @return int Number of options deleted
 */
function mxchat_cleanup_orphaned_chat_history() {
    global $wpdb;
    $count = 0;

    // Get all option keys that match our pattern
    $history_options = $wpdb->get_results(
        "SELECT option_name FROM {$wpdb->options}
         WHERE option_name LIKE 'mxchat_history_%'"
    );

    if (!empty($history_options)) {
        foreach ($history_options as $option) {
            // Extract the session ID from the option name
            $session_id = str_replace('mxchat_history_', '', $option->option_name);

            // Check if this session still exists in the custom table
            $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
            $exists = $wpdb->get_var(
                $wpdb->prepare(
                    "SELECT COUNT(*) FROM {$table_name} WHERE session_id = %s",
                    $session_id
                )
            );

            // If session doesn't exist in the main table, delete the option
            if ($exists == 0) {
                delete_option($option->option_name);
                // Also delete related metadata
                delete_option("mxchat_email_{$session_id}");
                delete_option("mxchat_name_{$session_id}");
                delete_option("mxchat_agent_name_{$session_id}");
                $count++;
            }
        }
    }

    return $count;
}

function mxchat_migrate_live_agent_status() {
    $options = get_option('mxchat_options', []);

    // Check if live_agent_status exists
    if (isset($options['live_agent_status'])) {
        $current_status = $options['live_agent_status'];
        $needs_update = false;

        // Convert to new format if needed
        if ($current_status === 'online') {
            $options['live_agent_status'] = 'on';
            $needs_update = true;
        } else if ($current_status === 'offline') {
            $options['live_agent_status'] = 'off';
            $needs_update = true;
        } else if (!in_array($current_status, ['on', 'off'])) {
            // Default to off for any unexpected values
            $options['live_agent_status'] = 'off';
            $needs_update = true;
        }

        // Only update if needed
        if ($needs_update) {
            update_option('mxchat_options', $options);
        }
    } else {
        // If status doesn't exist, set default to off
        $options['live_agent_status'] = 'off';
        update_option('mxchat_options', $options);
    }
}

function mxchat_handle_live_agent_update() {
    // Get the CURRENT stored version (before it gets updated)
    $current_version = get_option('mxchat_plugin_version', '0.0.0');
    $new_version = '2.2.2';
    
    // Only run this once for the update to 2.2.2
    $update_handled = get_option('mxchat_live_agent_update_2_2_2_handled', false);
    
    // Check if we're upgrading TO 2.2.2 and haven't handled this yet
    if (version_compare($current_version, $new_version, '<') && !$update_handled) {
        $options = get_option('mxchat_options', array());
        
        // Check if live agent was previously enabled
        if (isset($options['live_agent_status']) && $options['live_agent_status'] === 'on') {
            // Disable live agent
            $options['live_agent_status'] = 'off';
            update_option('mxchat_options', $options);
            
            // Set flag to show the notification banner
            update_option('mxchat_show_live_agent_disabled_notice', true);
        }
        
        // Mark this update as handled
        update_option('mxchat_live_agent_update_2_2_2_handled', true);
    }
}

// Initialize plugin safely
function mxchat_init() {
    // Include all class files first
    mxchat_include_classes();
    
    // Run update check
    mxchat_check_for_update();
    
    // CRITICAL: Ensure tables exist (for fresh installations that don't trigger activation hook)
    add_action('admin_init', 'mxchat_ensure_tables_exist', 1);
    
    // Add fallback rate limit check
    add_action('init', 'mxchat_check_fallback_rate_limits', 5);
    
    // Initialize classes with error handling
    try {
        // Initialize admin classes
        if (is_admin()) {
            if (class_exists('MxChat_Knowledge_Manager')) {
                $mxchat_knowledge_manager = new MxChat_Knowledge_Manager();
                
                if (class_exists('MxChat_Admin')) {
                    $mxchat_admin = new MxChat_Admin($mxchat_knowledge_manager);
                }
            }
            
            // Initialize meta box class
            if (class_exists('MxChat_Meta_Box')) {
                new MxChat_Meta_Box();
            }
        }
        
        // Initialize public classes
        if (class_exists('MxChat_Public')) {
            $mxchat_public = new MxChat_Public();
        }
        
        if (class_exists('MxChat_Integrator')) {
            $mxchat_integrator = new MxChat_Integrator();
        }
        
    } catch (Exception $e) {
        //error_log('MxChat initialization error: ' . $e->getMessage());
        
        // Show admin notice if there's an error
        if (is_admin()) {
            add_action('admin_notices', function() use ($e) {
                echo '<div class="notice notice-error"><p>';
                echo '<strong>MxChat Error:</strong> Plugin initialization failed. ';
                echo 'Please check error logs or contact support. Error: ' . esc_html($e->getMessage());
                echo '</p></div>';
            });
        }
    }
}

// Run initialization on plugins_loaded
add_action('plugins_loaded', 'mxchat_init');

// Register activation hook
register_activation_hook(__FILE__, 'mxchat_activate');

// Add cron schedule
add_filter('cron_schedules', function($schedules) {
    $schedules['one_minute'] = array(
        'interval' => 60,
        'display' => 'Every Minute'
    );
    return $schedules;
});

// Register deactivation hook
register_deactivation_hook(__FILE__, 'mxchat_deactivate');