PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.7
MxChat – AI Chatbot & Content Generation for WordPress v3.2.7
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 +213 -2 3.2.53.2.7 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.7
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,51 @@
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 +/**
50 93 * Exclude MxChat assets from caching plugin optimizations
51 94 *
52 95 * This prevents issues with WP Rocket, LiteSpeed Cache, Autoptimize, WP Super Cache,
53 96 * W3 Total Cache, SG Optimizer, and similar plugins that may break the chatbot by
@@ -250,8 +293,61 @@
250 293 $rejected[] = 'wp-admin/admin-ajax.php';
251 294 return $rejected;
252 295 });
253 296
297 +// ── Page-cache bypass for chat AJAX (companion to the 3.2.6 nonce-race hotfix)
298 +// Each cache plugin gets its own filter export so that visitors hitting an
299 +// edge-cached page never receive cached chat-AJAX responses. The chat send /
300 +// stream send / file upload all POST to /wp-admin/admin-ajax.php with
301 +// `action=mxchat_*`. Without these exports, a cache plugin can stale a response
302 +// and break the per-session nonce flow on the first message.
303 +
304 +// WP Rocket — `rocket_cache_reject_uri` takes a flat array of regex strings.
305 +add_filter('rocket_cache_reject_uri', function($uris) {
306 + if (!is_array($uris)) $uris = array();
307 + $uris[] = '/wp-admin/admin-ajax\.php\?action=mxchat_.*';
308 + return $uris;
309 +});
310 +
311 +// LiteSpeed Cache — `litespeed_cache_no_cache_for_request` short-circuits
312 +// caching when the request matches our chat-AJAX pattern.
313 +add_filter('litespeed_cache_no_cache_for_request', function($no_cache) {
314 + if ($no_cache) return $no_cache;
315 + if (!empty($_SERVER['REQUEST_URI']) &&
316 + strpos($_SERVER['REQUEST_URI'], '/wp-admin/admin-ajax.php') !== false &&
317 + !empty($_REQUEST['action']) &&
318 + strpos((string) $_REQUEST['action'], 'mxchat_') === 0) {
319 + return true;
320 + }
321 + return $no_cache;
322 +});
323 +
324 +// W3 Total Cache — `w3tc_pgcache_request_skip_uri` flips page-cache off when
325 +// the URI matches.
326 +add_filter('w3tc_pgcache_request_skip_uri', function($skip) {
327 + if ($skip) return $skip;
328 + if (!empty($_SERVER['REQUEST_URI']) &&
329 + strpos($_SERVER['REQUEST_URI'], '/wp-admin/admin-ajax.php') !== false &&
330 + !empty($_REQUEST['action']) &&
331 + strpos((string) $_REQUEST['action'], 'mxchat_') === 0) {
332 + return true;
333 + }
334 + return $skip;
335 +});
336 +
337 +// FlyingPress — `flying_press_cacheable` takes a boolean and is run per
338 +// request. Same pattern as LiteSpeed / W3TC.
339 +add_filter('flying_press_cacheable', function($cacheable) {
340 + if (!$cacheable) return $cacheable;
341 + if (!empty($_SERVER['REQUEST_URI']) &&
342 + strpos($_SERVER['REQUEST_URI'], '/wp-admin/admin-ajax.php') !== false &&
343 + !empty($_REQUEST['action']) &&
344 + strpos((string) $_REQUEST['action'], 'mxchat_') === 0) {
345 + return false;
346 + }
347 + return $cacheable;
348 +});
349 +
254 350 // Include classes with error handling
255 351 function mxchat_include_classes() {
256 352 $class_files = array(
257 353 'includes/class-mxchat-integrator.php',
@@ -283,8 +379,18 @@
283 379 $admin_api_page = plugin_dir_path(__FILE__) . 'includes/admin-api-page.php';
284 380 if (file_exists($admin_api_page)) {
285 381 require_once $admin_api_page;
286 382 }
383 + // f7c7d4 renamed this file admin-dashboard-page.php → admin-onboarding-page.php.
384 + // The require MUST live here (admin bootstrap) and not just inside
385 + // mxchat_add_plugin_page() on the admin_menu hook — admin_menu does NOT
386 + // fire on admin-ajax.php requests, so the wizard's AJAX handlers
387 + // (plan-905439: mxchat_onboarding_kb_status / save_step / mark_step /
388 + // auto_graduate + the f7c7d4 dismiss handler) would never register.
389 + $admin_onboarding_page = plugin_dir_path(__FILE__) . 'includes/admin-onboarding-page.php';
390 + if (file_exists($admin_onboarding_page)) {
391 + require_once $admin_onboarding_page;
392 + }
287 393 }
288 394 }
289 395
290 396 /**
@@ -715,8 +821,34 @@
715 821 dbDelta($sql);
716 822 }
717 823
718 824 /**
825 + * Create per-session satisfaction ratings table (v3.2.6)
826 + * Stores one 👍/👎 rating + optional feedback per chat session.
827 + */
828 +function mxchat_create_session_ratings_table() {
829 + global $wpdb;
830 + $charset_collate = $wpdb->get_charset_collate();
831 +
832 + $table_name = $wpdb->prefix . 'mxchat_session_ratings';
833 + $sql = "CREATE TABLE $table_name (
834 + id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
835 + session_id varchar(255) NOT NULL,
836 + bot_id varchar(50) NOT NULL DEFAULT 'default',
837 + rating_value tinyint(1) NOT NULL,
838 + rating_feedback text DEFAULT NULL,
839 + created_at datetime NOT NULL,
840 + PRIMARY KEY (id),
841 + UNIQUE KEY session_id (session_id),
842 + KEY bot_id (bot_id),
843 + KEY created_at (created_at)
844 + ) $charset_collate;";
845 +
846 + require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
847 + dbDelta($sql);
848 +}
849 +
850 +/**
719 851 * 2.5.2: Fix URL column size to support long URLs (especially with UTF-8 encoding)
720 852 * This fixes "url, source_url. The supplied values may be too long" errors
721 853 */
722 854 function mxchat_fix_url_column_size() {
@@ -902,8 +1034,11 @@
902 1034
903 1035 // Create transcript translations table
904 1036 mxchat_create_translations_table();
905 1037
1038 + // Create per-session satisfaction ratings table (v3.2.6)
1039 + mxchat_create_session_ratings_table();
1040 +
906 1041 // Ensure additional columns in system prompt table
907 1042 $existing_system_columns = $wpdb->get_results("SHOW COLUMNS FROM $system_prompt_table");
908 1043 if (!empty($existing_system_columns)) {
909 1044 $existing_system_column_names = array_column($existing_system_columns, 'Field');
@@ -1412,11 +1547,25 @@
1412 1547
1413 1548 mxchat_migrate_pinecone_roles_add_bot_id();
1414 1549 mxchat_migrate_add_content_type_column();
1415 1550 mxchat_migrate_add_translations_table();
1551 + mxchat_migrate_add_session_ratings_table();
1416 1552 }
1417 1553
1418 1554 /**
1555 + * Migration: Create per-session satisfaction ratings table (v3.2.6)
1556 + * For users upgrading from versions before 3.2.6
1557 + */
1558 +function mxchat_migrate_add_session_ratings_table() {
1559 + $migration_key = 'mxchat_session_ratings_table_created';
1560 + if (get_option($migration_key)) {
1561 + return;
1562 + }
1563 + mxchat_create_session_ratings_table();
1564 + update_option($migration_key, '3.2.6');
1565 +}
1566 +
1567 +/**
1419 1568 * Migration: Create transcript translations table (v3.0.4)
1420 1569 * For users upgrading from versions before 3.0.4
1421 1570 */
1422 1571 function mxchat_migrate_add_translations_table() {
@@ -1459,5 +1608,67 @@
1459 1608 return $schedules;
1460 1609 });
1461 1610
1462 1611 // Register deactivation hook
1463 -register_deactivation_hook(__FILE__, 'mxchat_deactivate');
1612 +register_deactivation_hook(__FILE__, 'mxchat_deactivate');
1613 +
1614 +/**
1615 + * Per-session satisfaction rating: AJAX save handler (v3.2.6).
1616 + * Records one 👍/👎 + optional feedback per chat session. The UNIQUE KEY on
1617 + * session_id makes this naturally idempotent — only the first rating per
1618 + * session is stored; duplicate POSTs are silent no-ops.
1619 + */
1620 +function mxchat_save_session_rating() {
1621 + global $wpdb;
1622 +
1623 + $session_id = isset($_POST['session_id']) ? sanitize_text_field(wp_unslash($_POST['session_id'])) : '';
1624 + $bot_id = isset($_POST['bot_id']) ? sanitize_text_field(wp_unslash($_POST['bot_id'])) : 'default';
1625 + $rating_raw = isset($_POST['rating']) ? (int) $_POST['rating'] : 0;
1626 + $feedback = isset($_POST['feedback']) ? sanitize_textarea_field(wp_unslash($_POST['feedback'])) : '';
1627 +
1628 + if ($session_id === '' || ($rating_raw !== 1 && $rating_raw !== -1)) {
1629 + wp_send_json_error(array('message' => 'invalid_input'), 400);
1630 + }
1631 +
1632 + if (strlen($feedback) > 1000) {
1633 + $feedback = substr($feedback, 0, 1000);
1634 + }
1635 +
1636 + $table_name = $wpdb->prefix . 'mxchat_session_ratings';
1637 + $existing = $wpdb->get_var($wpdb->prepare(
1638 + "SELECT id FROM $table_name WHERE session_id = %s LIMIT 1",
1639 + $session_id
1640 + ));
1641 +
1642 + if ($existing) {
1643 + if ($feedback !== '') {
1644 + $wpdb->update(
1645 + $table_name,
1646 + array('rating_feedback' => $feedback),
1647 + array('id' => (int) $existing),
1648 + array('%s'),
1649 + array('%d')
1650 + );
1651 + }
1652 + wp_send_json_success(array('updated' => true));
1653 + }
1654 +
1655 + $inserted = $wpdb->insert(
1656 + $table_name,
1657 + array(
1658 + 'session_id' => $session_id,
1659 + 'bot_id' => $bot_id !== '' ? $bot_id : 'default',
1660 + 'rating_value' => $rating_raw,
1661 + 'rating_feedback' => $feedback !== '' ? $feedback : null,
1662 + 'created_at' => current_time('mysql'),
1663 + ),
1664 + array('%s', '%s', '%d', '%s', '%s')
1665 + );
1666 +
1667 + if ($inserted === false) {
1668 + wp_send_json_error(array('message' => 'db_insert_failed'), 500);
1669 + }
1670 +
1671 + wp_send_json_success(array('saved' => true));
1672 +}
1673 +add_action('wp_ajax_mxchat_save_rating', 'mxchat_save_session_rating');
1674 +add_action('wp_ajax_nopriv_mxchat_save_rating', 'mxchat_save_session_rating');