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

1,445 lines 52.0 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.2.4
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 * 3.2.4: Backfill the active embedding model option for installs that already
613 * have KB content but no stamped model. The mismatch warning compares this
614 * against the user's currently selected model — no per-row column needed.
615 */
616 function mxchat_backfill_active_embedding_model() {
617 global $wpdb;
618
619 if (get_option('mxchat_active_embedding_model', '') !== '') {
620 return;
621 }
622
623 $kb_table = $wpdb->prefix . 'mxchat_system_prompt_content';
624 if ($wpdb->get_var("SHOW TABLES LIKE '{$kb_table}'") !== $kb_table) {
625 return;
626 }
627
628 $kb_count = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$kb_table}");
629 if ($kb_count > 0) {
630 $options = get_option('mxchat_options', array());
631 $current_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
632 update_option('mxchat_active_embedding_model', $current_model, false);
633 }
634 }
635
636 /**
637 * 2.5.2: Create queue processing tables for reliable background processing
638 */
639 function mxchat_create_queue_tables() {
640 global $wpdb;
641 $charset_collate = $wpdb->get_charset_collate();
642
643 // Main queue table
644 $queue_table = $wpdb->prefix . 'mxchat_processing_queue';
645 $sql_queue = "CREATE TABLE $queue_table (
646 id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
647 queue_id varchar(64) NOT NULL,
648 item_type varchar(20) NOT NULL,
649 item_data longtext NOT NULL,
650 status varchar(20) NOT NULL DEFAULT 'pending',
651 bot_id varchar(50) NOT NULL DEFAULT 'default',
652 priority int(11) NOT NULL DEFAULT 0,
653 attempts int(11) NOT NULL DEFAULT 0,
654 max_attempts int(11) NOT NULL DEFAULT 3,
655 error_message text DEFAULT NULL,
656 created_at datetime NOT NULL,
657 started_at datetime DEFAULT NULL,
658 completed_at datetime DEFAULT NULL,
659 PRIMARY KEY (id),
660 KEY queue_id (queue_id),
661 KEY status (status),
662 KEY item_type (item_type),
663 KEY priority (priority)
664 ) $charset_collate;";
665
666 // Queue metadata table
667 $meta_table = $wpdb->prefix . 'mxchat_queue_meta';
668 $sql_meta = "CREATE TABLE $meta_table (
669 id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
670 queue_id varchar(64) NOT NULL,
671 meta_key varchar(255) NOT NULL,
672 meta_value longtext,
673 PRIMARY KEY (id),
674 KEY queue_id (queue_id),
675 KEY meta_key (meta_key)
676 ) $charset_collate;";
677
678 require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
679 dbDelta($sql_queue);
680 dbDelta($sql_meta);
681
682 //error_log("MxChat: Queue tables created/updated successfully");
683 }
684
685 /**
686 * Create transcript translations table for persisting translations
687 */
688 function mxchat_create_translations_table() {
689 global $wpdb;
690 $charset_collate = $wpdb->get_charset_collate();
691
692 $table_name = $wpdb->prefix . 'mxchat_transcript_translations';
693 $sql = "CREATE TABLE $table_name (
694 id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
695 session_id varchar(255) NOT NULL,
696 language_code varchar(10) NOT NULL,
697 translations longtext NOT NULL,
698 created_at datetime NOT NULL,
699 updated_at datetime NOT NULL,
700 PRIMARY KEY (id),
701 UNIQUE KEY session_lang (session_id, language_code),
702 KEY session_id (session_id)
703 ) $charset_collate;";
704
705 require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
706 dbDelta($sql);
707 }
708
709 /**
710 * 2.5.2: Fix URL column size to support long URLs (especially with UTF-8 encoding)
711 * This fixes "url, source_url. The supplied values may be too long" errors
712 */
713 function mxchat_fix_url_column_size() {
714 global $wpdb;
715 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
716
717 // Check if table exists
718 $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;
719 if (!$table_exists) {
720 return;
721 }
722
723 // Change url and source_url from VARCHAR to TEXT to handle long URLs
724 // This is especially important for URLs with UTF-8 encoded characters (Hebrew, Arabic, etc.)
725 $wpdb->query("ALTER TABLE {$table_name} MODIFY COLUMN url TEXT");
726 $wpdb->query("ALTER TABLE {$table_name} MODIFY COLUMN source_url TEXT");
727
728 //error_log("MxChat: Successfully updated url and source_url columns to TEXT type for long URL support");
729 }
730
731 /**
732 * Migrate deprecated AI models to their replacements
733 * Version 2.5.1: Migrate Claude 3.5 Sonnet (deprecated) to Claude 3.7 Sonnet
734 * Version 3.1.2: Convert chat transcripts table to utf8mb4 for emoji support
735 * Without utf8mb4, any bot response containing emojis silently fails to insert.
736 */
737 function mxchat_migrate_transcripts_charset() {
738 global $wpdb;
739 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
740 $wpdb->query("ALTER TABLE $table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
741 }
742
743 /**
744 * Version 3.0.55: Migrate GPT-4 series models (deprecated 2026-02-17) to GPT-5 series
745 */
746 function mxchat_migrate_deprecated_models() {
747 $options = get_option('mxchat_options', array());
748 $migrated = false;
749 $migration_message = '';
750
751 if (!isset($options['model'])) {
752 return;
753 }
754
755 $current_model = $options['model'];
756
757 // Migrate deprecated Claude models to Claude Opus 4.6 (recommended replacement per Anthropic)
758 $deprecated_claude_models = array(
759 'claude-3-5-sonnet-20240620', // Retired Oct 28, 2025
760 'claude-3-5-sonnet-20241022', // Retired Oct 28, 2025
761 'claude-3-7-sonnet-20250219', // Retiring Feb 19, 2026
762 'claude-3-opus-20240229', // Retired Jan 5, 2026
763 'claude-3-sonnet-20240229', // Legacy
764 'claude-3-haiku-20240307', // Legacy
765 );
766 if (in_array($current_model, $deprecated_claude_models, true)) {
767 $options['model'] = 'claude-opus-4-6';
768 $migrated = true;
769 $migration_message = sprintf(
770 'Your chatbot model has been automatically updated from %s to Claude Opus 4.6 due to Anthropic deprecating older Claude models.',
771 $current_model
772 );
773 }
774
775 // Migrate deprecated Claude Haiku 3.5 to Claude Haiku 4.5
776 if ($current_model === 'claude-3-5-haiku-20241022') {
777 $options['model'] = 'claude-haiku-4-5-20251001';
778 $migrated = true;
779 $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.';
780 }
781
782 // Migrate deprecated GPT-4 series and GPT-3.5 Turbo to GPT-5.1 Chat Latest
783 if (in_array($current_model, array('gpt-4o', 'gpt-4.1-2025-04-14', 'gpt-4-turbo', 'gpt-4', 'gpt-3.5-turbo'), true)) {
784 $options['model'] = 'gpt-5.1-chat-latest';
785 $migrated = true;
786 $migration_message = sprintf(
787 'Your chatbot model has been automatically updated from %s to GPT-5.1 Chat Latest due to OpenAI deprecating older models.',
788 $current_model
789 );
790 }
791
792 // Migrate deprecated GPT-4o Mini and GPT-4.1 Mini to GPT-5 Mini
793 if (in_array($current_model, array('gpt-4o-mini', 'gpt-4.1-mini'), true)) {
794 $options['model'] = 'gpt-5-mini';
795 $migrated = true;
796 $migration_message = sprintf(
797 'Your chatbot model has been automatically updated from %s to GPT-5 Mini due to OpenAI deprecating GPT-4 series models.',
798 $current_model
799 );
800 }
801
802 if ($migrated) {
803 update_option('mxchat_options', $options);
804 update_option('mxchat_model_migrated_notice', true);
805 update_option('mxchat_model_migration_message', $migration_message);
806 }
807 }
808
809 /**
810 * Show admin notice after model migration
811 */
812 function mxchat_show_migration_notice() {
813 if (get_option('mxchat_model_migrated_notice')) {
814 $migration_message = get_option('mxchat_model_migration_message', __('Your chatbot model has been automatically updated due to a model deprecation.', 'mxchat'));
815 ?>
816 <div class="notice notice-info is-dismissible">
817 <p>
818 <strong><?php esc_html_e('MxChat Model Updated', 'mxchat'); ?></strong><br>
819 <?php echo esc_html($migration_message); ?>
820 </p>
821 </div>
822 <?php
823 delete_option('mxchat_model_migrated_notice');
824 delete_option('mxchat_model_migration_message');
825 }
826 }
827
828 function mxchat_activate() {
829 global $wpdb;
830 $charset_collate = $wpdb->get_charset_collate();
831
832 //error_log("MxChat: Running activation function");
833
834 // Create chat transcripts table with improved function
835 mxchat_create_chat_transcripts_table();
836
837 // System Prompt Content Table - UPDATED: Use TEXT for url and source_url columns
838 $system_prompt_table = $wpdb->prefix . 'mxchat_system_prompt_content';
839 $sql_system_prompt = "CREATE TABLE $system_prompt_table (
840 id MEDIUMINT(9) NOT NULL AUTO_INCREMENT,
841 url TEXT NOT NULL,
842 article_content LONGTEXT NOT NULL,
843 embedding_vector LONGTEXT,
844 source_url TEXT DEFAULT NULL,
845 role_restriction VARCHAR(50) DEFAULT 'public',
846 content_type VARCHAR(50) DEFAULT 'content',
847 timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
848 PRIMARY KEY (id),
849 KEY content_type (content_type)
850 ) $charset_collate;";
851
852 // Intents Table - NOW INCLUDES enabled_bots column from the start
853 $intents_table = $wpdb->prefix . 'mxchat_intents';
854 $sql_intents_table = "CREATE TABLE $intents_table (
855 id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
856 intent_label VARCHAR(255) NOT NULL,
857 phrases TEXT NOT NULL,
858 embedding_vector LONGTEXT NOT NULL,
859 callback_function VARCHAR(255) NOT NULL,
860 similarity_threshold FLOAT DEFAULT 0.85,
861 enabled TINYINT(1) NOT NULL DEFAULT 1,
862 enabled_bots LONGTEXT DEFAULT NULL,
863 PRIMARY KEY (id)
864 ) $charset_collate;";
865
866 // Individual Intent Phrases Table - each phrase gets its own embedding vector
867 $intent_phrases_table = $wpdb->prefix . 'mxchat_intent_phrases';
868 $sql_intent_phrases_table = "CREATE TABLE $intent_phrases_table (
869 id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
870 intent_id BIGINT(20) UNSIGNED NOT NULL,
871 phrase TEXT NOT NULL,
872 embedding_vector LONGTEXT NOT NULL,
873 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
874 PRIMARY KEY (id),
875 KEY intent_id (intent_id)
876 ) $charset_collate;";
877
878 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
879
880 // Create other tables
881 dbDelta($sql_system_prompt);
882 dbDelta($sql_intents_table);
883 dbDelta($sql_intent_phrases_table);
884
885 // Create URL click tracking table
886 mxchat_create_url_clicks_table();
887
888 // Create Pinecone roles table
889 mxchat_create_pinecone_roles_table();
890
891 // NEW 2.5.2: Create queue processing tables
892 mxchat_create_queue_tables();
893
894 // Create transcript translations table
895 mxchat_create_translations_table();
896
897 // Ensure additional columns in system prompt table
898 $existing_system_columns = $wpdb->get_results("SHOW COLUMNS FROM $system_prompt_table");
899 if (!empty($existing_system_columns)) {
900 $existing_system_column_names = array_column($existing_system_columns, 'Field');
901
902 if (!in_array('embedding_vector', $existing_system_column_names)) {
903 $wpdb->query("ALTER TABLE $system_prompt_table ADD COLUMN embedding_vector LONGTEXT");
904 }
905 if (!in_array('source_url', $existing_system_column_names)) {
906 $wpdb->query("ALTER TABLE $system_prompt_table ADD COLUMN source_url TEXT DEFAULT NULL");
907 }
908 if (!in_array('role_restriction', $existing_system_column_names)) {
909 $wpdb->query("ALTER TABLE $system_prompt_table ADD COLUMN role_restriction VARCHAR(50) DEFAULT 'public' AFTER source_url");
910 }
911 }
912
913 // Set default thresholds for existing intents
914 $wpdb->query("UPDATE {$intents_table} SET similarity_threshold = 0.85 WHERE similarity_threshold IS NULL");
915
916 // Ensure enabled column exists in intents table
917 $existing_intent_columns = $wpdb->get_results("SHOW COLUMNS FROM $intents_table");
918 if (!empty($existing_intent_columns)) {
919 $existing_intent_column_names = array_column($existing_intent_columns, 'Field');
920
921 if (!in_array('enabled', $existing_intent_column_names)) {
922 $wpdb->query("ALTER TABLE $intents_table ADD COLUMN enabled TINYINT(1) NOT NULL DEFAULT 1");
923 }
924
925 // Ensure enabled_bots column exists for existing installations
926 if (!in_array('enabled_bots', $existing_intent_column_names)) {
927 $wpdb->query("ALTER TABLE $intents_table ADD COLUMN enabled_bots LONGTEXT DEFAULT NULL AFTER enabled");
928
929 // Set existing actions to work with default bot
930 $default_bots = json_encode(['default']);
931 $wpdb->query($wpdb->prepare(
932 "UPDATE {$intents_table} SET enabled_bots = %s WHERE enabled_bots IS NULL",
933 $default_bots
934 ));
935 }
936 }
937
938 // Run migration for existing installations
939 mxchat_migrate_pinecone_roles_add_bot_id();
940
941 // 3.2.4: Backfill active embedding model option (replaces 3.2.3 column-based tracking)
942 mxchat_backfill_active_embedding_model();
943
944 // Setup cron jobs
945 mxchat_setup_cron_jobs();
946
947 // Update version
948 update_option('mxchat_plugin_version', MXCHAT_VERSION);
949
950 //error_log("MxChat: Activation function completed");
951 }
952
953 /**
954 * Setup cron jobs on plugin activation
955 */
956 function mxchat_setup_cron_jobs() {
957 // Clear any existing cron jobs first
958 wp_clear_scheduled_hook('mxchat_reset_rate_limits');
959
960 // Check if WordPress cron is disabled
961 if (defined('DISABLE_WP_CRON') && DISABLE_WP_CRON) {
962 // Set flag to use fallback system
963 update_option('mxchat_use_fallback_rate_limits', true);
964 update_option('mxchat_next_rate_limit_check', time() + 3600);
965 return;
966 }
967
968 // Schedule the rate limit reset cron job
969 $result = wp_schedule_event(time() + 300, 'hourly', 'mxchat_reset_rate_limits');
970
971 if ($result === false) {
972 // Fallback if scheduling fails
973 update_option('mxchat_use_fallback_rate_limits', true);
974 update_option('mxchat_next_rate_limit_check', time() + 3600);
975 } else {
976 // Clear fallback flags if cron scheduling succeeded
977 delete_option('mxchat_use_fallback_rate_limits');
978 }
979
980 // Schedule transcript cleanup if configured
981 $transcript_options = get_option('mxchat_transcripts_options', array());
982 $cleanup_interval = isset($transcript_options['mxchat_auto_delete_transcripts']) ? $transcript_options['mxchat_auto_delete_transcripts'] : 'never';
983
984 if ($cleanup_interval !== 'never') {
985 // Check if not already scheduled
986 if (!wp_next_scheduled('mxchat_cleanup_old_transcripts')) {
987 // Schedule to run daily at 3 AM
988 $next_run = strtotime('tomorrow 3:00 AM');
989 wp_schedule_event($next_run, 'daily', 'mxchat_cleanup_old_transcripts');
990 }
991 }
992 }
993
994 /**
995 * Clean up on plugin deactivation
996 */
997 function mxchat_deactivate() {
998 // Clear scheduled cron jobs
999 wp_clear_scheduled_hook('mxchat_reset_rate_limits');
1000 wp_clear_scheduled_hook('mxchat_cleanup_old_transcripts');
1001 wp_clear_scheduled_hook('mxchat_send_delayed_transcript');
1002
1003 // Clear fallback options
1004 delete_option('mxchat_use_fallback_rate_limits');
1005 delete_option('mxchat_next_rate_limit_check');
1006 delete_option('mxchat_fallback_check_interval');
1007
1008 // NOTE: We do NOT delete queue tables on deactivation
1009 // This preserves data if user accidentally deactivates the plugin
1010 }
1011
1012 /**
1013 * Check if fallback rate limit cleanup is needed
1014 */
1015 function mxchat_check_fallback_rate_limits() {
1016 $use_fallback = get_option('mxchat_use_fallback_rate_limits', false);
1017
1018 if (!$use_fallback) {
1019 return;
1020 }
1021
1022 $next_check = get_option('mxchat_next_rate_limit_check', 0);
1023
1024 if (time() >= $next_check) {
1025 // Only run reset if the MxChat_Integrator class exists
1026 if (class_exists('MxChat_Integrator')) {
1027 $integrator = new MxChat_Integrator();
1028 if (method_exists($integrator, 'mxchat_reset_rate_limits')) {
1029 $integrator->mxchat_reset_rate_limits();
1030 update_option('mxchat_next_rate_limit_check', time() + 3600);
1031 }
1032 }
1033 }
1034 }
1035
1036 /**
1037 * Robust update checking with role restriction migration, model deprecation, and queue tables
1038 * CRITICAL: This runs on EVERY page load to ensure tables exist
1039 */
1040 function mxchat_check_for_update() {
1041 global $wpdb;
1042
1043 try {
1044 $current_version = get_option('mxchat_plugin_version', '0.0.0');
1045 $plugin_version = MXCHAT_VERSION;
1046
1047 // Always ensure critical tables exist (even if version matches)
1048 // This handles manual table deletion or fresh installs
1049 $chat_table = $wpdb->prefix . 'mxchat_chat_transcripts';
1050 $queue_table = $wpdb->prefix . 'mxchat_processing_queue';
1051
1052 $chat_exists = $wpdb->get_var("SHOW TABLES LIKE '$chat_table'") === $chat_table;
1053 $queue_exists = $wpdb->get_var("SHOW TABLES LIKE '$queue_table'") === $queue_table;
1054
1055 if (!$chat_exists || !$queue_exists) {
1056 //error_log("MxChat: Critical tables missing, running activation");
1057 mxchat_activate();
1058 }
1059
1060 // Version-specific migrations
1061 if ($current_version !== $plugin_version) {
1062 //error_log("MxChat: Version change detected: $current_version -> $plugin_version");
1063
1064 // Run live agent update BEFORE updating the stored version
1065 mxchat_handle_live_agent_update();
1066
1067 // Run theme migration notice for 3.0.1 (AI theme CSS structure changes)
1068 mxchat_handle_theme_migration_notice();
1069
1070 // Run role restriction migration for 2.4.1
1071 if (version_compare($current_version, '2.4.1', '<')) {
1072 mxchat_add_role_restriction_column();
1073 }
1074
1075 // Run enabled_bots column migration for 2.4.4
1076 if (version_compare($current_version, '2.4.4', '<')) {
1077 mxchat_add_enabled_bots_column();
1078 }
1079
1080 // Run model migration for 2.5.1 (Claude deprecation)
1081 if (version_compare($current_version, '2.5.1', '<')) {
1082 mxchat_migrate_deprecated_models();
1083 }
1084
1085 // 2.5.2: Ensure queue tables exist and fix URL column sizes for all users upgrading to 2.5.2
1086 if (version_compare($current_version, '2.5.2', '<')) {
1087 mxchat_create_queue_tables();
1088 mxchat_fix_url_column_size(); // NEW: Fix URL column size for long URLs
1089 //error_log("MxChat: Queue tables created and URL columns updated for upgrade to 2.5.2");
1090 }
1091
1092 // 2.6.0: Ensure rag_context column exists for retrieved documents feature
1093 if (version_compare($current_version, '2.6.0', '<')) {
1094 $chat_table = $wpdb->prefix . 'mxchat_chat_transcripts';
1095 mxchat_ensure_all_columns($chat_table);
1096 //error_log("MxChat: rag_context column migration for 2.6.0");
1097 }
1098
1099 // 3.0.5: Migrate deprecated Gemini embedding model
1100 if (version_compare($current_version, '3.0.5', '<')) {
1101 mxchat_migrate_gemini_embedding_model();
1102 }
1103
1104 // 3.0.6: Migrate deprecated OpenAI and Claude models
1105 if (version_compare($current_version, '3.0.6', '<')) {
1106 mxchat_migrate_deprecated_models();
1107 }
1108
1109 // 3.1.2: Convert chat transcripts table to utf8mb4 for emoji support
1110 if (version_compare($current_version, '3.1.2', '<')) {
1111 mxchat_migrate_transcripts_charset();
1112 }
1113
1114 // 3.1.7: Clean up stale shared session email/name entries
1115 if (version_compare($current_version, '3.1.7', '<')) {
1116 delete_option('mxchat_email_null');
1117 delete_option('mxchat_name_null');
1118 }
1119
1120 // 3.2.4: Backfill active embedding model option for the warning UI
1121 // (replaces the per-row column tracking from 3.2.3, which was reverted)
1122 if (version_compare($current_version, '3.2.4', '<')) {
1123 mxchat_backfill_active_embedding_model();
1124 }
1125
1126 // Run full activation to ensure everything is up to date
1127 mxchat_activate();
1128
1129 // Run migration functions
1130 mxchat_migrate_live_agent_status();
1131
1132 // Add the cleanup function for version 2.1.8
1133 if (version_compare($current_version, '2.1.8', '<')) {
1134 $deleted = mxchat_cleanup_orphaned_chat_history();
1135 }
1136
1137 // Update version LAST
1138 update_option('mxchat_plugin_version', $plugin_version);
1139
1140 //error_log("MxChat: Updated from version $current_version to $plugin_version");
1141 }
1142
1143 } catch (Exception $e) {
1144 //error_log('MxChat update error: ' . $e->getMessage());
1145 // Don't update version if there was an error
1146 }
1147 }
1148
1149 /**
1150 * Ensure tables exist on every admin load for fresh installations
1151 * This is a safety net for cases where activation hook doesn't fire
1152 */
1153 function mxchat_ensure_tables_exist() {
1154 global $wpdb;
1155
1156 // Only run for admin users to avoid performance impact
1157 if (!current_user_can('administrator')) {
1158 return;
1159 }
1160
1161 // Check if we've already verified tables in this session
1162 static $tables_checked = false;
1163 if ($tables_checked) {
1164 return;
1165 }
1166 $tables_checked = true;
1167
1168 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1169 $queue_table = $wpdb->prefix . 'mxchat_processing_queue';
1170
1171 $chat_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;
1172 $queue_exists = $wpdb->get_var("SHOW TABLES LIKE '$queue_table'") === $queue_table;
1173
1174 if (!$chat_exists || !$queue_exists) {
1175 //error_log("MxChat: Tables missing on admin load, running activation");
1176 mxchat_activate();
1177 }
1178 }
1179
1180 /**
1181 * Clean up orphaned chat history options from the wp_options table
1182 * @return int Number of options deleted
1183 */
1184 function mxchat_cleanup_orphaned_chat_history() {
1185 global $wpdb;
1186 $count = 0;
1187
1188 // Get all option keys that match our pattern
1189 $history_options = $wpdb->get_results(
1190 "SELECT option_name FROM {$wpdb->options}
1191 WHERE option_name LIKE 'mxchat_history_%'"
1192 );
1193
1194 if (!empty($history_options)) {
1195 foreach ($history_options as $option) {
1196 // Extract the session ID from the option name
1197 $session_id = str_replace('mxchat_history_', '', $option->option_name);
1198
1199 // Check if this session still exists in the custom table
1200 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1201 $exists = $wpdb->get_var(
1202 $wpdb->prepare(
1203 "SELECT COUNT(*) FROM {$table_name} WHERE session_id = %s",
1204 $session_id
1205 )
1206 );
1207
1208 // If session doesn't exist in the main table, delete the option
1209 if ($exists == 0) {
1210 delete_option($option->option_name);
1211 // Also delete related metadata
1212 delete_option("mxchat_email_{$session_id}");
1213 delete_option("mxchat_name_{$session_id}");
1214 delete_option("mxchat_agent_name_{$session_id}");
1215 $count++;
1216 }
1217 }
1218 }
1219
1220 return $count;
1221 }
1222
1223 function mxchat_migrate_live_agent_status() {
1224 $options = get_option('mxchat_options', []);
1225
1226 // Check if live_agent_status exists
1227 if (isset($options['live_agent_status'])) {
1228 $current_status = $options['live_agent_status'];
1229 $needs_update = false;
1230
1231 // Convert to new format if needed
1232 if ($current_status === 'online') {
1233 $options['live_agent_status'] = 'on';
1234 $needs_update = true;
1235 } else if ($current_status === 'offline') {
1236 $options['live_agent_status'] = 'off';
1237 $needs_update = true;
1238 } else if (!in_array($current_status, ['on', 'off'])) {
1239 // Default to off for any unexpected values
1240 $options['live_agent_status'] = 'off';
1241 $needs_update = true;
1242 }
1243
1244 // Only update if needed
1245 if ($needs_update) {
1246 update_option('mxchat_options', $options);
1247 }
1248 } else {
1249 // If status doesn't exist, set default to off
1250 $options['live_agent_status'] = 'off';
1251 update_option('mxchat_options', $options);
1252 }
1253 }
1254
1255 function mxchat_handle_live_agent_update() {
1256 // Get the CURRENT stored version (before it gets updated)
1257 $current_version = get_option('mxchat_plugin_version', '0.0.0');
1258 $new_version = '2.2.2';
1259
1260 // Only run this once for the update to 2.2.2
1261 $update_handled = get_option('mxchat_live_agent_update_2_2_2_handled', false);
1262
1263 // Check if we're upgrading TO 2.2.2 and haven't handled this yet
1264 if (version_compare($current_version, $new_version, '<') && !$update_handled) {
1265 $options = get_option('mxchat_options', array());
1266
1267 // Check if live agent was previously enabled
1268 if (isset($options['live_agent_status']) && $options['live_agent_status'] === 'on') {
1269 // Disable live agent
1270 $options['live_agent_status'] = 'off';
1271 update_option('mxchat_options', $options);
1272
1273 // Set flag to show the notification banner
1274 update_option('mxchat_show_live_agent_disabled_notice', true);
1275 }
1276
1277 // Mark this update as handled
1278 update_option('mxchat_live_agent_update_2_2_2_handled', true);
1279 }
1280 }
1281
1282 /**
1283 * Handle theme migration notice for version 3.0.1
1284 * Shows a dismissible notice to Pro users about migrating AI-generated themes
1285 */
1286 function mxchat_handle_theme_migration_notice() {
1287 // Get the CURRENT stored version (before it gets updated)
1288 $current_version = get_option('mxchat_plugin_version', '0.0.0');
1289 $target_version = '3.0.1';
1290
1291 // Only run this once for the update to 3.0.1
1292 $update_handled = get_option('mxchat_theme_migration_update_3_0_1_handled', false);
1293
1294 // Check if we're upgrading TO 3.0.1 and haven't handled this yet
1295 if (version_compare($current_version, $target_version, '<') && !$update_handled) {
1296 // Check if Pro is activated - only show to Pro users
1297 $license_status = get_option('mxchat_license_status', 'inactive');
1298 $is_pro = ($license_status === 'active');
1299
1300 if ($is_pro) {
1301 // Set flag to show the theme migration notification banner
1302 update_option('mxchat_show_theme_migration_notice', true);
1303 }
1304
1305 // Mark this update as handled (whether Pro or not)
1306 update_option('mxchat_theme_migration_update_3_0_1_handled', true);
1307 }
1308 }
1309
1310 // Initialize plugin safely
1311 function mxchat_init() {
1312 // Include all class files first
1313 mxchat_include_classes();
1314
1315 // Run update check (this also ensures tables exist)
1316 mxchat_check_for_update();
1317
1318 // CRITICAL: Ensure tables exist on admin pages (safety net)
1319 add_action('admin_init', 'mxchat_ensure_tables_exist', 1);
1320
1321 // Add fallback rate limit check
1322 add_action('init', 'mxchat_check_fallback_rate_limits', 5);
1323
1324 // Add migration notice hook
1325 add_action('admin_notices', 'mxchat_show_migration_notice');
1326
1327 // Initialize classes with error handling
1328 try {
1329 // Initialize admin classes
1330 if (is_admin()) {
1331 if (class_exists('MxChat_Knowledge_Manager')) {
1332 $mxchat_knowledge_manager = new MxChat_Knowledge_Manager();
1333
1334 if (class_exists('MxChat_Admin')) {
1335 $mxchat_admin = new MxChat_Admin($mxchat_knowledge_manager);
1336 }
1337 }
1338
1339 // Initialize meta box class
1340 if (class_exists('MxChat_Meta_Box')) {
1341 new MxChat_Meta_Box();
1342 }
1343
1344 }
1345
1346 // Initialize content generator globally — it registers wp_head hook
1347 // for frontend CSS injection, plus wp_ajax_ hooks for admin.
1348 if (class_exists('MxChat_Content_Generator')) {
1349 new MxChat_Content_Generator();
1350 }
1351
1352 // Initialize public classes
1353 if (class_exists('MxChat_Public')) {
1354 $mxchat_public = new MxChat_Public();
1355 }
1356
1357 if (class_exists('MxChat_Integrator')) {
1358 global $mxchat_integrator;
1359 $mxchat_integrator = new MxChat_Integrator();
1360 }
1361
1362 } catch (Exception $e) {
1363 //error_log('MxChat initialization error: ' . $e->getMessage());
1364
1365 // Show admin notice if there's an error
1366 if (is_admin()) {
1367 add_action('admin_notices', function() use ($e) {
1368 echo '<div class="notice notice-error"><p>';
1369 echo '<strong>MxChat Error:</strong> Plugin initialization failed. ';
1370 echo 'Please check error logs or contact support. Error: ' . esc_html($e->getMessage());
1371 echo '</p></div>';
1372 });
1373 }
1374 }
1375 }
1376
1377 // Run initialization on plugins_loaded
1378 add_action('plugins_loaded', 'mxchat_init');
1379
1380 // Run migration check on admin init (for auto-updates without reactivation)
1381 add_action('admin_init', 'mxchat_check_and_run_migrations');
1382
1383 /**
1384 * Check and run migrations on admin init
1385 * This ensures migrations run even when plugin is auto-updated
1386 */
1387 function mxchat_check_and_run_migrations() {
1388 // Only run in admin and not on every request
1389 static $checked = false;
1390 if ($checked) {
1391 return;
1392 }
1393 $checked = true;
1394
1395 mxchat_migrate_pinecone_roles_add_bot_id();
1396 mxchat_migrate_add_content_type_column();
1397 mxchat_migrate_add_translations_table();
1398 }
1399
1400 /**
1401 * Migration: Create transcript translations table (v3.0.4)
1402 * For users upgrading from versions before 3.0.4
1403 */
1404 function mxchat_migrate_add_translations_table() {
1405 $migration_key = 'mxchat_translations_table_created';
1406
1407 // Check if migration already ran
1408 if (get_option($migration_key)) {
1409 return;
1410 }
1411
1412 // Create the translations table
1413 mxchat_create_translations_table();
1414
1415 // Mark migration as complete
1416 update_option($migration_key, '3.0.4');
1417 }
1418
1419 /**
1420 * Migration: Update deprecated Gemini embedding model (v3.0.5)
1421 * Updates gemini-embedding-exp-03-07 to gemini-embedding-001 for users who had it selected
1422 */
1423 function mxchat_migrate_gemini_embedding_model() {
1424 $options = get_option('mxchat_options', array());
1425
1426 if (isset($options['embedding_model']) && $options['embedding_model'] === 'gemini-embedding-exp-03-07') {
1427 $options['embedding_model'] = 'gemini-embedding-001';
1428 update_option('mxchat_options', $options);
1429 }
1430 }
1431
1432 // Register activation hook
1433 register_activation_hook(__FILE__, 'mxchat_activate');
1434
1435 // Add cron schedule
1436 add_filter('cron_schedules', function($schedules) {
1437 $schedules['one_minute'] = array(
1438 'interval' => 60,
1439 'display' => 'Every Minute'
1440 );
1441 return $schedules;
1442 });
1443
1444 // Register deactivation hook
1445 register_deactivation_hook(__FILE__, 'mxchat_deactivate');