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

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