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

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

1,411 lines 50.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: MxChat
4 * Plugin URI: https://mxchat.ai/
5 * Description: AI chatbot for WordPress with OpenAI, Claude, xAI, DeepSeek, live agent, PDF uploads, WooCommerce, and training on website data.
6 * Version: 3.2.0
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.',
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.';
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.',
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.',
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 // Individual Intent Phrases Table - each phrase gets its own embedding vector
842 $intent_phrases_table = $wpdb->prefix . 'mxchat_intent_phrases';
843 $sql_intent_phrases_table = "CREATE TABLE $intent_phrases_table (
844 id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
845 intent_id BIGINT(20) UNSIGNED NOT NULL,
846 phrase TEXT NOT NULL,
847 embedding_vector LONGTEXT NOT NULL,
848 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
849 PRIMARY KEY (id),
850 KEY intent_id (intent_id)
851 ) $charset_collate;";
852
853 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
854
855 // Create other tables
856 dbDelta($sql_system_prompt);
857 dbDelta($sql_intents_table);
858 dbDelta($sql_intent_phrases_table);
859
860 // Create URL click tracking table
861 mxchat_create_url_clicks_table();
862
863 // Create Pinecone roles table
864 mxchat_create_pinecone_roles_table();
865
866 // NEW 2.5.2: Create queue processing tables
867 mxchat_create_queue_tables();
868
869 // Create transcript translations table
870 mxchat_create_translations_table();
871
872 // Ensure additional columns in system prompt table
873 $existing_system_columns = $wpdb->get_results("SHOW COLUMNS FROM $system_prompt_table");
874 if (!empty($existing_system_columns)) {
875 $existing_system_column_names = array_column($existing_system_columns, 'Field');
876
877 if (!in_array('embedding_vector', $existing_system_column_names)) {
878 $wpdb->query("ALTER TABLE $system_prompt_table ADD COLUMN embedding_vector LONGTEXT");
879 }
880 if (!in_array('source_url', $existing_system_column_names)) {
881 $wpdb->query("ALTER TABLE $system_prompt_table ADD COLUMN source_url TEXT DEFAULT NULL");
882 }
883 if (!in_array('role_restriction', $existing_system_column_names)) {
884 $wpdb->query("ALTER TABLE $system_prompt_table ADD COLUMN role_restriction VARCHAR(50) DEFAULT 'public' AFTER source_url");
885 }
886 }
887
888 // Set default thresholds for existing intents
889 $wpdb->query("UPDATE {$intents_table} SET similarity_threshold = 0.85 WHERE similarity_threshold IS NULL");
890
891 // Ensure enabled column exists in intents table
892 $existing_intent_columns = $wpdb->get_results("SHOW COLUMNS FROM $intents_table");
893 if (!empty($existing_intent_columns)) {
894 $existing_intent_column_names = array_column($existing_intent_columns, 'Field');
895
896 if (!in_array('enabled', $existing_intent_column_names)) {
897 $wpdb->query("ALTER TABLE $intents_table ADD COLUMN enabled TINYINT(1) NOT NULL DEFAULT 1");
898 }
899
900 // Ensure enabled_bots column exists for existing installations
901 if (!in_array('enabled_bots', $existing_intent_column_names)) {
902 $wpdb->query("ALTER TABLE $intents_table ADD COLUMN enabled_bots LONGTEXT DEFAULT NULL AFTER enabled");
903
904 // Set existing actions to work with default bot
905 $default_bots = json_encode(['default']);
906 $wpdb->query($wpdb->prepare(
907 "UPDATE {$intents_table} SET enabled_bots = %s WHERE enabled_bots IS NULL",
908 $default_bots
909 ));
910 }
911 }
912
913 // Run migration for existing installations
914 mxchat_migrate_pinecone_roles_add_bot_id();
915
916 // Setup cron jobs
917 mxchat_setup_cron_jobs();
918
919 // Update version
920 update_option('mxchat_plugin_version', MXCHAT_VERSION);
921
922 //error_log("MxChat: Activation function completed");
923 }
924
925 /**
926 * Setup cron jobs on plugin activation
927 */
928 function mxchat_setup_cron_jobs() {
929 // Clear any existing cron jobs first
930 wp_clear_scheduled_hook('mxchat_reset_rate_limits');
931
932 // Check if WordPress cron is disabled
933 if (defined('DISABLE_WP_CRON') && DISABLE_WP_CRON) {
934 // Set flag to use fallback system
935 update_option('mxchat_use_fallback_rate_limits', true);
936 update_option('mxchat_next_rate_limit_check', time() + 3600);
937 return;
938 }
939
940 // Schedule the rate limit reset cron job
941 $result = wp_schedule_event(time() + 300, 'hourly', 'mxchat_reset_rate_limits');
942
943 if ($result === false) {
944 // Fallback if scheduling fails
945 update_option('mxchat_use_fallback_rate_limits', true);
946 update_option('mxchat_next_rate_limit_check', time() + 3600);
947 } else {
948 // Clear fallback flags if cron scheduling succeeded
949 delete_option('mxchat_use_fallback_rate_limits');
950 }
951
952 // Schedule transcript cleanup if configured
953 $transcript_options = get_option('mxchat_transcripts_options', array());
954 $cleanup_interval = isset($transcript_options['mxchat_auto_delete_transcripts']) ? $transcript_options['mxchat_auto_delete_transcripts'] : 'never';
955
956 if ($cleanup_interval !== 'never') {
957 // Check if not already scheduled
958 if (!wp_next_scheduled('mxchat_cleanup_old_transcripts')) {
959 // Schedule to run daily at 3 AM
960 $next_run = strtotime('tomorrow 3:00 AM');
961 wp_schedule_event($next_run, 'daily', 'mxchat_cleanup_old_transcripts');
962 }
963 }
964 }
965
966 /**
967 * Clean up on plugin deactivation
968 */
969 function mxchat_deactivate() {
970 // Clear scheduled cron jobs
971 wp_clear_scheduled_hook('mxchat_reset_rate_limits');
972 wp_clear_scheduled_hook('mxchat_cleanup_old_transcripts');
973 wp_clear_scheduled_hook('mxchat_send_delayed_transcript');
974
975 // Clear fallback options
976 delete_option('mxchat_use_fallback_rate_limits');
977 delete_option('mxchat_next_rate_limit_check');
978 delete_option('mxchat_fallback_check_interval');
979
980 // NOTE: We do NOT delete queue tables on deactivation
981 // This preserves data if user accidentally deactivates the plugin
982 }
983
984 /**
985 * Check if fallback rate limit cleanup is needed
986 */
987 function mxchat_check_fallback_rate_limits() {
988 $use_fallback = get_option('mxchat_use_fallback_rate_limits', false);
989
990 if (!$use_fallback) {
991 return;
992 }
993
994 $next_check = get_option('mxchat_next_rate_limit_check', 0);
995
996 if (time() >= $next_check) {
997 // Only run reset if the MxChat_Integrator class exists
998 if (class_exists('MxChat_Integrator')) {
999 $integrator = new MxChat_Integrator();
1000 if (method_exists($integrator, 'mxchat_reset_rate_limits')) {
1001 $integrator->mxchat_reset_rate_limits();
1002 update_option('mxchat_next_rate_limit_check', time() + 3600);
1003 }
1004 }
1005 }
1006 }
1007
1008 /**
1009 * Robust update checking with role restriction migration, model deprecation, and queue tables
1010 * CRITICAL: This runs on EVERY page load to ensure tables exist
1011 */
1012 function mxchat_check_for_update() {
1013 global $wpdb;
1014
1015 try {
1016 $current_version = get_option('mxchat_plugin_version', '0.0.0');
1017 $plugin_version = MXCHAT_VERSION;
1018
1019 // Always ensure critical tables exist (even if version matches)
1020 // This handles manual table deletion or fresh installs
1021 $chat_table = $wpdb->prefix . 'mxchat_chat_transcripts';
1022 $queue_table = $wpdb->prefix . 'mxchat_processing_queue';
1023
1024 $chat_exists = $wpdb->get_var("SHOW TABLES LIKE '$chat_table'") === $chat_table;
1025 $queue_exists = $wpdb->get_var("SHOW TABLES LIKE '$queue_table'") === $queue_table;
1026
1027 if (!$chat_exists || !$queue_exists) {
1028 //error_log("MxChat: Critical tables missing, running activation");
1029 mxchat_activate();
1030 }
1031
1032 // Version-specific migrations
1033 if ($current_version !== $plugin_version) {
1034 //error_log("MxChat: Version change detected: $current_version -> $plugin_version");
1035
1036 // Run live agent update BEFORE updating the stored version
1037 mxchat_handle_live_agent_update();
1038
1039 // Run theme migration notice for 3.0.1 (AI theme CSS structure changes)
1040 mxchat_handle_theme_migration_notice();
1041
1042 // Run role restriction migration for 2.4.1
1043 if (version_compare($current_version, '2.4.1', '<')) {
1044 mxchat_add_role_restriction_column();
1045 }
1046
1047 // Run enabled_bots column migration for 2.4.4
1048 if (version_compare($current_version, '2.4.4', '<')) {
1049 mxchat_add_enabled_bots_column();
1050 }
1051
1052 // Run model migration for 2.5.1 (Claude deprecation)
1053 if (version_compare($current_version, '2.5.1', '<')) {
1054 mxchat_migrate_deprecated_models();
1055 }
1056
1057 // 2.5.2: Ensure queue tables exist and fix URL column sizes for all users upgrading to 2.5.2
1058 if (version_compare($current_version, '2.5.2', '<')) {
1059 mxchat_create_queue_tables();
1060 mxchat_fix_url_column_size(); // NEW: Fix URL column size for long URLs
1061 //error_log("MxChat: Queue tables created and URL columns updated for upgrade to 2.5.2");
1062 }
1063
1064 // 2.6.0: Ensure rag_context column exists for retrieved documents feature
1065 if (version_compare($current_version, '2.6.0', '<')) {
1066 $chat_table = $wpdb->prefix . 'mxchat_chat_transcripts';
1067 mxchat_ensure_all_columns($chat_table);
1068 //error_log("MxChat: rag_context column migration for 2.6.0");
1069 }
1070
1071 // 3.0.5: Migrate deprecated Gemini embedding model
1072 if (version_compare($current_version, '3.0.5', '<')) {
1073 mxchat_migrate_gemini_embedding_model();
1074 }
1075
1076 // 3.0.6: Migrate deprecated OpenAI and Claude models
1077 if (version_compare($current_version, '3.0.6', '<')) {
1078 mxchat_migrate_deprecated_models();
1079 }
1080
1081 // 3.1.2: Convert chat transcripts table to utf8mb4 for emoji support
1082 if (version_compare($current_version, '3.1.2', '<')) {
1083 mxchat_migrate_transcripts_charset();
1084 }
1085
1086 // 3.1.7: Clean up stale shared session email/name entries
1087 if (version_compare($current_version, '3.1.7', '<')) {
1088 delete_option('mxchat_email_null');
1089 delete_option('mxchat_name_null');
1090 }
1091
1092 // Run full activation to ensure everything is up to date
1093 mxchat_activate();
1094
1095 // Run migration functions
1096 mxchat_migrate_live_agent_status();
1097
1098 // Add the cleanup function for version 2.1.8
1099 if (version_compare($current_version, '2.1.8', '<')) {
1100 $deleted = mxchat_cleanup_orphaned_chat_history();
1101 }
1102
1103 // Update version LAST
1104 update_option('mxchat_plugin_version', $plugin_version);
1105
1106 //error_log("MxChat: Updated from version $current_version to $plugin_version");
1107 }
1108
1109 } catch (Exception $e) {
1110 //error_log('MxChat update error: ' . $e->getMessage());
1111 // Don't update version if there was an error
1112 }
1113 }
1114
1115 /**
1116 * Ensure tables exist on every admin load for fresh installations
1117 * This is a safety net for cases where activation hook doesn't fire
1118 */
1119 function mxchat_ensure_tables_exist() {
1120 global $wpdb;
1121
1122 // Only run for admin users to avoid performance impact
1123 if (!current_user_can('administrator')) {
1124 return;
1125 }
1126
1127 // Check if we've already verified tables in this session
1128 static $tables_checked = false;
1129 if ($tables_checked) {
1130 return;
1131 }
1132 $tables_checked = true;
1133
1134 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1135 $queue_table = $wpdb->prefix . 'mxchat_processing_queue';
1136
1137 $chat_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;
1138 $queue_exists = $wpdb->get_var("SHOW TABLES LIKE '$queue_table'") === $queue_table;
1139
1140 if (!$chat_exists || !$queue_exists) {
1141 //error_log("MxChat: Tables missing on admin load, running activation");
1142 mxchat_activate();
1143 }
1144 }
1145
1146 /**
1147 * Clean up orphaned chat history options from the wp_options table
1148 * @return int Number of options deleted
1149 */
1150 function mxchat_cleanup_orphaned_chat_history() {
1151 global $wpdb;
1152 $count = 0;
1153
1154 // Get all option keys that match our pattern
1155 $history_options = $wpdb->get_results(
1156 "SELECT option_name FROM {$wpdb->options}
1157 WHERE option_name LIKE 'mxchat_history_%'"
1158 );
1159
1160 if (!empty($history_options)) {
1161 foreach ($history_options as $option) {
1162 // Extract the session ID from the option name
1163 $session_id = str_replace('mxchat_history_', '', $option->option_name);
1164
1165 // Check if this session still exists in the custom table
1166 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1167 $exists = $wpdb->get_var(
1168 $wpdb->prepare(
1169 "SELECT COUNT(*) FROM {$table_name} WHERE session_id = %s",
1170 $session_id
1171 )
1172 );
1173
1174 // If session doesn't exist in the main table, delete the option
1175 if ($exists == 0) {
1176 delete_option($option->option_name);
1177 // Also delete related metadata
1178 delete_option("mxchat_email_{$session_id}");
1179 delete_option("mxchat_name_{$session_id}");
1180 delete_option("mxchat_agent_name_{$session_id}");
1181 $count++;
1182 }
1183 }
1184 }
1185
1186 return $count;
1187 }
1188
1189 function mxchat_migrate_live_agent_status() {
1190 $options = get_option('mxchat_options', []);
1191
1192 // Check if live_agent_status exists
1193 if (isset($options['live_agent_status'])) {
1194 $current_status = $options['live_agent_status'];
1195 $needs_update = false;
1196
1197 // Convert to new format if needed
1198 if ($current_status === 'online') {
1199 $options['live_agent_status'] = 'on';
1200 $needs_update = true;
1201 } else if ($current_status === 'offline') {
1202 $options['live_agent_status'] = 'off';
1203 $needs_update = true;
1204 } else if (!in_array($current_status, ['on', 'off'])) {
1205 // Default to off for any unexpected values
1206 $options['live_agent_status'] = 'off';
1207 $needs_update = true;
1208 }
1209
1210 // Only update if needed
1211 if ($needs_update) {
1212 update_option('mxchat_options', $options);
1213 }
1214 } else {
1215 // If status doesn't exist, set default to off
1216 $options['live_agent_status'] = 'off';
1217 update_option('mxchat_options', $options);
1218 }
1219 }
1220
1221 function mxchat_handle_live_agent_update() {
1222 // Get the CURRENT stored version (before it gets updated)
1223 $current_version = get_option('mxchat_plugin_version', '0.0.0');
1224 $new_version = '2.2.2';
1225
1226 // Only run this once for the update to 2.2.2
1227 $update_handled = get_option('mxchat_live_agent_update_2_2_2_handled', false);
1228
1229 // Check if we're upgrading TO 2.2.2 and haven't handled this yet
1230 if (version_compare($current_version, $new_version, '<') && !$update_handled) {
1231 $options = get_option('mxchat_options', array());
1232
1233 // Check if live agent was previously enabled
1234 if (isset($options['live_agent_status']) && $options['live_agent_status'] === 'on') {
1235 // Disable live agent
1236 $options['live_agent_status'] = 'off';
1237 update_option('mxchat_options', $options);
1238
1239 // Set flag to show the notification banner
1240 update_option('mxchat_show_live_agent_disabled_notice', true);
1241 }
1242
1243 // Mark this update as handled
1244 update_option('mxchat_live_agent_update_2_2_2_handled', true);
1245 }
1246 }
1247
1248 /**
1249 * Handle theme migration notice for version 3.0.1
1250 * Shows a dismissible notice to Pro users about migrating AI-generated themes
1251 */
1252 function mxchat_handle_theme_migration_notice() {
1253 // Get the CURRENT stored version (before it gets updated)
1254 $current_version = get_option('mxchat_plugin_version', '0.0.0');
1255 $target_version = '3.0.1';
1256
1257 // Only run this once for the update to 3.0.1
1258 $update_handled = get_option('mxchat_theme_migration_update_3_0_1_handled', false);
1259
1260 // Check if we're upgrading TO 3.0.1 and haven't handled this yet
1261 if (version_compare($current_version, $target_version, '<') && !$update_handled) {
1262 // Check if Pro is activated - only show to Pro users
1263 $license_status = get_option('mxchat_license_status', 'inactive');
1264 $is_pro = ($license_status === 'active');
1265
1266 if ($is_pro) {
1267 // Set flag to show the theme migration notification banner
1268 update_option('mxchat_show_theme_migration_notice', true);
1269 }
1270
1271 // Mark this update as handled (whether Pro or not)
1272 update_option('mxchat_theme_migration_update_3_0_1_handled', true);
1273 }
1274 }
1275
1276 // Initialize plugin safely
1277 function mxchat_init() {
1278 // Include all class files first
1279 mxchat_include_classes();
1280
1281 // Run update check (this also ensures tables exist)
1282 mxchat_check_for_update();
1283
1284 // CRITICAL: Ensure tables exist on admin pages (safety net)
1285 add_action('admin_init', 'mxchat_ensure_tables_exist', 1);
1286
1287 // Add fallback rate limit check
1288 add_action('init', 'mxchat_check_fallback_rate_limits', 5);
1289
1290 // Add migration notice hook
1291 add_action('admin_notices', 'mxchat_show_migration_notice');
1292
1293 // Initialize classes with error handling
1294 try {
1295 // Initialize admin classes
1296 if (is_admin()) {
1297 if (class_exists('MxChat_Knowledge_Manager')) {
1298 $mxchat_knowledge_manager = new MxChat_Knowledge_Manager();
1299
1300 if (class_exists('MxChat_Admin')) {
1301 $mxchat_admin = new MxChat_Admin($mxchat_knowledge_manager);
1302 }
1303 }
1304
1305 // Initialize meta box class
1306 if (class_exists('MxChat_Meta_Box')) {
1307 new MxChat_Meta_Box();
1308 }
1309
1310 }
1311
1312 // Initialize content generator globally — it registers wp_head hook
1313 // for frontend CSS injection, plus wp_ajax_ hooks for admin.
1314 if (class_exists('MxChat_Content_Generator')) {
1315 new MxChat_Content_Generator();
1316 }
1317
1318 // Initialize public classes
1319 if (class_exists('MxChat_Public')) {
1320 $mxchat_public = new MxChat_Public();
1321 }
1322
1323 if (class_exists('MxChat_Integrator')) {
1324 global $mxchat_integrator;
1325 $mxchat_integrator = new MxChat_Integrator();
1326 }
1327
1328 } catch (Exception $e) {
1329 //error_log('MxChat initialization error: ' . $e->getMessage());
1330
1331 // Show admin notice if there's an error
1332 if (is_admin()) {
1333 add_action('admin_notices', function() use ($e) {
1334 echo '<div class="notice notice-error"><p>';
1335 echo '<strong>MxChat Error:</strong> Plugin initialization failed. ';
1336 echo 'Please check error logs or contact support. Error: ' . esc_html($e->getMessage());
1337 echo '</p></div>';
1338 });
1339 }
1340 }
1341 }
1342
1343 // Run initialization on plugins_loaded
1344 add_action('plugins_loaded', 'mxchat_init');
1345
1346 // Run migration check on admin init (for auto-updates without reactivation)
1347 add_action('admin_init', 'mxchat_check_and_run_migrations');
1348
1349 /**
1350 * Check and run migrations on admin init
1351 * This ensures migrations run even when plugin is auto-updated
1352 */
1353 function mxchat_check_and_run_migrations() {
1354 // Only run in admin and not on every request
1355 static $checked = false;
1356 if ($checked) {
1357 return;
1358 }
1359 $checked = true;
1360
1361 mxchat_migrate_pinecone_roles_add_bot_id();
1362 mxchat_migrate_add_content_type_column();
1363 mxchat_migrate_add_translations_table();
1364 }
1365
1366 /**
1367 * Migration: Create transcript translations table (v3.0.4)
1368 * For users upgrading from versions before 3.0.4
1369 */
1370 function mxchat_migrate_add_translations_table() {
1371 $migration_key = 'mxchat_translations_table_created';
1372
1373 // Check if migration already ran
1374 if (get_option($migration_key)) {
1375 return;
1376 }
1377
1378 // Create the translations table
1379 mxchat_create_translations_table();
1380
1381 // Mark migration as complete
1382 update_option($migration_key, '3.0.4');
1383 }
1384
1385 /**
1386 * Migration: Update deprecated Gemini embedding model (v3.0.5)
1387 * Updates gemini-embedding-exp-03-07 to gemini-embedding-001 for users who had it selected
1388 */
1389 function mxchat_migrate_gemini_embedding_model() {
1390 $options = get_option('mxchat_options', array());
1391
1392 if (isset($options['embedding_model']) && $options['embedding_model'] === 'gemini-embedding-exp-03-07') {
1393 $options['embedding_model'] = 'gemini-embedding-001';
1394 update_option('mxchat_options', $options);
1395 }
1396 }
1397
1398 // Register activation hook
1399 register_activation_hook(__FILE__, 'mxchat_activate');
1400
1401 // Add cron schedule
1402 add_filter('cron_schedules', function($schedules) {
1403 $schedules['one_minute'] = array(
1404 'interval' => 60,
1405 'display' => 'Every Minute'
1406 );
1407 return $schedules;
1408 });
1409
1410 // Register deactivation hook
1411 register_deactivation_hook(__FILE__, 'mxchat_deactivate');