PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.1.5
MxChat – AI Chatbot & Content Generation for WordPress v3.1.5
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
mxchat-basic / mxchat-basic.php

mxchat-basic.php in MxChat – AI Chatbot & Content Generation for WordPress 3.1.5, at mxchat-basic.php

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