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

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