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

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