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

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