PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.0.7
MxChat – AI Chatbot & Content Generation for WordPress v3.0.7
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
mxchat-basic / mxchat-basic.php

mxchat-basic.php in MxChat – AI Chatbot & Content Generation for WordPress 3.0.7, at mxchat-basic.php

1,142 lines 42.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: MxChat
4 * Plugin URI: https://mxchat.ai/
5 * Description: AI chatbot for WordPress with OpenAI, Claude, xAI, DeepSeek, live agent, PDF uploads, WooCommerce, and training on website data.
6 * Version: 3.0.7
7 * Author: MxChat
8 * Author URI: https://mxchat.ai
9 * License: GPLv2 or later
10 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
11 * Text Domain: mxchat
12 * Domain Path: /languages
13 */
14
15 if (!defined('ABSPATH')) {
16 exit; // Exit if accessed directly.
17 }
18
19 // Define plugin version constant for asset versioning
20 // Reads version from plugin header automatically
21 if (!defined('MXCHAT_VERSION')) {
22 $plugin_data = get_file_data(__FILE__, array('Version' => 'Version'), 'plugin');
23 define('MXCHAT_VERSION', $plugin_data['Version']);
24 }
25
26 function mxchat_load_textdomain() {
27 $domain = 'mxchat';
28 $locale = determine_locale();
29
30 // First, try to load from /wp-content/languages/plugins/ (preserved during updates)
31 $mo_file = WP_LANG_DIR . '/plugins/' . $domain . '-' . $locale . '.mo';
32 if (file_exists($mo_file)) {
33 load_textdomain($domain, $mo_file);
34 return;
35 }
36
37 // Fallback to plugin's /languages directory
38 load_plugin_textdomain($domain, false, dirname(plugin_basename(__FILE__)) . '/languages');
39 }
40 add_action('init', 'mxchat_load_textdomain');
41
42 // Include classes with error handling
43 function mxchat_include_classes() {
44 $class_files = array(
45 'includes/class-mxchat-integrator.php',
46 'includes/class-mxchat-admin.php',
47 'includes/class-mxchat-public.php',
48 'includes/class-mxchat-utils.php',
49 'includes/class-mxchat-user.php',
50 'includes/class-mxchat-meta-box.php',
51 'includes/class-mxchat-chunker.php',
52 'includes/pdf-parser/alt_autoload.php',
53 'includes/class-mxchat-word-handler.php',
54 'admin/class-ajax-handler.php',
55 'admin/class-pinecone-manager.php',
56 'admin/class-knowledge-manager.php'
57 );
58
59 foreach ($class_files as $file) {
60 $file_path = plugin_dir_path(__FILE__) . $file;
61 if (file_exists($file_path)) {
62 require_once $file_path;
63 } else {
64 //error_log('MxChat: Missing class file - ' . $file);
65 }
66 }
67 }
68
69 /**
70 * Create URL click tracking table
71 */
72 function mxchat_create_url_clicks_table() {
73 global $wpdb;
74
75 $table_name = $wpdb->prefix . 'mxchat_url_clicks';
76
77 $charset_collate = $wpdb->get_charset_collate();
78
79 $sql = "CREATE TABLE $table_name (
80 id mediumint(9) NOT NULL AUTO_INCREMENT,
81 session_id varchar(100) NOT NULL,
82 clicked_url text NOT NULL,
83 message_context text,
84 click_timestamp datetime DEFAULT CURRENT_TIMESTAMP,
85 user_ip varchar(45),
86 user_agent text,
87 PRIMARY KEY (id),
88 KEY session_id (session_id),
89 KEY click_timestamp (click_timestamp)
90 ) $charset_collate;";
91
92 require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
93 dbDelta($sql);
94 }
95
96 /**
97 * FIXED: Robust table creation and column management
98 */
99 function mxchat_create_chat_transcripts_table() {
100 global $wpdb;
101
102 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
103 $charset_collate = $wpdb->get_charset_collate();
104
105 // Create table with ALL columns including user_name from the start
106 $sql = "CREATE TABLE $table_name (
107 id MEDIUMINT(9) NOT NULL AUTO_INCREMENT,
108 user_id MEDIUMINT(9) DEFAULT 0,
109 session_id VARCHAR(255) NOT NULL,
110 role VARCHAR(255) NOT NULL,
111 message TEXT NOT NULL,
112 user_email VARCHAR(255) DEFAULT NULL,
113 user_name VARCHAR(100) DEFAULT NULL,
114 user_identifier VARCHAR(255) DEFAULT NULL,
115 originating_page_url TEXT DEFAULT NULL,
116 originating_page_title VARCHAR(500) DEFAULT NULL,
117 timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
118 PRIMARY KEY (id),
119 KEY session_id (session_id),
120 KEY user_email (user_email),
121 KEY timestamp (timestamp)
122 ) $charset_collate;";
123
124 require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
125 $result = dbDelta($sql);
126
127 // Log the result for debugging
128 if (empty($result)) {
129 //error_log("MxChat: dbDelta returned empty result for chat transcripts table");
130 } else {
131 //error_log("MxChat: dbDelta result: " . print_r($result, true));
132 }
133
134 // IMPORTANT: Ensure all columns exist for existing installations
135 mxchat_ensure_all_columns($table_name);
136 }
137
138 /**
139 * Ensure all required columns exist (for upgrades)
140 */
141 function mxchat_ensure_all_columns($table_name) {
142 global $wpdb;
143
144 // First check if table exists
145 $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;
146 if (!$table_exists) {
147 //error_log("MxChat: Table $table_name does not exist, cannot add columns");
148 return;
149 }
150
151 // Define all required columns and their types
152 $required_columns = [
153 'user_identifier' => 'VARCHAR(255) DEFAULT NULL',
154 'user_email' => 'VARCHAR(255) DEFAULT NULL',
155 'user_name' => 'VARCHAR(100) DEFAULT NULL',
156 'originating_page_url' => 'TEXT DEFAULT NULL',
157 'originating_page_title' => 'VARCHAR(500) DEFAULT NULL',
158 'rag_context' => 'LONGTEXT DEFAULT NULL'
159 ];
160
161 // Get existing columns
162 $existing_columns = $wpdb->get_results("SHOW COLUMNS FROM $table_name");
163 if (empty($existing_columns)) {
164 //error_log("MxChat: Could not get columns for table $table_name");
165 return;
166 }
167
168 $existing_column_names = array_column($existing_columns, 'Field');
169
170 // Add missing columns
171 foreach ($required_columns as $column_name => $column_definition) {
172 if (!in_array($column_name, $existing_column_names)) {
173 $alter_sql = "ALTER TABLE $table_name ADD COLUMN $column_name $column_definition";
174 $result = $wpdb->query($alter_sql);
175
176 if ($result === false) {
177 //error_log("MxChat: Failed to add column $column_name to $table_name. Error: " . $wpdb->last_error);
178 } else {
179 //error_log("MxChat: Successfully added column $column_name to $table_name");
180 }
181 }
182 }
183 }
184
185 /**
186 * Add role restriction column to knowledge base table
187 */
188 function mxchat_add_role_restriction_column() {
189 global $wpdb;
190 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
191
192 // Check if table exists first
193 $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;
194 if (!$table_exists) {
195 //error_log("MxChat: System prompt content table does not exist, cannot add role_restriction column");
196 return;
197 }
198
199 // Check if column already exists
200 $column_exists = $wpdb->get_results(
201 $wpdb->prepare(
202 "SHOW COLUMNS FROM {$table_name} LIKE %s",
203 'role_restriction'
204 )
205 );
206
207 if (empty($column_exists)) {
208 $alter_sql = "ALTER TABLE {$table_name} ADD COLUMN role_restriction VARCHAR(50) DEFAULT 'public' AFTER source_url";
209 $result = $wpdb->query($alter_sql);
210
211 if ($result === false) {
212 //error_log("MxChat: Failed to add role_restriction column. Error: " . $wpdb->last_error);
213 } else {
214 //error_log("MxChat: Successfully added role_restriction column");
215
216 // Set all existing records to 'public' (everyone can access)
217 $update_result = $wpdb->query(
218 "UPDATE {$table_name}
219 SET role_restriction = 'public'
220 WHERE role_restriction IS NULL OR role_restriction = ''"
221 );
222
223 if ($update_result !== false) {
224 //error_log("MxChat: Updated {$update_result} existing records to public access");
225 }
226 }
227 }
228 }
229
230 /**
231 * Add enabled_bots column to intents table for multi-bot action filtering
232 */
233 function mxchat_add_enabled_bots_column() {
234 global $wpdb;
235 $table_name = $wpdb->prefix . 'mxchat_intents';
236
237 // Check if table exists first
238 $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;
239 if (!$table_exists) {
240 //error_log("MxChat: Intents table does not exist, cannot add enabled_bots column");
241 return;
242 }
243
244 // Check if column already exists
245 $column_exists = $wpdb->get_results(
246 $wpdb->prepare(
247 "SHOW COLUMNS FROM {$table_name} LIKE %s",
248 'enabled_bots'
249 )
250 );
251
252 if (empty($column_exists)) {
253 $alter_sql = "ALTER TABLE {$table_name} ADD COLUMN enabled_bots LONGTEXT DEFAULT NULL AFTER enabled";
254 $result = $wpdb->query($alter_sql);
255
256 if ($result === false) {
257 //error_log("MxChat: Failed to add enabled_bots column. Error: " . $wpdb->last_error);
258 } else {
259 //error_log("MxChat: Successfully added enabled_bots column");
260
261 // Set all existing actions to work with 'default' bot for backward compatibility
262 $default_bots = json_encode(['default']);
263 $update_result = $wpdb->query(
264 $wpdb->prepare(
265 "UPDATE {$table_name}
266 SET enabled_bots = %s
267 WHERE enabled_bots IS NULL OR enabled_bots = ''",
268 $default_bots
269 )
270 );
271
272 if ($update_result !== false) {
273 //error_log("MxChat: Updated {$update_result} existing actions to work with default bot");
274 }
275 }
276 }
277 }
278
279 /**
280 * Create Pinecone role restrictions table with multi-bot support
281 */
282 function mxchat_create_pinecone_roles_table() {
283 global $wpdb;
284
285 $table_name = $wpdb->prefix . 'mxchat_pinecone_roles';
286 $charset_collate = $wpdb->get_charset_collate();
287
288 $sql = "CREATE TABLE $table_name (
289 id mediumint(9) NOT NULL AUTO_INCREMENT,
290 vector_id varchar(255) NOT NULL,
291 bot_id varchar(50) NOT NULL DEFAULT 'default',
292 source_url text,
293 role_restriction varchar(50) DEFAULT 'public',
294 updated_at timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
295 PRIMARY KEY (id),
296 UNIQUE KEY vector_bot (vector_id, bot_id),
297 KEY role_restriction (role_restriction),
298 KEY bot_id (bot_id)
299 ) $charset_collate;";
300
301 require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
302 dbDelta($sql);
303 }
304
305 /**
306 * Add bot_id column to mxchat_pinecone_roles table for multi-bot support
307 * This migration runs once to update existing installations
308 */
309 function mxchat_migrate_pinecone_roles_add_bot_id() {
310 global $wpdb;
311
312 // Check if migration already ran
313 $migration_version = get_option('mxchat_pinecone_roles_migration_version', '0');
314 if (version_compare($migration_version, '2.5.2', '>=')) {
315 return; // Already migrated
316 }
317
318 $table_name = $wpdb->prefix . 'mxchat_pinecone_roles';
319
320 // Check if table exists
321 if ($wpdb->get_var("SHOW TABLES LIKE '{$table_name}'") != $table_name) {
322 return; // Table doesn't exist yet
323 }
324
325 // Check if bot_id column already exists
326 $column_exists = $wpdb->get_results("SHOW COLUMNS FROM {$table_name} LIKE 'bot_id'");
327
328 if (empty($column_exists)) {
329 // Add bot_id column
330 $wpdb->query("ALTER TABLE {$table_name} ADD COLUMN bot_id VARCHAR(50) NOT NULL DEFAULT 'default' AFTER vector_id");
331
332 // Update the unique key to include bot_id
333 $wpdb->query("ALTER TABLE {$table_name} DROP INDEX vector_id");
334 $wpdb->query("ALTER TABLE {$table_name} ADD UNIQUE KEY vector_bot (vector_id, bot_id)");
335
336 // Add index for bot_id
337 $wpdb->query("ALTER TABLE {$table_name} ADD KEY bot_id (bot_id)");
338
339 error_log('MxChat: Successfully added bot_id column to mxchat_pinecone_roles table');
340 }
341
342 // Mark migration as complete
343 update_option('mxchat_pinecone_roles_migration_version', '2.5.2');
344 }
345
346 /**
347 * 2.5.6: Add content_type column to mxchat_system_prompt_content table
348 * Enables filtering knowledge base by content type (posts, pages, PDFs, etc.)
349 */
350 function mxchat_migrate_add_content_type_column() {
351 global $wpdb;
352
353 // Check if migration already ran
354 $migration_version = get_option('mxchat_content_type_migration_version', '0');
355 if (version_compare($migration_version, '2.5.6', '>=')) {
356 return;
357 }
358
359 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
360
361 // Check if table exists
362 if ($wpdb->get_var("SHOW TABLES LIKE '{$table_name}'") != $table_name) {
363 return;
364 }
365
366 // Check if content_type column already exists
367 $column_exists = $wpdb->get_results("SHOW COLUMNS FROM {$table_name} LIKE 'content_type'");
368
369 if (empty($column_exists)) {
370 // Add content_type column with default value 'content' for backwards compatibility
371 $wpdb->query("ALTER TABLE {$table_name} ADD COLUMN content_type VARCHAR(50) DEFAULT 'content' AFTER role_restriction");
372
373 // Add index for better query performance
374 $wpdb->query("ALTER TABLE {$table_name} ADD KEY content_type (content_type)");
375
376 error_log('MxChat: Successfully added content_type column to mxchat_system_prompt_content table');
377 }
378
379 // Mark migration as complete
380 update_option('mxchat_content_type_migration_version', '2.5.6');
381 }
382
383 /**
384 * 2.5.2: Create queue processing tables for reliable background processing
385 */
386 function mxchat_create_queue_tables() {
387 global $wpdb;
388 $charset_collate = $wpdb->get_charset_collate();
389
390 // Main queue table
391 $queue_table = $wpdb->prefix . 'mxchat_processing_queue';
392 $sql_queue = "CREATE TABLE $queue_table (
393 id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
394 queue_id varchar(64) NOT NULL,
395 item_type varchar(20) NOT NULL,
396 item_data longtext NOT NULL,
397 status varchar(20) NOT NULL DEFAULT 'pending',
398 bot_id varchar(50) NOT NULL DEFAULT 'default',
399 priority int(11) NOT NULL DEFAULT 0,
400 attempts int(11) NOT NULL DEFAULT 0,
401 max_attempts int(11) NOT NULL DEFAULT 3,
402 error_message text DEFAULT NULL,
403 created_at datetime NOT NULL,
404 started_at datetime DEFAULT NULL,
405 completed_at datetime DEFAULT NULL,
406 PRIMARY KEY (id),
407 KEY queue_id (queue_id),
408 KEY status (status),
409 KEY item_type (item_type),
410 KEY priority (priority)
411 ) $charset_collate;";
412
413 // Queue metadata table
414 $meta_table = $wpdb->prefix . 'mxchat_queue_meta';
415 $sql_meta = "CREATE TABLE $meta_table (
416 id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
417 queue_id varchar(64) NOT NULL,
418 meta_key varchar(255) NOT NULL,
419 meta_value longtext,
420 PRIMARY KEY (id),
421 KEY queue_id (queue_id),
422 KEY meta_key (meta_key)
423 ) $charset_collate;";
424
425 require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
426 dbDelta($sql_queue);
427 dbDelta($sql_meta);
428
429 //error_log("MxChat: Queue tables created/updated successfully");
430 }
431
432 /**
433 * Create transcript translations table for persisting translations
434 */
435 function mxchat_create_translations_table() {
436 global $wpdb;
437 $charset_collate = $wpdb->get_charset_collate();
438
439 $table_name = $wpdb->prefix . 'mxchat_transcript_translations';
440 $sql = "CREATE TABLE $table_name (
441 id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
442 session_id varchar(255) NOT NULL,
443 language_code varchar(10) NOT NULL,
444 translations longtext NOT NULL,
445 created_at datetime NOT NULL,
446 updated_at datetime NOT NULL,
447 PRIMARY KEY (id),
448 UNIQUE KEY session_lang (session_id, language_code),
449 KEY session_id (session_id)
450 ) $charset_collate;";
451
452 require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
453 dbDelta($sql);
454 }
455
456 /**
457 * 2.5.2: Fix URL column size to support long URLs (especially with UTF-8 encoding)
458 * This fixes "url, source_url. The supplied values may be too long" errors
459 */
460 function mxchat_fix_url_column_size() {
461 global $wpdb;
462 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
463
464 // Check if table exists
465 $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;
466 if (!$table_exists) {
467 return;
468 }
469
470 // Change url and source_url from VARCHAR to TEXT to handle long URLs
471 // This is especially important for URLs with UTF-8 encoded characters (Hebrew, Arabic, etc.)
472 $wpdb->query("ALTER TABLE {$table_name} MODIFY COLUMN url TEXT");
473 $wpdb->query("ALTER TABLE {$table_name} MODIFY COLUMN source_url TEXT");
474
475 //error_log("MxChat: Successfully updated url and source_url columns to TEXT type for long URL support");
476 }
477
478 /**
479 * Migrate deprecated AI models to their replacements
480 * Version 2.5.1: Migrate Claude 3.5 Sonnet (deprecated) to Claude 3.7 Sonnet
481 * Version 3.0.55: Migrate GPT-4 series models (deprecated 2026-02-17) to GPT-5 series
482 */
483 function mxchat_migrate_deprecated_models() {
484 $options = get_option('mxchat_options', array());
485 $migrated = false;
486 $migration_message = '';
487
488 if (!isset($options['model'])) {
489 return;
490 }
491
492 $current_model = $options['model'];
493
494 // Migrate deprecated Claude models to Claude Opus 4.6 (recommended replacement per Anthropic)
495 $deprecated_claude_models = array(
496 'claude-3-5-sonnet-20240620', // Retired Oct 28, 2025
497 'claude-3-5-sonnet-20241022', // Retired Oct 28, 2025
498 'claude-3-7-sonnet-20250219', // Retiring Feb 19, 2026
499 'claude-3-opus-20240229', // Retired Jan 5, 2026
500 'claude-3-sonnet-20240229', // Legacy
501 'claude-3-haiku-20240307', // Legacy
502 );
503 if (in_array($current_model, $deprecated_claude_models, true)) {
504 $options['model'] = 'claude-opus-4-6';
505 $migrated = true;
506 $migration_message = sprintf(
507 __('Your chatbot model has been automatically updated from %s to Claude Opus 4.6 due to Anthropic deprecating older Claude models.', 'mxchat'),
508 $current_model
509 );
510 }
511
512 // Migrate deprecated Claude Haiku 3.5 to Claude Haiku 4.5
513 if ($current_model === 'claude-3-5-haiku-20241022') {
514 $options['model'] = 'claude-haiku-4-5-20251001';
515 $migrated = true;
516 $migration_message = __('Your chatbot model has been automatically updated from Claude Haiku 3.5 to Claude Haiku 4.5 due to Anthropic deprecating the older model.', 'mxchat');
517 }
518
519 // Migrate deprecated GPT-4 series and GPT-3.5 Turbo to GPT-5.1 Chat Latest
520 if (in_array($current_model, array('gpt-4o', 'gpt-4.1-2025-04-14', 'gpt-4-turbo', 'gpt-4', 'gpt-3.5-turbo'), true)) {
521 $options['model'] = 'gpt-5.1-chat-latest';
522 $migrated = true;
523 $migration_message = sprintf(
524 __('Your chatbot model has been automatically updated from %s to GPT-5.1 Chat Latest due to OpenAI deprecating older models.', 'mxchat'),
525 $current_model
526 );
527 }
528
529 // Migrate deprecated GPT-4o Mini and GPT-4.1 Mini to GPT-5 Mini
530 if (in_array($current_model, array('gpt-4o-mini', 'gpt-4.1-mini'), true)) {
531 $options['model'] = 'gpt-5-mini';
532 $migrated = true;
533 $migration_message = sprintf(
534 __('Your chatbot model has been automatically updated from %s to GPT-5 Mini due to OpenAI deprecating GPT-4 series models.', 'mxchat'),
535 $current_model
536 );
537 }
538
539 if ($migrated) {
540 update_option('mxchat_options', $options);
541 update_option('mxchat_model_migrated_notice', true);
542 update_option('mxchat_model_migration_message', $migration_message);
543 }
544 }
545
546 /**
547 * Show admin notice after model migration
548 */
549 function mxchat_show_migration_notice() {
550 if (get_option('mxchat_model_migrated_notice')) {
551 $migration_message = get_option('mxchat_model_migration_message', __('Your chatbot model has been automatically updated due to a model deprecation.', 'mxchat'));
552 ?>
553 <div class="notice notice-info is-dismissible">
554 <p>
555 <strong><?php esc_html_e('MxChat Model Updated', 'mxchat'); ?></strong><br>
556 <?php echo esc_html($migration_message); ?>
557 </p>
558 </div>
559 <?php
560 delete_option('mxchat_model_migrated_notice');
561 delete_option('mxchat_model_migration_message');
562 }
563 }
564
565 function mxchat_activate() {
566 global $wpdb;
567 $charset_collate = $wpdb->get_charset_collate();
568
569 //error_log("MxChat: Running activation function");
570
571 // Create chat transcripts table with improved function
572 mxchat_create_chat_transcripts_table();
573
574 // System Prompt Content Table - UPDATED: Use TEXT for url and source_url columns
575 $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
576 $sql_system_prompt = "CREATE TABLE $system_prompt_table (
577 id MEDIUMINT(9) NOT NULL AUTO_INCREMENT,
578 url TEXT NOT NULL,
579 article_content LONGTEXT NOT NULL,
580 embedding_vector LONGTEXT,
581 source_url TEXT DEFAULT NULL,
582 role_restriction VARCHAR(50) DEFAULT 'public',
583 content_type VARCHAR(50) DEFAULT 'content',
584 timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
585 PRIMARY KEY (id),
586 KEY content_type (content_type)
587 ) $charset_collate;";
588
589 // Intents Table - NOW INCLUDES enabled_bots column from the start
590 $intents_table = $wpdb->prefix . 'mxchat_intents';
591 $sql_intents_table = "CREATE TABLE $intents_table (
592 id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
593 intent_label VARCHAR(255) NOT NULL,
594 phrases TEXT NOT NULL,
595 embedding_vector LONGTEXT NOT NULL,
596 callback_function VARCHAR(255) NOT NULL,
597 similarity_threshold FLOAT DEFAULT 0.85,
598 enabled TINYINT(1) NOT NULL DEFAULT 1,
599 enabled_bots LONGTEXT DEFAULT NULL,
600 PRIMARY KEY (id)
601 ) $charset_collate;";
602
603 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
604
605 // Create other tables
606 dbDelta($sql_system_prompt);
607 dbDelta($sql_intents_table);
608
609 // Create URL click tracking table
610 mxchat_create_url_clicks_table();
611
612 // Create Pinecone roles table
613 mxchat_create_pinecone_roles_table();
614
615 // NEW 2.5.2: Create queue processing tables
616 mxchat_create_queue_tables();
617
618 // Create transcript translations table
619 mxchat_create_translations_table();
620
621 // Ensure additional columns in system prompt table
622 $existing_system_columns = $wpdb->get_results("SHOW COLUMNS FROM $system_prompt_table");
623 if (!empty($existing_system_columns)) {
624 $existing_system_column_names = array_column($existing_system_columns, 'Field');
625
626 if (!in_array('embedding_vector', $existing_system_column_names)) {
627 $wpdb->query("ALTER TABLE $system_prompt_table ADD COLUMN embedding_vector LONGTEXT");
628 }
629 if (!in_array('source_url', $existing_system_column_names)) {
630 $wpdb->query("ALTER TABLE $system_prompt_table ADD COLUMN source_url TEXT DEFAULT NULL");
631 }
632 if (!in_array('role_restriction', $existing_system_column_names)) {
633 $wpdb->query("ALTER TABLE $system_prompt_table ADD COLUMN role_restriction VARCHAR(50) DEFAULT 'public' AFTER source_url");
634 }
635 }
636
637 // Set default thresholds for existing intents
638 $wpdb->query("UPDATE {$intents_table} SET similarity_threshold = 0.85 WHERE similarity_threshold IS NULL");
639
640 // Ensure enabled column exists in intents table
641 $existing_intent_columns = $wpdb->get_results("SHOW COLUMNS FROM $intents_table");
642 if (!empty($existing_intent_columns)) {
643 $existing_intent_column_names = array_column($existing_intent_columns, 'Field');
644
645 if (!in_array('enabled', $existing_intent_column_names)) {
646 $wpdb->query("ALTER TABLE $intents_table ADD COLUMN enabled TINYINT(1) NOT NULL DEFAULT 1");
647 }
648
649 // Ensure enabled_bots column exists for existing installations
650 if (!in_array('enabled_bots', $existing_intent_column_names)) {
651 $wpdb->query("ALTER TABLE $intents_table ADD COLUMN enabled_bots LONGTEXT DEFAULT NULL AFTER enabled");
652
653 // Set existing actions to work with default bot
654 $default_bots = json_encode(['default']);
655 $wpdb->query($wpdb->prepare(
656 "UPDATE {$intents_table} SET enabled_bots = %s WHERE enabled_bots IS NULL",
657 $default_bots
658 ));
659 }
660 }
661
662 // Run migration for existing installations
663 mxchat_migrate_pinecone_roles_add_bot_id();
664
665 // Setup cron jobs
666 mxchat_setup_cron_jobs();
667
668 // Update version
669 update_option('mxchat_plugin_version', MXCHAT_VERSION);
670
671 //error_log("MxChat: Activation function completed");
672 }
673
674 /**
675 * Setup cron jobs on plugin activation
676 */
677 function mxchat_setup_cron_jobs() {
678 // Clear any existing cron jobs first
679 wp_clear_scheduled_hook('mxchat_reset_rate_limits');
680
681 // Check if WordPress cron is disabled
682 if (defined('DISABLE_WP_CRON') && DISABLE_WP_CRON) {
683 // Set flag to use fallback system
684 update_option('mxchat_use_fallback_rate_limits', true);
685 update_option('mxchat_next_rate_limit_check', time() + 3600);
686 return;
687 }
688
689 // Schedule the rate limit reset cron job
690 $result = wp_schedule_event(time() + 300, 'hourly', 'mxchat_reset_rate_limits');
691
692 if ($result === false) {
693 // Fallback if scheduling fails
694 update_option('mxchat_use_fallback_rate_limits', true);
695 update_option('mxchat_next_rate_limit_check', time() + 3600);
696 } else {
697 // Clear fallback flags if cron scheduling succeeded
698 delete_option('mxchat_use_fallback_rate_limits');
699 }
700
701 // Schedule transcript cleanup if configured
702 $transcript_options = get_option('mxchat_transcripts_options', array());
703 $cleanup_interval = isset($transcript_options['mxchat_auto_delete_transcripts']) ? $transcript_options['mxchat_auto_delete_transcripts'] : 'never';
704
705 if ($cleanup_interval !== 'never') {
706 // Check if not already scheduled
707 if (!wp_next_scheduled('mxchat_cleanup_old_transcripts')) {
708 // Schedule to run daily at 3 AM
709 $next_run = strtotime('tomorrow 3:00 AM');
710 wp_schedule_event($next_run, 'daily', 'mxchat_cleanup_old_transcripts');
711 }
712 }
713 }
714
715 /**
716 * Clean up on plugin deactivation
717 */
718 function mxchat_deactivate() {
719 // Clear scheduled cron jobs
720 wp_clear_scheduled_hook('mxchat_reset_rate_limits');
721 wp_clear_scheduled_hook('mxchat_cleanup_old_transcripts');
722 wp_clear_scheduled_hook('mxchat_send_delayed_transcript');
723
724 // Clear fallback options
725 delete_option('mxchat_use_fallback_rate_limits');
726 delete_option('mxchat_next_rate_limit_check');
727 delete_option('mxchat_fallback_check_interval');
728
729 // NOTE: We do NOT delete queue tables on deactivation
730 // This preserves data if user accidentally deactivates the plugin
731 }
732
733 /**
734 * Check if fallback rate limit cleanup is needed
735 */
736 function mxchat_check_fallback_rate_limits() {
737 $use_fallback = get_option('mxchat_use_fallback_rate_limits', false);
738
739 if (!$use_fallback) {
740 return;
741 }
742
743 $next_check = get_option('mxchat_next_rate_limit_check', 0);
744
745 if (time() >= $next_check) {
746 // Only run reset if the MxChat_Integrator class exists
747 if (class_exists('MxChat_Integrator')) {
748 $integrator = new MxChat_Integrator();
749 if (method_exists($integrator, 'mxchat_reset_rate_limits')) {
750 $integrator->mxchat_reset_rate_limits();
751 update_option('mxchat_next_rate_limit_check', time() + 3600);
752 }
753 }
754 }
755 }
756
757 /**
758 * Robust update checking with role restriction migration, model deprecation, and queue tables
759 * CRITICAL: This runs on EVERY page load to ensure tables exist
760 */
761 function mxchat_check_for_update() {
762 global $wpdb;
763
764 try {
765 $current_version = get_option('mxchat_plugin_version', '0.0.0');
766 $plugin_version = MXCHAT_VERSION;
767
768 // Always ensure critical tables exist (even if version matches)
769 // This handles manual table deletion or fresh installs
770 $chat_table = $wpdb->prefix . 'mxchat_chat_transcripts';
771 $queue_table = $wpdb->prefix . 'mxchat_processing_queue';
772
773 $chat_exists = $wpdb->get_var("SHOW TABLES LIKE '$chat_table'") === $chat_table;
774 $queue_exists = $wpdb->get_var("SHOW TABLES LIKE '$queue_table'") === $queue_table;
775
776 if (!$chat_exists || !$queue_exists) {
777 //error_log("MxChat: Critical tables missing, running activation");
778 mxchat_activate();
779 }
780
781 // Version-specific migrations
782 if ($current_version !== $plugin_version) {
783 //error_log("MxChat: Version change detected: $current_version -> $plugin_version");
784
785 // Run live agent update BEFORE updating the stored version
786 mxchat_handle_live_agent_update();
787
788 // Run theme migration notice for 3.0.1 (AI theme CSS structure changes)
789 mxchat_handle_theme_migration_notice();
790
791 // Run role restriction migration for 2.4.1
792 if (version_compare($current_version, '2.4.1', '<')) {
793 mxchat_add_role_restriction_column();
794 }
795
796 // Run enabled_bots column migration for 2.4.4
797 if (version_compare($current_version, '2.4.4', '<')) {
798 mxchat_add_enabled_bots_column();
799 }
800
801 // Run model migration for 2.5.1 (Claude deprecation)
802 if (version_compare($current_version, '2.5.1', '<')) {
803 mxchat_migrate_deprecated_models();
804 }
805
806 // 2.5.2: Ensure queue tables exist and fix URL column sizes for all users upgrading to 2.5.2
807 if (version_compare($current_version, '2.5.2', '<')) {
808 mxchat_create_queue_tables();
809 mxchat_fix_url_column_size(); // NEW: Fix URL column size for long URLs
810 //error_log("MxChat: Queue tables created and URL columns updated for upgrade to 2.5.2");
811 }
812
813 // 2.6.0: Ensure rag_context column exists for retrieved documents feature
814 if (version_compare($current_version, '2.6.0', '<')) {
815 $chat_table = $wpdb->prefix . 'mxchat_chat_transcripts';
816 mxchat_ensure_all_columns($chat_table);
817 //error_log("MxChat: rag_context column migration for 2.6.0");
818 }
819
820 // 3.0.5: Migrate deprecated Gemini embedding model
821 if (version_compare($current_version, '3.0.5', '<')) {
822 mxchat_migrate_gemini_embedding_model();
823 }
824
825 // 3.0.6: Migrate deprecated OpenAI and Claude models
826 if (version_compare($current_version, '3.0.6', '<')) {
827 mxchat_migrate_deprecated_models();
828 }
829
830 // Run full activation to ensure everything is up to date
831 mxchat_activate();
832
833 // Run migration functions
834 mxchat_migrate_live_agent_status();
835
836 // Add the cleanup function for version 2.1.8
837 if (version_compare($current_version, '2.1.8', '<')) {
838 $deleted = mxchat_cleanup_orphaned_chat_history();
839 }
840
841 // Update version LAST
842 update_option('mxchat_plugin_version', $plugin_version);
843
844 //error_log("MxChat: Updated from version $current_version to $plugin_version");
845 }
846
847 } catch (Exception $e) {
848 //error_log('MxChat update error: ' . $e->getMessage());
849 // Don't update version if there was an error
850 }
851 }
852
853 /**
854 * Ensure tables exist on every admin load for fresh installations
855 * This is a safety net for cases where activation hook doesn't fire
856 */
857 function mxchat_ensure_tables_exist() {
858 global $wpdb;
859
860 // Only run for admin users to avoid performance impact
861 if (!current_user_can('administrator')) {
862 return;
863 }
864
865 // Check if we've already verified tables in this session
866 static $tables_checked = false;
867 if ($tables_checked) {
868 return;
869 }
870 $tables_checked = true;
871
872 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
873 $queue_table = $wpdb->prefix . 'mxchat_processing_queue';
874
875 $chat_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;
876 $queue_exists = $wpdb->get_var("SHOW TABLES LIKE '$queue_table'") === $queue_table;
877
878 if (!$chat_exists || !$queue_exists) {
879 //error_log("MxChat: Tables missing on admin load, running activation");
880 mxchat_activate();
881 }
882 }
883
884 /**
885 * Clean up orphaned chat history options from the wp_options table
886 * @return int Number of options deleted
887 */
888 function mxchat_cleanup_orphaned_chat_history() {
889 global $wpdb;
890 $count = 0;
891
892 // Get all option keys that match our pattern
893 $history_options = $wpdb->get_results(
894 "SELECT option_name FROM {$wpdb->options}
895 WHERE option_name LIKE 'mxchat_history_%'"
896 );
897
898 if (!empty($history_options)) {
899 foreach ($history_options as $option) {
900 // Extract the session ID from the option name
901 $session_id = str_replace('mxchat_history_', '', $option->option_name);
902
903 // Check if this session still exists in the custom table
904 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
905 $exists = $wpdb->get_var(
906 $wpdb->prepare(
907 "SELECT COUNT(*) FROM {$table_name} WHERE session_id = %s",
908 $session_id
909 )
910 );
911
912 // If session doesn't exist in the main table, delete the option
913 if ($exists == 0) {
914 delete_option($option->option_name);
915 // Also delete related metadata
916 delete_option("mxchat_email_{$session_id}");
917 delete_option("mxchat_name_{$session_id}");
918 delete_option("mxchat_agent_name_{$session_id}");
919 $count++;
920 }
921 }
922 }
923
924 return $count;
925 }
926
927 function mxchat_migrate_live_agent_status() {
928 $options = get_option('mxchat_options', []);
929
930 // Check if live_agent_status exists
931 if (isset($options['live_agent_status'])) {
932 $current_status = $options['live_agent_status'];
933 $needs_update = false;
934
935 // Convert to new format if needed
936 if ($current_status === 'online') {
937 $options['live_agent_status'] = 'on';
938 $needs_update = true;
939 } else if ($current_status === 'offline') {
940 $options['live_agent_status'] = 'off';
941 $needs_update = true;
942 } else if (!in_array($current_status, ['on', 'off'])) {
943 // Default to off for any unexpected values
944 $options['live_agent_status'] = 'off';
945 $needs_update = true;
946 }
947
948 // Only update if needed
949 if ($needs_update) {
950 update_option('mxchat_options', $options);
951 }
952 } else {
953 // If status doesn't exist, set default to off
954 $options['live_agent_status'] = 'off';
955 update_option('mxchat_options', $options);
956 }
957 }
958
959 function mxchat_handle_live_agent_update() {
960 // Get the CURRENT stored version (before it gets updated)
961 $current_version = get_option('mxchat_plugin_version', '0.0.0');
962 $new_version = '2.2.2';
963
964 // Only run this once for the update to 2.2.2
965 $update_handled = get_option('mxchat_live_agent_update_2_2_2_handled', false);
966
967 // Check if we're upgrading TO 2.2.2 and haven't handled this yet
968 if (version_compare($current_version, $new_version, '<') && !$update_handled) {
969 $options = get_option('mxchat_options', array());
970
971 // Check if live agent was previously enabled
972 if (isset($options['live_agent_status']) && $options['live_agent_status'] === 'on') {
973 // Disable live agent
974 $options['live_agent_status'] = 'off';
975 update_option('mxchat_options', $options);
976
977 // Set flag to show the notification banner
978 update_option('mxchat_show_live_agent_disabled_notice', true);
979 }
980
981 // Mark this update as handled
982 update_option('mxchat_live_agent_update_2_2_2_handled', true);
983 }
984 }
985
986 /**
987 * Handle theme migration notice for version 3.0.1
988 * Shows a dismissible notice to Pro users about migrating AI-generated themes
989 */
990 function mxchat_handle_theme_migration_notice() {
991 // Get the CURRENT stored version (before it gets updated)
992 $current_version = get_option('mxchat_plugin_version', '0.0.0');
993 $target_version = '3.0.1';
994
995 // Only run this once for the update to 3.0.1
996 $update_handled = get_option('mxchat_theme_migration_update_3_0_1_handled', false);
997
998 // Check if we're upgrading TO 3.0.1 and haven't handled this yet
999 if (version_compare($current_version, $target_version, '<') && !$update_handled) {
1000 // Check if Pro is activated - only show to Pro users
1001 $license_status = get_option('mxchat_license_status', 'inactive');
1002 $is_pro = ($license_status === 'active' || $license_status === esc_html__('active', 'mxchat'));
1003
1004 if ($is_pro) {
1005 // Set flag to show the theme migration notification banner
1006 update_option('mxchat_show_theme_migration_notice', true);
1007 }
1008
1009 // Mark this update as handled (whether Pro or not)
1010 update_option('mxchat_theme_migration_update_3_0_1_handled', true);
1011 }
1012 }
1013
1014 // Initialize plugin safely
1015 function mxchat_init() {
1016 // Include all class files first
1017 mxchat_include_classes();
1018
1019 // Run update check (this also ensures tables exist)
1020 mxchat_check_for_update();
1021
1022 // CRITICAL: Ensure tables exist on admin pages (safety net)
1023 add_action('admin_init', 'mxchat_ensure_tables_exist', 1);
1024
1025 // Add fallback rate limit check
1026 add_action('init', 'mxchat_check_fallback_rate_limits', 5);
1027
1028 // Add migration notice hook
1029 add_action('admin_notices', 'mxchat_show_migration_notice');
1030
1031 // Initialize classes with error handling
1032 try {
1033 // Initialize admin classes
1034 if (is_admin()) {
1035 if (class_exists('MxChat_Knowledge_Manager')) {
1036 $mxchat_knowledge_manager = new MxChat_Knowledge_Manager();
1037
1038 if (class_exists('MxChat_Admin')) {
1039 $mxchat_admin = new MxChat_Admin($mxchat_knowledge_manager);
1040 }
1041 }
1042
1043 // Initialize meta box class
1044 if (class_exists('MxChat_Meta_Box')) {
1045 new MxChat_Meta_Box();
1046 }
1047 }
1048
1049 // Initialize public classes
1050 if (class_exists('MxChat_Public')) {
1051 $mxchat_public = new MxChat_Public();
1052 }
1053
1054 if (class_exists('MxChat_Integrator')) {
1055 global $mxchat_integrator;
1056 $mxchat_integrator = new MxChat_Integrator();
1057 }
1058
1059 } catch (Exception $e) {
1060 //error_log('MxChat initialization error: ' . $e->getMessage());
1061
1062 // Show admin notice if there's an error
1063 if (is_admin()) {
1064 add_action('admin_notices', function() use ($e) {
1065 echo '<div class="notice notice-error"><p>';
1066 echo '<strong>MxChat Error:</strong> Plugin initialization failed. ';
1067 echo 'Please check error logs or contact support. Error: ' . esc_html($e->getMessage());
1068 echo '</p></div>';
1069 });
1070 }
1071 }
1072 }
1073
1074 // Run initialization on plugins_loaded
1075 add_action('plugins_loaded', 'mxchat_init');
1076
1077 // Run migration check on admin init (for auto-updates without reactivation)
1078 add_action('admin_init', 'mxchat_check_and_run_migrations');
1079
1080 /**
1081 * Check and run migrations on admin init
1082 * This ensures migrations run even when plugin is auto-updated
1083 */
1084 function mxchat_check_and_run_migrations() {
1085 // Only run in admin and not on every request
1086 static $checked = false;
1087 if ($checked) {
1088 return;
1089 }
1090 $checked = true;
1091
1092 mxchat_migrate_pinecone_roles_add_bot_id();
1093 mxchat_migrate_add_content_type_column();
1094 mxchat_migrate_add_translations_table();
1095 }
1096
1097 /**
1098 * Migration: Create transcript translations table (v3.0.4)
1099 * For users upgrading from versions before 3.0.4
1100 */
1101 function mxchat_migrate_add_translations_table() {
1102 $migration_key = 'mxchat_translations_table_created';
1103
1104 // Check if migration already ran
1105 if (get_option($migration_key)) {
1106 return;
1107 }
1108
1109 // Create the translations table
1110 mxchat_create_translations_table();
1111
1112 // Mark migration as complete
1113 update_option($migration_key, '3.0.4');
1114 }
1115
1116 /**
1117 * Migration: Update deprecated Gemini embedding model (v3.0.5)
1118 * Updates gemini-embedding-exp-03-07 to gemini-embedding-001 for users who had it selected
1119 */
1120 function mxchat_migrate_gemini_embedding_model() {
1121 $options = get_option('mxchat_options', array());
1122
1123 if (isset($options['embedding_model']) && $options['embedding_model'] === 'gemini-embedding-exp-03-07') {
1124 $options['embedding_model'] = 'gemini-embedding-001';
1125 update_option('mxchat_options', $options);
1126 }
1127 }
1128
1129 // Register activation hook
1130 register_activation_hook(__FILE__, 'mxchat_activate');
1131
1132 // Add cron schedule
1133 add_filter('cron_schedules', function($schedules) {
1134 $schedules['one_minute'] = array(
1135 'interval' => 60,
1136 'display' => 'Every Minute'
1137 );
1138 return $schedules;
1139 });
1140
1141 // Register deactivation hook
1142 register_deactivation_hook(__FILE__, 'mxchat_deactivate');