PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.2
MxChat – AI Chatbot & Content Generation for WordPress v3.2.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
← All changes | mxchat-basic.php +5 -335 3.2.133.2.2 View file →
@@ -2,9 +2,9 @@
2 2 /**
3 3 * Plugin Name: MxChat
4 4 * Plugin URI: https://mxchat.ai/
5 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.13
6 + * Version: 3.2.2
7 7 * Author: MxChat
8 8 * Author URI: https://mxchat.ai
9 9 * License: GPLv2 or later
10 10 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -29,27 +29,8 @@
29 29 }
30 30 define('MXCHAT_VERSION', $version);
31 31 }
32 32
33 -/**
34 - * Honest, versioned User-Agent for MXChat remote-content ingestion fetches
35 - * (Knowledge Base PDF import, URL import, sitemap / website crawl).
36 - *
37 - * WAF rulesets (SiteGround/ModSecurity, Wordfence, Cloudflare managed rules)
38 - * flag stale spoofed-browser UAs as scrapers and return 403 — which silently
39 - * broke the single most common KB source: self-hosted media on the site's own
40 - * domain. A truthful crawler identifier is the industry norm for well-behaved
41 - * bots and lets a site owner allowlist "MXChatBot" in their WAF. Filterable so
42 - * a locked-down host can supply a different string without a code change.
43 - */
44 -if (!function_exists('mxchat_ingest_user_agent')) {
45 - function mxchat_ingest_user_agent() {
46 - $version = defined('MXCHAT_VERSION') ? MXCHAT_VERSION : '1.0';
47 - $ua = 'MXChatBot/' . $version . ' (+https://mxchat.ai/bot)';
48 - return apply_filters('mxchat_ingest_user_agent', $ua);
49 - }
50 -}
51 -
52 33 function mxchat_load_textdomain() {
53 34 $domain = 'mxchat';
54 35 $locale = determine_locale();
55 36
@@ -65,84 +46,8 @@
65 46 }
66 47 add_action('init', 'mxchat_load_textdomain');
67 48
68 49 /**
69 - * One-time migration: gemini-3-pro-preview was shut down by Google on March 9, 2026.
70 - * Existing installs with the dead ID get auto-remapped to gemini-3.1-pro-preview
71 - * (Google's official migration target) the first time admin_init fires after update.
72 - */
73 -add_action('admin_init', function () {
74 - if (get_option('mxchat_gemini_3_remap_done')) {
75 - return;
76 - }
77 - $opts = get_option('mxchat_options');
78 - if (is_array($opts) && isset($opts['model']) && $opts['model'] === 'gemini-3-pro-preview') {
79 - $opts['model'] = 'gemini-3.1-pro-preview';
80 - update_option('mxchat_options', $opts);
81 - }
82 - if (is_array($opts) && isset($opts['content_model']) && $opts['content_model'] === 'gemini-3-pro-preview') {
83 - $opts['content_model'] = 'gemini-3.1-pro-preview';
84 - update_option('mxchat_options', $opts);
85 - }
86 - update_option('mxchat_gemini_3_remap_done', 1);
87 -});
88 -
89 -/**
90 - * One-time migration: the Grok 2 family was retired by xAI (grok-2, grok-2-1212,
91 - * grok-2-latest, grok-2-vision-1212 all return 400 "Model not found"). Existing
92 - * installs with the dead ID get auto-remapped to grok-4-1-fast-non-reasoning
93 - * (modern, fast, broadly available) the first time admin_init fires after update.
94 - */
95 -add_action('admin_init', function () {
96 - if (get_option('mxchat_grok_2_remap_done')) {
97 - return;
98 - }
99 - $opts = get_option('mxchat_options');
100 - if (is_array($opts) && isset($opts['model']) && $opts['model'] === 'grok-2') {
101 - $opts['model'] = 'grok-4-1-fast-non-reasoning';
102 - update_option('mxchat_options', $opts);
103 - }
104 - if (is_array($opts) && isset($opts['content_model']) && $opts['content_model'] === 'grok-2') {
105 - $opts['content_model'] = 'grok-4-1-fast-non-reasoning';
106 - update_option('mxchat_options', $opts);
107 - }
108 - update_option('mxchat_grok_2_remap_done', 1);
109 -});
110 -
111 -/**
112 - * One-time migration: Anthropic retired the Claude 4 (2025-05-14) snapshots on
113 - * June 15, 2026 — claude-opus-4-20250514 and claude-sonnet-4-20250514 now return
114 - * an API error. Existing installs with a dead ID get auto-remapped to the current
115 - * equivalents Anthropic recommends (Opus 4.8 / Sonnet 4.6) the first time admin_init
116 - * fires after update. Mirrors the gemini-3-pro-preview / grok-2 rescues above.
117 - */
118 -add_action('admin_init', function () {
119 - if (get_option('mxchat_claude_4_retire_remap_done')) {
120 - return;
121 - }
122 - $map = array(
123 - 'claude-opus-4-20250514' => 'claude-opus-4-8',
124 - 'claude-sonnet-4-20250514' => 'claude-sonnet-4-6',
125 - );
126 - $opts = get_option('mxchat_options');
127 - if (is_array($opts)) {
128 - $changed = false;
129 - if (isset($opts['model']) && isset($map[$opts['model']])) {
130 - $opts['model'] = $map[$opts['model']];
131 - $changed = true;
132 - }
133 - if (isset($opts['content_model']) && isset($map[$opts['content_model']])) {
134 - $opts['content_model'] = $map[$opts['content_model']];
135 - $changed = true;
136 - }
137 - if ($changed) {
138 - update_option('mxchat_options', $opts);
139 - }
140 - }
141 - update_option('mxchat_claude_4_retire_remap_done', 1);
142 -});
143 -
144 -/**
145 50 * Exclude MxChat assets from caching plugin optimizations
146 51 *
147 52 * This prevents issues with WP Rocket, LiteSpeed Cache, Autoptimize, WP Super Cache,
148 53 * W3 Total Cache, SG Optimizer, and similar plugins that may break the chatbot by
@@ -345,66 +250,11 @@
345 250 $rejected[] = 'wp-admin/admin-ajax.php';
346 251 return $rejected;
347 252 });
348 253
349 -// ── Page-cache bypass for chat AJAX (companion to the 3.2.6 nonce-race hotfix)
350 -// Each cache plugin gets its own filter export so that visitors hitting an
351 -// edge-cached page never receive cached chat-AJAX responses. The chat send /
352 -// stream send / file upload all POST to /wp-admin/admin-ajax.php with
353 -// `action=mxchat_*`. Without these exports, a cache plugin can stale a response
354 -// and break the per-session nonce flow on the first message.
355 -
356 -// WP Rocket — `rocket_cache_reject_uri` takes a flat array of regex strings.
357 -add_filter('rocket_cache_reject_uri', function($uris) {
358 - if (!is_array($uris)) $uris = array();
359 - $uris[] = '/wp-admin/admin-ajax\.php\?action=mxchat_.*';
360 - return $uris;
361 -});
362 -
363 -// LiteSpeed Cache — `litespeed_cache_no_cache_for_request` short-circuits
364 -// caching when the request matches our chat-AJAX pattern.
365 -add_filter('litespeed_cache_no_cache_for_request', function($no_cache) {
366 - if ($no_cache) return $no_cache;
367 - if (!empty($_SERVER['REQUEST_URI']) &&
368 - strpos($_SERVER['REQUEST_URI'], '/wp-admin/admin-ajax.php') !== false &&
369 - !empty($_REQUEST['action']) &&
370 - strpos((string) $_REQUEST['action'], 'mxchat_') === 0) {
371 - return true;
372 - }
373 - return $no_cache;
374 -});
375 -
376 -// W3 Total Cache — `w3tc_pgcache_request_skip_uri` flips page-cache off when
377 -// the URI matches.
378 -add_filter('w3tc_pgcache_request_skip_uri', function($skip) {
379 - if ($skip) return $skip;
380 - if (!empty($_SERVER['REQUEST_URI']) &&
381 - strpos($_SERVER['REQUEST_URI'], '/wp-admin/admin-ajax.php') !== false &&
382 - !empty($_REQUEST['action']) &&
383 - strpos((string) $_REQUEST['action'], 'mxchat_') === 0) {
384 - return true;
385 - }
386 - return $skip;
387 -});
388 -
389 -// FlyingPress — `flying_press_cacheable` takes a boolean and is run per
390 -// request. Same pattern as LiteSpeed / W3TC.
391 -add_filter('flying_press_cacheable', function($cacheable) {
392 - if (!$cacheable) return $cacheable;
393 - if (!empty($_SERVER['REQUEST_URI']) &&
394 - strpos($_SERVER['REQUEST_URI'], '/wp-admin/admin-ajax.php') !== false &&
395 - !empty($_REQUEST['action']) &&
396 - strpos((string) $_REQUEST['action'], 'mxchat_') === 0) {
397 - return false;
398 - }
399 - return $cacheable;
400 -});
401 -
402 254 // Include classes with error handling
403 255 function mxchat_include_classes() {
404 256 $class_files = array(
405 - 'includes/class-mxchat-model-catalog.php',
406 - 'includes/class-mxchat-tool-registry.php',
407 257 'includes/class-mxchat-integrator.php',
408 258 'includes/class-mxchat-admin.php',
409 259 'includes/class-mxchat-public.php',
410 260 'includes/class-mxchat-utils.php',
@@ -412,10 +262,8 @@
412 262 'includes/class-mxchat-meta-box.php',
413 263 'includes/class-mxchat-chunker.php',
414 264 'includes/class-mxchat-word-handler.php',
415 265 'includes/class-mxchat-content-generator.php',
416 - 'includes/class-mxchat-cache-purge.php',
417 - 'includes/class-rest-api.php',
418 266 'admin/class-ajax-handler.php',
419 267 'admin/class-pinecone-manager.php',
420 268 'admin/class-knowledge-manager.php'
421 269 );
@@ -427,31 +275,8 @@
427 275 } else {
428 276 //error_log('MxChat: Missing class file - ' . $file);
429 277 }
430 278 }
431 -
432 - // Register the native function-calling admin-post save handler (a41dee).
433 - if (class_exists('MxChat_Tool_Registry')) {
434 - MxChat_Tool_Registry::init();
435 - }
436 -
437 - // Admin pages that aren't classes (procedural include).
438 - if (is_admin()) {
439 - $admin_api_page = plugin_dir_path(__FILE__) . 'includes/admin-api-page.php';
440 - if (file_exists($admin_api_page)) {
441 - require_once $admin_api_page;
442 - }
443 - // f7c7d4 renamed this file admin-dashboard-page.php → admin-onboarding-page.php.
444 - // The require MUST live here (admin bootstrap) and not just inside
445 - // mxchat_add_plugin_page() on the admin_menu hook — admin_menu does NOT
446 - // fire on admin-ajax.php requests, so the wizard's AJAX handlers
447 - // (plan-905439: mxchat_onboarding_kb_status / save_step / mark_step /
448 - // auto_graduate + the f7c7d4 dismiss handler) would never register.
449 - $admin_onboarding_page = plugin_dir_path(__FILE__) . 'includes/admin-onboarding-page.php';
450 - if (file_exists($admin_onboarding_page)) {
451 - require_once $admin_onboarding_page;
452 - }
453 - }
454 279 }
455 280
456 281 /**
457 282 * Lazy-load the PDF parser library only when needed.
@@ -783,33 +608,8 @@
783 608 update_option('mxchat_content_type_migration_version', '2.5.6');
784 609 }
785 610
786 611 /**
787 - * 3.2.4: Backfill the active embedding model option for installs that already
788 - * have KB content but no stamped model. The mismatch warning compares this
789 - * against the user's currently selected model — no per-row column needed.
790 - */
791 -function mxchat_backfill_active_embedding_model() {
792 - global $wpdb;
793 -
794 - if (get_option('mxchat_active_embedding_model', '') !== '') {
795 - return;
796 - }
797 -
798 - $kb_table = $wpdb->prefix . 'mxchat_system_prompt_content';
799 - if ($wpdb->get_var("SHOW TABLES LIKE '{$kb_table}'") !== $kb_table) {
800 - return;
801 - }
802 -
803 - $kb_count = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$kb_table}");
804 - if ($kb_count > 0) {
805 - $options = get_option('mxchat_options', array());
806 - $current_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
807 - update_option('mxchat_active_embedding_model', $current_model, false);
808 - }
809 -}
810 -
811 -/**
812 612 * 2.5.2: Create queue processing tables for reliable background processing
813 613 */
814 614 function mxchat_create_queue_tables() {
815 615 global $wpdb;
@@ -881,34 +681,8 @@
881 681 dbDelta($sql);
882 682 }
883 683
884 684 /**
885 - * Create per-session satisfaction ratings table (v3.2.6)
886 - * Stores one 👍/👎 rating + optional feedback per chat session.
887 - */
888 -function mxchat_create_session_ratings_table() {
889 - global $wpdb;
890 - $charset_collate = $wpdb->get_charset_collate();
891 -
892 - $table_name = $wpdb->prefix . 'mxchat_session_ratings';
893 - $sql = "CREATE TABLE $table_name (
894 - id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
895 - session_id varchar(255) NOT NULL,
896 - bot_id varchar(50) NOT NULL DEFAULT 'default',
897 - rating_value tinyint(1) NOT NULL,
898 - rating_feedback text DEFAULT NULL,
899 - created_at datetime NOT NULL,
900 - PRIMARY KEY (id),
901 - UNIQUE KEY session_id (session_id),
902 - KEY bot_id (bot_id),
903 - KEY created_at (created_at)
904 - ) $charset_collate;";
905 -
906 - require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
907 - dbDelta($sql);
908 -}
909 -
910 -/**
911 685 * 2.5.2: Fix URL column size to support long URLs (especially with UTF-8 encoding)
912 686 * This fixes "url, source_url. The supplied values may be too long" errors
913 687 */
914 688 function mxchat_fix_url_column_size() {
@@ -1094,11 +868,8 @@
1094 868
1095 869 // Create transcript translations table
1096 870 mxchat_create_translations_table();
1097 871
1098 - // Create per-session satisfaction ratings table (v3.2.6)
1099 - mxchat_create_session_ratings_table();
1100 -
1101 872 // Ensure additional columns in system prompt table
1102 873 $existing_system_columns = $wpdb->get_results("SHOW COLUMNS FROM $system_prompt_table");
1103 874 if (!empty($existing_system_columns)) {
1104 875 $existing_system_column_names = array_column($existing_system_columns, 'Field');
@@ -1141,11 +912,8 @@
1141 912
1142 913 // Run migration for existing installations
1143 914 mxchat_migrate_pinecone_roles_add_bot_id();
1144 915
1145 - // 3.2.4: Backfill active embedding model option (replaces 3.2.3 column-based tracking)
1146 - mxchat_backfill_active_embedding_model();
1147 -
1148 916 // Setup cron jobs
1149 917 mxchat_setup_cron_jobs();
1150 918
1151 919 // Update version
@@ -1180,14 +948,13 @@
1180 948 // Clear fallback flags if cron scheduling succeeded
1181 949 delete_option('mxchat_use_fallback_rate_limits');
1182 950 }
1183 951
1184 - // Schedule transcript cleanup if configured (bucket dropdown OR custom retention-days > 0)
952 + // Schedule transcript cleanup if configured
1185 953 $transcript_options = get_option('mxchat_transcripts_options', array());
1186 954 $cleanup_interval = isset($transcript_options['mxchat_auto_delete_transcripts']) ? $transcript_options['mxchat_auto_delete_transcripts'] : 'never';
1187 - $custom_retention = isset($transcript_options['mxchat_retention_days']) ? (int) $transcript_options['mxchat_retention_days'] : 0;
1188 -
1189 - if ($cleanup_interval !== 'never' || $custom_retention > 0) {
955 +
956 + if ($cleanup_interval !== 'never') {
1190 957 // Check if not already scheduled
1191 958 if (!wp_next_scheduled('mxchat_cleanup_old_transcripts')) {
1192 959 // Schedule to run daily at 3 AM
1193 960 $next_run = strtotime('tomorrow 3:00 AM');
@@ -1321,14 +1088,8 @@
1321 1088 delete_option('mxchat_email_null');
1322 1089 delete_option('mxchat_name_null');
1323 1090 }
1324 1091
1325 - // 3.2.4: Backfill active embedding model option for the warning UI
1326 - // (replaces the per-row column tracking from 3.2.3, which was reverted)
1327 - if (version_compare($current_version, '3.2.4', '<')) {
1328 - mxchat_backfill_active_embedding_model();
1329 - }
1330 -
1331 1092 // Run full activation to ensure everything is up to date
1332 1093 mxchat_activate();
1333 1094
1334 1095 // Run migration functions
@@ -1553,23 +1314,8 @@
1553 1314 if (class_exists('MxChat_Content_Generator')) {
1554 1315 new MxChat_Content_Generator();
1555 1316 }
1556 1317
1557 - // Initialize cache purge globally — settings writes can happen on any
1558 - // request type (admin screens, admin-ajax autosave, wp-cli), and the
1559 - // deferred-purge cron event fires on front-end requests.
1560 - if (class_exists('MxChat_Cache_Purge')) {
1561 - MxChat_Cache_Purge::init();
1562 - }
1563 -
1564 - // Initialize REST API globally — endpoints must be registered on
1565 - // every request (admin and frontend) so they're reachable via /wp-json/.
1566 - // Endpoints are auth-gated and locked until the site owner generates
1567 - // a token in MxChat → API Access.
1568 - if (class_exists('MxChat_Rest_Api')) {
1569 - new MxChat_Rest_Api();
1570 - }
1571 -
1572 1318 // Initialize public classes
1573 1319 if (class_exists('MxChat_Public')) {
1574 1320 $mxchat_public = new MxChat_Public();
1575 1321 }
@@ -1614,25 +1360,11 @@
1614 1360
1615 1361 mxchat_migrate_pinecone_roles_add_bot_id();
1616 1362 mxchat_migrate_add_content_type_column();
1617 1363 mxchat_migrate_add_translations_table();
1618 - mxchat_migrate_add_session_ratings_table();
1619 1364 }
1620 1365
1621 1366 /**
1622 - * Migration: Create per-session satisfaction ratings table (v3.2.6)
1623 - * For users upgrading from versions before 3.2.6
1624 - */
1625 -function mxchat_migrate_add_session_ratings_table() {
1626 - $migration_key = 'mxchat_session_ratings_table_created';
1627 - if (get_option($migration_key)) {
1628 - return;
1629 - }
1630 - mxchat_create_session_ratings_table();
1631 - update_option($migration_key, '3.2.6');
1632 -}
1633 -
1634 -/**
1635 1367 * Migration: Create transcript translations table (v3.0.4)
1636 1368 * For users upgrading from versions before 3.0.4
1637 1369 */
1638 1370 function mxchat_migrate_add_translations_table() {
@@ -1675,67 +1407,5 @@
1675 1407 return $schedules;
1676 1408 });
1677 1409
1678 1410 // Register deactivation hook
1679 -register_deactivation_hook(__FILE__, 'mxchat_deactivate');
1680 -
1681 -/**
1682 - * Per-session satisfaction rating: AJAX save handler (v3.2.6).
1683 - * Records one 👍/👎 + optional feedback per chat session. The UNIQUE KEY on
1684 - * session_id makes this naturally idempotent — only the first rating per
1685 - * session is stored; duplicate POSTs are silent no-ops.
1686 - */
1687 -function mxchat_save_session_rating() {
1688 - global $wpdb;
1689 -
1690 - $session_id = isset($_POST['session_id']) ? sanitize_text_field(wp_unslash($_POST['session_id'])) : '';
1691 - $bot_id = isset($_POST['bot_id']) ? sanitize_text_field(wp_unslash($_POST['bot_id'])) : 'default';
1692 - $rating_raw = isset($_POST['rating']) ? (int) $_POST['rating'] : 0;
1693 - $feedback = isset($_POST['feedback']) ? sanitize_textarea_field(wp_unslash($_POST['feedback'])) : '';
1694 -
1695 - if ($session_id === '' || ($rating_raw !== 1 && $rating_raw !== -1)) {
1696 - wp_send_json_error(array('message' => 'invalid_input'), 400);
1697 - }
1698 -
1699 - if (strlen($feedback) > 1000) {
1700 - $feedback = substr($feedback, 0, 1000);
1701 - }
1702 -
1703 - $table_name = $wpdb->prefix . 'mxchat_session_ratings';
1704 - $existing = $wpdb->get_var($wpdb->prepare(
1705 - "SELECT id FROM $table_name WHERE session_id = %s LIMIT 1",
1706 - $session_id
1707 - ));
1708 -
1709 - if ($existing) {
1710 - if ($feedback !== '') {
1711 - $wpdb->update(
1712 - $table_name,
1713 - array('rating_feedback' => $feedback),
1714 - array('id' => (int) $existing),
1715 - array('%s'),
1716 - array('%d')
1717 - );
1718 - }
1719 - wp_send_json_success(array('updated' => true));
1720 - }
1721 -
1722 - $inserted = $wpdb->insert(
1723 - $table_name,
1724 - array(
1725 - 'session_id' => $session_id,
1726 - 'bot_id' => $bot_id !== '' ? $bot_id : 'default',
1727 - 'rating_value' => $rating_raw,
1728 - 'rating_feedback' => $feedback !== '' ? $feedback : null,
1729 - 'created_at' => current_time('mysql'),
1730 - ),
1731 - array('%s', '%s', '%d', '%s', '%s')
1732 - );
1733 -
1734 - if ($inserted === false) {
1735 - wp_send_json_error(array('message' => 'db_insert_failed'), 500);
1736 - }
1737 -
1738 - wp_send_json_success(array('saved' => true));
1739 -}
1740 -add_action('wp_ajax_mxchat_save_rating', 'mxchat_save_session_rating');
1741 -add_action('wp_ajax_nopriv_mxchat_save_rating', 'mxchat_save_session_rating');
1411 +register_deactivation_hook(__FILE__, 'mxchat_deactivate');