Plugin Directory


Ignore:
Timestamp:
10/17/2026 01:47:11 PM (12 days ago)
Author:
mxchat
Message:

Release 3.2.26 - security hardening, new models (GPT-6.1 Sol, Claude 5.5 family, Grok 4.7, Gemini 4 Argon, DeepSeek Flash), automatic moves off discontinued models, token usage in Transcripts, page-builder and ACF Blocks imports

File:
1 edited

Legend:

Unmodified
Added
Removed
  • mxchat-basic/trunk/js/chat-script.js

    r3738408 r3751971  
    10651065            if (response.data && response.data.chat_mode) {
    10661066                updateChatModeIndicator(response.data.chat_mode, botId);
     1067            }
     1068
     1069            // Server-side pre-chat gate (f2cce2): this session has not completed
     1070            // the form, so show it again instead of an error bubble.
     1071            if (response.status === 'lead_capture_required') {
     1072                getElement(botId, 'chat-box').find('.bot-message.temporary-message').remove();
     1073                enableChatInput(botId);
     1074                if (typeof showEmailFormForBot === 'function') { showEmailFormForBot(botId); }
     1075                return;
    10671076            }
    10681077
    … …  
    15661575    // NOTE: Don't remove temporary message here - let replaceLastMessage handle it
    15671576    // This prevents a visual gap between thinking dots disappearing and content appearing
     1577
     1578    // Server-side pre-chat gate (f2cce2): show the form again, no error bubble.
     1579    if (data.status === 'lead_capture_required') {
     1580        getElement(botId, 'chat-box').find('.bot-message.temporary-message').remove();
     1581        enableChatInput(botId);
     1582        if (typeof showEmailFormForBot === 'function') { showEmailFormForBot(botId); }
     1583        return;
     1584    }
    15681585
    15691586    // SECURITY FIX: Check for errors FIRST
    … …  
    26782695    });
    26792696   
     2697    // Fenced code blocks go back last (M7a)
     2698    processedText = literals.restoreBlocks(processedText);
     2699
    26802700    return processedText;
    26812701}
    … …  
    26892709    function protectLiterals(text) {
    26902710        const store = [];
    2691         const out = String(text).replace(/\\([\\`*_~])|(```[\s\S]*?```)|(`[^`\n]+`)/g, function(match, escaped) {
     2711        const out = String(text).replace(/\\([\\`*_~])|(```[\s\S]*?```)|(`[^`\n]+`)/g, function(match, escaped, fenced) {
    26922712            store.push(escaped !== undefined ? escaped : formatCodeBlocks(match));
    2693             return '\uE000' + (store.length - 1) + '\uE001';
     2713            return (fenced !== undefined ? '\uE002' : '\uE000') + (store.length - 1) + '\uE001';
    26942714        });
    26952715        return {
    … …  
    26972717            restore: function(t) {
    26982718                return String(t).replace(/\uE000(\d+)\uE001/g, function(m, i) {
     2719                    return store[Number(i)];
     2720                });
     2721            },
     2722            // Fenced blocks have their own mark and go back last, after the
     2723            // table, paragraph and link rules (M7a): none of those may reach
     2724            // inside the code, so a blank line no longer splits the block and
     2725            // Copy gets the code as written.
     2726            restoreBlocks: function(t) {
     2727                return String(t).replace(/\uE002(\d+)\uE001/g, function(m, i) {
    26992728                    return store[Number(i)];
    27002729                });
    … …  
    27972826}
    27982827    function formatCodeBlocks(text) {
    2799         // Handle fenced code blocks with language specification (```language)
    2800         text = text.replace(/```(\w+)?\n?([\s\S]*?)```/g, (match, language, code) => {
    2801             const lang = language || 'text';
    2802             const escapedCode = escapeHtml(code.trim());
    2803             return `<div class="mxchat-code-block-container">
    2804                 <div class="mxchat-code-header">
    2805                     <span class="mxchat-code-language">${lang}</span>
    2806                     <button class="mxchat-copy-button" aria-label="Copy to clipboard">Copy</button>
    2807                 </div>
    2808                 <pre class="mxchat-code-block"><code class="language-${lang}">${escapedCode}</code></pre>
    2809             </div>`;
     2828        // Fenced code blocks (```language) and inline code in ONE pass (M7a):
     2829        // the inline rule used to run over the finished block and turn a
     2830        // backtick inside the code into an inline-code span.
     2831        text = text.replace(/```(\w+)?\n?([\s\S]*?)```|`([^`\n]+)`/g, (match, language, code, inline) => {
     2832            if (inline !== undefined) {
     2833                return '<code class="mxchat-inline-code">' + escapeInlineCode(inline) + '</code>';
     2834            }
     2835            return codeBlockHtml(language || 'text', escapeHtml(code.trim()));
    28102836        });
    2811 
    2812         // Handle inline code with single backticks
    2813         text = text.replace(/`([^`\n]+)`/g, '<code class="mxchat-inline-code">$1</code>');
    28142837
    28152838        // Handle raw PHP tags (legacy support)
    28162839        text = text.replace(/(<\?php[\s\S]*?\?>)/g, (match) => {
    2817             const escapedCode = escapeHtml(match);
    2818             return `<div class="mxchat-code-block-container">
    2819                 <div class="mxchat-code-header">
    2820                     <span class="mxchat-code-language">php</span>
    2821                     <button class="mxchat-copy-button" aria-label="Copy to clipboard">Copy</button>
    2822                 </div>
    2823                 <pre class="mxchat-code-block"><code class="language-php">${escapedCode}</code></pre>
    2824             </div>`;
     2840            return codeBlockHtml('php', escapeHtml(match));
    28252841        });
    28262842
    28272843        return text;
     2844    }
     2845
     2846    // The code box as one line of markup (M7a): .bot-message keeps newlines
     2847    // (white-space: pre-line), so the indented template it was built from
     2848    // drew an empty band above the header and another under it.
     2849    function codeBlockHtml(lang, escapedCode) {
     2850        return '<div class="mxchat-code-block-container">' +
     2851            '<div class="mxchat-code-header">' +
     2852                '<span class="mxchat-code-language">' + lang + '</span>' +
     2853                '<button class="mxchat-copy-button" aria-label="Copy to clipboard">Copy</button>' +
     2854            '</div>' +
     2855            '<pre class="mxchat-code-block"><code class="language-' + lang + '">' + escapedCode + '</code></pre>' +
     2856        '</div>';
    28282857    }
    28292858
    … …  
    28972926    }
    28982927
     2928    // Text and attribute safe: the result also lands in alt="" for image
     2929    // search results, so quotes are escaped too (CVE-2026-110662 follow-up).
    28992930    function sanitizeUserInput(text) {
    2900         const div = document.createElement('div');
    2901         div.textContent = text;
    2902         return div.innerHTML;
     2931        if (text === null || typeof text === 'undefined') return '';
     2932        return String(text)
     2933            .replace(/&/g, '&amp;')
     2934            .replace(/</g, '&lt;')
     2935            .replace(/>/g, '&gt;')
     2936            .replace(/"/g, '&quot;')
     2937            .replace(/'/g, '&#039;');
    29032938    }
    29042939
    … …  
    29202955
    29212956    function escapeHtml(unsafe) {
    2922         // Skip escaping if it's already escaped or contains HTML code block markup
    2923         if (unsafe.includes('&lt;') || unsafe.includes('&gt;') ||
    2924             unsafe.includes('<pre><code') || unsafe.includes('</code></pre>')) {
    2925             return unsafe;
     2957        // Code text is escaped exactly once (M7a). Code that arrives already
     2958        // escaped (&lt; / &gt;) is decoded first, so it shows the same
     2959        // characters as before; it used to be returned unescaped, so raw
     2960        // markup next to an entity was inserted as markup.
     2961        if (unsafe.includes('&lt;') || unsafe.includes('&gt;')) {
     2962            unsafe = decodeHTMLEntities(unsafe);
    29262963        }
    29272964       
    … …  
    29342971    }
    29352972   
     2973    // Inline code text, escaped exactly once like a code block's (M7b), so it
     2974    // shows the characters as written. Quotes stay as typed: the span goes back
     2975    // before the link rules, and a URL after &quot; would be linked where one
     2976    // after a quote is not.
     2977    function escapeInlineCode(code) {
     2978        if (code.includes('&lt;') || code.includes('&gt;')) {
     2979            code = decodeHTMLEntities(code);
     2980        }
     2981        return code
     2982            .replace(/&/g, "&amp;")
     2983            .replace(/</g, "&lt;")
     2984            .replace(/>/g, "&gt;");
     2985    }
     2986
    29362987    function decodeHTMLEntities(text) {
    29372988        var textArea = document.createElement('textarea');
    … …  
    35283579    function initializeChatVisibility(botId) {
    35293580        botId = botId || 'default';
    3530         const complianzEnabled = mxchatChat.complianz_toggle === 'on' ||
    3531                                 mxchatChat.complianz_toggle === '1' ||
    3532                                 mxchatChat.complianz_toggle === 1;
     3581        // The Complianz setting arrives as "1" from the normal loader
     3582        // (wp_localize_script), as a JSON true from the delayed and On User
     3583        // Interaction loader, or as "on" as the settings store it: every
     3584        // loading mode asks for consent the same way (M7g).
     3585        const complianzToggle = mxchatChat.complianz_toggle;
     3586        const complianzEnabled = complianzToggle === true ||
     3587                                complianzToggle === 'on' ||
     3588                                complianzToggle === '1' ||
     3589                                complianzToggle === 1;
    35333590
    35343591        if (complianzEnabled && typeof cmplz_has_consent === "function" && typeof complianz !== 'undefined') {
    … …  
    35413598            });
    35423599        } else {
    3543             // If Complianz is not enabled, always show
     3600            // If Complianz is not enabled (or its script is not on the page),
     3601            // always show. The window's no-consent mark is cleared with the
     3602            // launcher's, so once this script has run both marks mean the same
     3603            // thing in every loading mode: consent is needed and not given.
    35443604            getElement(botId, 'floating-chatbot-button')
    35453605                .css('display', 'flex')
    35463606                .removeClass('hidden no-consent')
    35473607                .fadeTo(500, 1);
     3608            getElement(botId, 'floating-chatbot').removeClass('no-consent');
    35483609
    35493610            // Also check pre-chat message when Complianz is not enabled
    … …  
    41334194        if (visitorName && visitorName.trim()) {
    41344195            // Escape HTML to prevent XSS
    4135             var safeName = $('<div>').text(visitorName.trim()).html();
     4196            var safeName = $('<div>').text(visitorName.trim()).html().replace(/"/g, '&quot;').replace(/'/g, '&#039;');
    41364197            html = html.replace(/\{visitor_name\}/gi, safeName);
    41374198        } else {
    … …  
    46314692    initializeAllInstances();
    46324693
    4633     // Legacy initialization for single bot compatibility
     4694    // Legacy initialization for single bot compatibility. A launcher kept back
     4695    // for cookie consent (no-consent) is left to initializeChatVisibility():
     4696    // un-hiding it here painted it for the length of its fade-out (M7g).
    46344697    $('.floating-chatbot.hidden').each(function() {
    46354698        var botId = getBotIdFromElement(this);
    4636         getElement(botId, 'floating-chatbot-button').removeClass('hidden');
     4699        getElement(botId, 'floating-chatbot-button').not('.no-consent').removeClass('hidden');
    46374700    });
    46384701
Note: See TracChangeset for help on using the changeset viewer.