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

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