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

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