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');