PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.1.7
MxChat – AI Chatbot & Content Generation for WordPress v3.1.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.1.7, at mxchat-basic.php

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