PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.10
MxChat – AI Chatbot & Content Generation for WordPress v3.2.10
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 +261 -2 3.2.53.2.10 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.5
6 + * Version: 3.2.10
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
@@ -46,8 +46,84 @@
46 46 }
47 47 add_action('init', 'mxchat_load_textdomain');
48 48
49 49 /**
50 + * One-time migration: gemini-3-pro-preview was shut down by Google on March 9, 2026.
51 + * Existing installs with the dead ID get auto-remapped to gemini-3.1-pro-preview
52 + * (Google's official migration target) the first time admin_init fires after update.
53 + */
54 +add_action('admin_init', function () {
55 + if (get_option('mxchat_gemini_3_remap_done')) {
56 + return;
57 + }
58 + $opts = get_option('mxchat_options');
59 + if (is_array($opts) && isset($opts['model']) && $opts['model'] === 'gemini-3-pro-preview') {
60 + $opts['model'] = 'gemini-3.1-pro-preview';
61 + update_option('mxchat_options', $opts);
62 + }
63 + if (is_array($opts) && isset($opts['content_model']) && $opts['content_model'] === 'gemini-3-pro-preview') {
64 + $opts['content_model'] = 'gemini-3.1-pro-preview';
65 + update_option('mxchat_options', $opts);
66 + }
67 + update_option('mxchat_gemini_3_remap_done', 1);
68 +});
69 +
70 +/**
71 + * One-time migration: the Grok 2 family was retired by xAI (grok-2, grok-2-1212,
72 + * grok-2-latest, grok-2-vision-1212 all return 400 "Model not found"). Existing
73 + * installs with the dead ID get auto-remapped to grok-4-1-fast-non-reasoning
74 + * (modern, fast, broadly available) the first time admin_init fires after update.
75 + */
76 +add_action('admin_init', function () {
77 + if (get_option('mxchat_grok_2_remap_done')) {
78 + return;
79 + }
80 + $opts = get_option('mxchat_options');
81 + if (is_array($opts) && isset($opts['model']) && $opts['model'] === 'grok-2') {
82 + $opts['model'] = 'grok-4-1-fast-non-reasoning';
83 + update_option('mxchat_options', $opts);
84 + }
85 + if (is_array($opts) && isset($opts['content_model']) && $opts['content_model'] === 'grok-2') {
86 + $opts['content_model'] = 'grok-4-1-fast-non-reasoning';
87 + update_option('mxchat_options', $opts);
88 + }
89 + update_option('mxchat_grok_2_remap_done', 1);
90 +});
91 +
92 +/**
93 + * One-time migration: Anthropic retired the Claude 4 (2025-05-14) snapshots on
94 + * June 15, 2026 — claude-opus-4-20250514 and claude-sonnet-4-20250514 now return
95 + * an API error. Existing installs with a dead ID get auto-remapped to the current
96 + * equivalents Anthropic recommends (Opus 4.8 / Sonnet 4.6) the first time admin_init
97 + * fires after update. Mirrors the gemini-3-pro-preview / grok-2 rescues above.
98 + */
99 +add_action('admin_init', function () {
100 + if (get_option('mxchat_claude_4_retire_remap_done')) {
101 + return;
102 + }
103 + $map = array(
104 + 'claude-opus-4-20250514' => 'claude-opus-4-8',
105 + 'claude-sonnet-4-20250514' => 'claude-sonnet-4-6',
106 + );
107 + $opts = get_option('mxchat_options');
108 + if (is_array($opts)) {
109 + $changed = false;
110 + if (isset($opts['model']) && isset($map[$opts['model']])) {
111 + $opts['model'] = $map[$opts['model']];
112 + $changed = true;
113 + }
114 + if (isset($opts['content_model']) && isset($map[$opts['content_model']])) {
115 + $opts['content_model'] = $map[$opts['content_model']];
116 + $changed = true;
117 + }
118 + if ($changed) {
119 + update_option('mxchat_options', $opts);
120 + }
121 + }
122 + update_option('mxchat_claude_4_retire_remap_done', 1);
123 +});
124 +
125 +/**
50 126 * Exclude MxChat assets from caching plugin optimizations
51 127 *
52 128 * This prevents issues with WP Rocket, LiteSpeed Cache, Autoptimize, WP Super Cache,
53 129 * W3 Total Cache, SG Optimizer, and similar plugins that may break the chatbot by
@@ -250,11 +326,66 @@
250 326 $rejected[] = 'wp-admin/admin-ajax.php';
251 327 return $rejected;
252 328 });
253 329
330 +// ── Page-cache bypass for chat AJAX (companion to the 3.2.6 nonce-race hotfix)
331 +// Each cache plugin gets its own filter export so that visitors hitting an
332 +// edge-cached page never receive cached chat-AJAX responses. The chat send /
333 +// stream send / file upload all POST to /wp-admin/admin-ajax.php with
334 +// `action=mxchat_*`. Without these exports, a cache plugin can stale a response
335 +// and break the per-session nonce flow on the first message.
336 +
337 +// WP Rocket — `rocket_cache_reject_uri` takes a flat array of regex strings.
338 +add_filter('rocket_cache_reject_uri', function($uris) {
339 + if (!is_array($uris)) $uris = array();
340 + $uris[] = '/wp-admin/admin-ajax\.php\?action=mxchat_.*';
341 + return $uris;
342 +});
343 +
344 +// LiteSpeed Cache — `litespeed_cache_no_cache_for_request` short-circuits
345 +// caching when the request matches our chat-AJAX pattern.
346 +add_filter('litespeed_cache_no_cache_for_request', function($no_cache) {
347 + if ($no_cache) return $no_cache;
348 + if (!empty($_SERVER['REQUEST_URI']) &&
349 + strpos($_SERVER['REQUEST_URI'], '/wp-admin/admin-ajax.php') !== false &&
350 + !empty($_REQUEST['action']) &&
351 + strpos((string) $_REQUEST['action'], 'mxchat_') === 0) {
352 + return true;
353 + }
354 + return $no_cache;
355 +});
356 +
357 +// W3 Total Cache — `w3tc_pgcache_request_skip_uri` flips page-cache off when
358 +// the URI matches.
359 +add_filter('w3tc_pgcache_request_skip_uri', function($skip) {
360 + if ($skip) return $skip;
361 + if (!empty($_SERVER['REQUEST_URI']) &&
362 + strpos($_SERVER['REQUEST_URI'], '/wp-admin/admin-ajax.php') !== false &&
363 + !empty($_REQUEST['action']) &&
364 + strpos((string) $_REQUEST['action'], 'mxchat_') === 0) {
365 + return true;
366 + }
367 + return $skip;
368 +});
369 +
370 +// FlyingPress — `flying_press_cacheable` takes a boolean and is run per
371 +// request. Same pattern as LiteSpeed / W3TC.
372 +add_filter('flying_press_cacheable', function($cacheable) {
373 + if (!$cacheable) return $cacheable;
374 + if (!empty($_SERVER['REQUEST_URI']) &&
375 + strpos($_SERVER['REQUEST_URI'], '/wp-admin/admin-ajax.php') !== false &&
376 + !empty($_REQUEST['action']) &&
377 + strpos((string) $_REQUEST['action'], 'mxchat_') === 0) {
378 + return false;
379 + }
380 + return $cacheable;
381 +});
382 +
254 383 // Include classes with error handling
255 384 function mxchat_include_classes() {
256 385 $class_files = array(
386 + 'includes/class-mxchat-model-catalog.php',
387 + 'includes/class-mxchat-tool-registry.php',
257 388 'includes/class-mxchat-integrator.php',
258 389 'includes/class-mxchat-admin.php',
259 390 'includes/class-mxchat-public.php',
260 391 'includes/class-mxchat-utils.php',
@@ -262,8 +393,9 @@
262 393 'includes/class-mxchat-meta-box.php',
263 394 'includes/class-mxchat-chunker.php',
264 395 'includes/class-mxchat-word-handler.php',
265 396 'includes/class-mxchat-content-generator.php',
397 + 'includes/class-mxchat-cache-purge.php',
266 398 'includes/class-rest-api.php',
267 399 'admin/class-ajax-handler.php',
268 400 'admin/class-pinecone-manager.php',
269 401 'admin/class-knowledge-manager.php'
@@ -277,8 +409,13 @@
277 409 //error_log('MxChat: Missing class file - ' . $file);
278 410 }
279 411 }
280 412
413 + // Register the native function-calling admin-post save handler (a41dee).
414 + if (class_exists('MxChat_Tool_Registry')) {
415 + MxChat_Tool_Registry::init();
416 + }
417 +
281 418 // Admin pages that aren't classes (procedural include).
282 419 if (is_admin()) {
283 420 $admin_api_page = plugin_dir_path(__FILE__) . 'includes/admin-api-page.php';
284 421 if (file_exists($admin_api_page)) {
@@ -283,8 +420,18 @@
283 420 $admin_api_page = plugin_dir_path(__FILE__) . 'includes/admin-api-page.php';
284 421 if (file_exists($admin_api_page)) {
285 422 require_once $admin_api_page;
286 423 }
424 + // f7c7d4 renamed this file admin-dashboard-page.php → admin-onboarding-page.php.
425 + // The require MUST live here (admin bootstrap) and not just inside
426 + // mxchat_add_plugin_page() on the admin_menu hook — admin_menu does NOT
427 + // fire on admin-ajax.php requests, so the wizard's AJAX handlers
428 + // (plan-905439: mxchat_onboarding_kb_status / save_step / mark_step /
429 + // auto_graduate + the f7c7d4 dismiss handler) would never register.
430 + $admin_onboarding_page = plugin_dir_path(__FILE__) . 'includes/admin-onboarding-page.php';
431 + if (file_exists($admin_onboarding_page)) {
432 + require_once $admin_onboarding_page;
433 + }
287 434 }
288 435 }
289 436
290 437 /**
@@ -715,8 +862,34 @@
715 862 dbDelta($sql);
716 863 }
717 864
718 865 /**
866 + * Create per-session satisfaction ratings table (v3.2.6)
867 + * Stores one 👍/👎 rating + optional feedback per chat session.
868 + */
869 +function mxchat_create_session_ratings_table() {
870 + global $wpdb;
871 + $charset_collate = $wpdb->get_charset_collate();
872 +
873 + $table_name = $wpdb->prefix . 'mxchat_session_ratings';
874 + $sql = "CREATE TABLE $table_name (
875 + id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
876 + session_id varchar(255) NOT NULL,
877 + bot_id varchar(50) NOT NULL DEFAULT 'default',
878 + rating_value tinyint(1) NOT NULL,
879 + rating_feedback text DEFAULT NULL,
880 + created_at datetime NOT NULL,
881 + PRIMARY KEY (id),
882 + UNIQUE KEY session_id (session_id),
883 + KEY bot_id (bot_id),
884 + KEY created_at (created_at)
885 + ) $charset_collate;";
886 +
887 + require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
888 + dbDelta($sql);
889 +}
890 +
891 +/**
719 892 * 2.5.2: Fix URL column size to support long URLs (especially with UTF-8 encoding)
720 893 * This fixes "url, source_url. The supplied values may be too long" errors
721 894 */
722 895 function mxchat_fix_url_column_size() {
@@ -902,8 +1075,11 @@
902 1075
903 1076 // Create transcript translations table
904 1077 mxchat_create_translations_table();
905 1078
1079 + // Create per-session satisfaction ratings table (v3.2.6)
1080 + mxchat_create_session_ratings_table();
1081 +
906 1082 // Ensure additional columns in system prompt table
907 1083 $existing_system_columns = $wpdb->get_results("SHOW COLUMNS FROM $system_prompt_table");
908 1084 if (!empty($existing_system_columns)) {
909 1085 $existing_system_column_names = array_column($existing_system_columns, 'Field');
@@ -1358,8 +1534,15 @@
1358 1534 if (class_exists('MxChat_Content_Generator')) {
1359 1535 new MxChat_Content_Generator();
1360 1536 }
1361 1537
1538 + // Initialize cache purge globally — settings writes can happen on any
1539 + // request type (admin screens, admin-ajax autosave, wp-cli), and the
1540 + // deferred-purge cron event fires on front-end requests.
1541 + if (class_exists('MxChat_Cache_Purge')) {
1542 + MxChat_Cache_Purge::init();
1543 + }
1544 +
1362 1545 // Initialize REST API globally — endpoints must be registered on
1363 1546 // every request (admin and frontend) so they're reachable via /wp-json/.
1364 1547 // Endpoints are auth-gated and locked until the site owner generates
1365 1548 // a token in MxChat → API Access.
@@ -1412,11 +1595,25 @@
1412 1595
1413 1596 mxchat_migrate_pinecone_roles_add_bot_id();
1414 1597 mxchat_migrate_add_content_type_column();
1415 1598 mxchat_migrate_add_translations_table();
1599 + mxchat_migrate_add_session_ratings_table();
1416 1600 }
1417 1601
1418 1602 /**
1603 + * Migration: Create per-session satisfaction ratings table (v3.2.6)
1604 + * For users upgrading from versions before 3.2.6
1605 + */
1606 +function mxchat_migrate_add_session_ratings_table() {
1607 + $migration_key = 'mxchat_session_ratings_table_created';
1608 + if (get_option($migration_key)) {
1609 + return;
1610 + }
1611 + mxchat_create_session_ratings_table();
1612 + update_option($migration_key, '3.2.6');
1613 +}
1614 +
1615 +/**
1419 1616 * Migration: Create transcript translations table (v3.0.4)
1420 1617 * For users upgrading from versions before 3.0.4
1421 1618 */
1422 1619 function mxchat_migrate_add_translations_table() {
@@ -1459,5 +1656,67 @@
1459 1656 return $schedules;
1460 1657 });
1461 1658
1462 1659 // Register deactivation hook
1463 -register_deactivation_hook(__FILE__, 'mxchat_deactivate');
1660 +register_deactivation_hook(__FILE__, 'mxchat_deactivate');
1661 +
1662 +/**
1663 + * Per-session satisfaction rating: AJAX save handler (v3.2.6).
1664 + * Records one 👍/👎 + optional feedback per chat session. The UNIQUE KEY on
1665 + * session_id makes this naturally idempotent — only the first rating per
1666 + * session is stored; duplicate POSTs are silent no-ops.
1667 + */
1668 +function mxchat_save_session_rating() {
1669 + global $wpdb;
1670 +
1671 + $session_id = isset($_POST['session_id']) ? sanitize_text_field(wp_unslash($_POST['session_id'])) : '';
1672 + $bot_id = isset($_POST['bot_id']) ? sanitize_text_field(wp_unslash($_POST['bot_id'])) : 'default';
1673 + $rating_raw = isset($_POST['rating']) ? (int) $_POST['rating'] : 0;
1674 + $feedback = isset($_POST['feedback']) ? sanitize_textarea_field(wp_unslash($_POST['feedback'])) : '';
1675 +
1676 + if ($session_id === '' || ($rating_raw !== 1 && $rating_raw !== -1)) {
1677 + wp_send_json_error(array('message' => 'invalid_input'), 400);
1678 + }
1679 +
1680 + if (strlen($feedback) > 1000) {
1681 + $feedback = substr($feedback, 0, 1000);
1682 + }
1683 +
1684 + $table_name = $wpdb->prefix . 'mxchat_session_ratings';
1685 + $existing = $wpdb->get_var($wpdb->prepare(
1686 + "SELECT id FROM $table_name WHERE session_id = %s LIMIT 1",
1687 + $session_id
1688 + ));
1689 +
1690 + if ($existing) {
1691 + if ($feedback !== '') {
1692 + $wpdb->update(
1693 + $table_name,
1694 + array('rating_feedback' => $feedback),
1695 + array('id' => (int) $existing),
1696 + array('%s'),
1697 + array('%d')
1698 + );
1699 + }
1700 + wp_send_json_success(array('updated' => true));
1701 + }
1702 +
1703 + $inserted = $wpdb->insert(
1704 + $table_name,
1705 + array(
1706 + 'session_id' => $session_id,
1707 + 'bot_id' => $bot_id !== '' ? $bot_id : 'default',
1708 + 'rating_value' => $rating_raw,
1709 + 'rating_feedback' => $feedback !== '' ? $feedback : null,
1710 + 'created_at' => current_time('mysql'),
1711 + ),
1712 + array('%s', '%s', '%d', '%s', '%s')
1713 + );
1714 +
1715 + if ($inserted === false) {
1716 + wp_send_json_error(array('message' => 'db_insert_failed'), 500);
1717 + }
1718 +
1719 + wp_send_json_success(array('saved' => true));
1720 +}
1721 +add_action('wp_ajax_mxchat_save_rating', 'mxchat_save_session_rating');
1722 +add_action('wp_ajax_nopriv_mxchat_save_rating', 'mxchat_save_session_rating');