Changeset 3751971 for mxchat-basic/trunk/js/chat-script.js
- Timestamp:
- 10/17/2026 01:47:11 PM (12 days ago)
- File:
-
- 1 edited
-
mxchat-basic/trunk/js/chat-script.js (modified) (13 diffs)
Legend:
- Unmodified
- Added
- Removed
-
mxchat-basic/trunk/js/chat-script.js
r3738408 r3751971 1065 1065 if (response.data && response.data.chat_mode) { 1066 1066 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; 1067 1076 } 1068 1077 … … 1566 1575 // NOTE: Don't remove temporary message here - let replaceLastMessage handle it 1567 1576 // 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 } 1568 1585 1569 1586 // SECURITY FIX: Check for errors FIRST … … 2678 2695 }); 2679 2696 2697 // Fenced code blocks go back last (M7a) 2698 processedText = literals.restoreBlocks(processedText); 2699 2680 2700 return processedText; 2681 2701 } … … 2689 2709 function protectLiterals(text) { 2690 2710 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) { 2692 2712 store.push(escaped !== undefined ? escaped : formatCodeBlocks(match)); 2693 return '\uE000'+ (store.length - 1) + '\uE001';2713 return (fenced !== undefined ? '\uE002' : '\uE000') + (store.length - 1) + '\uE001'; 2694 2714 }); 2695 2715 return { … … 2697 2717 restore: function(t) { 2698 2718 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) { 2699 2728 return store[Number(i)]; 2700 2729 }); … … 2797 2826 } 2798 2827 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())); 2810 2836 }); 2811 2812 // Handle inline code with single backticks2813 text = text.replace(/`([^`\n]+)`/g, '<code class="mxchat-inline-code">$1</code>');2814 2837 2815 2838 // Handle raw PHP tags (legacy support) 2816 2839 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)); 2825 2841 }); 2826 2842 2827 2843 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>'; 2828 2857 } 2829 2858 … … 2897 2926 } 2898 2927 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). 2899 2930 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, '&') 2934 .replace(/</g, '<') 2935 .replace(/>/g, '>') 2936 .replace(/"/g, '"') 2937 .replace(/'/g, '''); 2903 2938 } 2904 2939 … … 2920 2955 2921 2956 function escapeHtml(unsafe) { 2922 // Skip escaping if it's already escaped or contains HTML code block markup 2923 if (unsafe.includes('<') || unsafe.includes('>') || 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 (< / >) 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('<') || unsafe.includes('>')) { 2962 unsafe = decodeHTMLEntities(unsafe); 2926 2963 } 2927 2964 … … 2934 2971 } 2935 2972 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 " would be linked where one 2976 // after a quote is not. 2977 function escapeInlineCode(code) { 2978 if (code.includes('<') || code.includes('>')) { 2979 code = decodeHTMLEntities(code); 2980 } 2981 return code 2982 .replace(/&/g, "&") 2983 .replace(/</g, "<") 2984 .replace(/>/g, ">"); 2985 } 2986 2936 2987 function decodeHTMLEntities(text) { 2937 2988 var textArea = document.createElement('textarea'); … … 3528 3579 function initializeChatVisibility(botId) { 3529 3580 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; 3533 3590 3534 3591 if (complianzEnabled && typeof cmplz_has_consent === "function" && typeof complianz !== 'undefined') { … … 3541 3598 }); 3542 3599 } 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. 3544 3604 getElement(botId, 'floating-chatbot-button') 3545 3605 .css('display', 'flex') 3546 3606 .removeClass('hidden no-consent') 3547 3607 .fadeTo(500, 1); 3608 getElement(botId, 'floating-chatbot').removeClass('no-consent'); 3548 3609 3549 3610 // Also check pre-chat message when Complianz is not enabled … … 4133 4194 if (visitorName && visitorName.trim()) { 4134 4195 // Escape HTML to prevent XSS 4135 var safeName = $('<div>').text(visitorName.trim()).html() ;4196 var safeName = $('<div>').text(visitorName.trim()).html().replace(/"/g, '"').replace(/'/g, '''); 4136 4197 html = html.replace(/\{visitor_name\}/gi, safeName); 4137 4198 } else { … … 4631 4692 initializeAllInstances(); 4632 4693 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). 4634 4697 $('.floating-chatbot.hidden').each(function() { 4635 4698 var botId = getBotIdFromElement(this); 4636 getElement(botId, 'floating-chatbot-button'). removeClass('hidden');4699 getElement(botId, 'floating-chatbot-button').not('.no-consent').removeClass('hidden'); 4637 4700 }); 4638 4701
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)