Plugin Directory


Ignore:
Timestamp:
10/17/2026 01:47:11 PM (11 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/mxchat_transcripts.js

    r3704282 r3751971  
    44 */
    55jQuery(document).ready(function($) {
     6    // Read-only transcripts role (plan 93999a): the server sends can_manage '0'
     7    // and the delete controls this script draws (chat-list selection boxes, the
     8    // per-lead Delete button) are left out. Administrators get no flag and keep
     9    // every control.
     10    const canManage = !(typeof mxchatAdmin !== 'undefined' && mxchatAdmin && mxchatAdmin.can_manage === '0');
     11
    612    // ==========================================================================
    713    // Sidebar Navigation
    … …  
    259265    // Render a rating badge for a session row. Uses inline SVG so the glyph
    260266    // renders the same across OSes (emoji fonts vary). Tooltip surfaces the
    261     // optional feedback text.
     267    // optional feedback text. The badge is built with DOM APIs so visitor
     268    // feedback only ever reaches the page through setAttribute(), never through
     269    // an HTML string (CVE-2026-110662).
     270    const RATING_ICON_UP = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 10v12"/><path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H7"/><path d="M3 10h4"/></svg>';
     271    const RATING_ICON_DOWN = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 14V2"/><path d="M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H17"/><path d="M21 14h-4"/></svg>';
     272
    262273    function renderRatingBadge(session) {
    263274        const value = (session && typeof session.rating_value === 'number') ? session.rating_value : null;
    264         const feedback = (session && session.rating_feedback) ? session.rating_feedback : '';
     275        const feedback = (session && session.rating_feedback) ? String(session.rating_feedback) : '';
     276        const badge = document.createElement('span');
     277        let title;
    265278        if (value === 1) {
    266             const title = feedback ? ('Visitor: ' + feedback) : 'Visitor rated this chat positively';
    267             return '<span class="mxch-chat-rating mxch-chat-rating-up" title="' + escapeHtml(title) + '" aria-label="' + escapeHtml(title) + '"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 10v12"/><path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H7"/><path d="M3 10h4"/></svg></span>';
    268         }
    269         if (value === -1) {
    270             const title = feedback ? ('Visitor: ' + feedback) : 'Visitor rated this chat negatively';
    271             return '<span class="mxch-chat-rating mxch-chat-rating-down" title="' + escapeHtml(title) + '" aria-label="' + escapeHtml(title) + '"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 14V2"/><path d="M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H17"/><path d="M21 14h-4"/></svg></span>';
    272         }
    273         return '<span class="mxch-chat-rating mxch-chat-rating-empty" title="No rating yet" aria-label="No rating yet">—</span>';
     279            badge.className = 'mxch-chat-rating mxch-chat-rating-up';
     280            title = feedback ? ('Visitor: ' + feedback) : 'Visitor rated this chat positively';
     281            badge.innerHTML = RATING_ICON_UP;
     282        } else if (value === -1) {
     283            badge.className = 'mxch-chat-rating mxch-chat-rating-down';
     284            title = feedback ? ('Visitor: ' + feedback) : 'Visitor rated this chat negatively';
     285            badge.innerHTML = RATING_ICON_DOWN;
     286        } else {
     287            badge.className = 'mxch-chat-rating mxch-chat-rating-empty';
     288            title = 'No rating yet';
     289            badge.textContent = '—';
     290        }
     291        badge.setAttribute('title', title);
     292        badge.setAttribute('aria-label', title);
     293        return badge.outerHTML;
    274294    }
    275295
    … …  
    408428            html += `
    409429                <div class="mxch-chat-item${isActive}${isSelected}" data-session-id="${escapeHtml(session.session_id)}">
    410                     <input type="checkbox" class="mxch-chat-checkbox"${isChecked}>
     430                    ${canManage ? `<input type="checkbox" class="mxch-chat-checkbox"${isChecked}>` : ''}
    411431                    <div class="mxch-chat-avatar">
    412432                        <span>${escapeHtml(session.initials)}</span>
    … …  
    565585        }
    566586
     587        // IP row — the server sends user.ip only when the guest's identifier is a
     588        // valid address; a logged-in visitor (identified by login) keeps it hidden.
     589        if (data.user && data.user.ip) {
     590            $('#mxch-detail-ip').text(data.user.ip);
     591            $('#mxch-detail-ip-row').show();
     592        } else {
     593            $('#mxch-detail-ip').text('-');
     594            $('#mxch-detail-ip-row').hide();
     595        }
     596
    567597        if (data.user.email) {
    568598            $('#mxch-detail-email').text(data.user.email);
    … …  
    592622        } else {
    593623            $('#mxch-clicked-section').hide();
     624        }
     625
     626        // Chat token total (plan 705b6c): text APIs only; hidden when no reply
     627        // recorded usage (every chat saved before 3.2.26).
     628        const usageTotal = (data && data.usage_total && data.usage_total.label) ? data.usage_total : null;
     629        if (usageTotal) {
     630            $('#mxch-conversation-usage-value').text(String(usageTotal.label));
     631            $('#mxch-conversation-usage').attr('title', String(usageTotal.title || '')).prop('hidden', false);
     632        } else {
     633            $('#mxch-conversation-usage-value').text('');
     634            $('#mxch-conversation-usage').removeAttr('title').prop('hidden', true);
    594635        }
    595636
    … …  
    616657                    ? '<span class="mxch-agent-label">Live Agent</span>'
    617658                    : '<span class="mxch-bot-label">AI Assistant</span>';
     659                // Model and tokens of this reply (plan 705b6c), beside the time.
     660                // Replies without recorded usage keep the time line alone.
     661                const usage = (!isAgent && msg.usage && msg.usage.label) ? msg.usage : null;
     662                const timeLine = `<div class="mxch-message-time">${escapeHtml(msg.timestamp)}</div>`;
     663                const footer = usage
     664                    ? `<div class="mxch-message-foot">
     665                            ${timeLine}
     666                            <div class="mxch-message-usage">${usage.model ? `<span class="mxch-message-usage-model">${escapeHtml(usage.model)}</span> ` : ''}<span class="mxch-message-usage-tokens">${escapeHtml(usage.label)}</span></div>
     667                        </div>`
     668                    : timeLine;
    618669                messagesHtml += `
    619670                    <div class="mxch-message mxch-message-bot${isAgent ? ' mxch-message-agent' : ''}" data-message-id="${msg.id}">
    … …  
    627678                            </div>
    628679                        </div>
    629                         <div class="mxch-message-time">${escapeHtml(msg.timestamp)}</div>
     680                        ${footer}
    630681                    </div>
    631682                `;
    … …  
    772823            const $bubble = $('#mxch-messages-area .mxch-message-bubble').eq(item.index);
    773824            if ($bubble.length) {
    774                 $bubble.html(item.translated);
     825                // A translation is model output from the messages' plain text
     826                // (visitor-written), so it is shown as text; line breaks kept.
     827                $bubble.html(escapeHtml(item.translated).replace(/\r?\n/g, '<br>'));
    775828                $bubble.addClass('translated');
    776829            }
    … …  
    12501303    }
    12511304
     1305    // Escapes for both text and attribute contexts (quotes included), so a value
     1306    // can never break out of title="", href="" or data-*="" (CVE-2026-110662).
    12521307    function escapeHtml(text) {
    1253         if (!text) return '';
    1254         const div = document.createElement('div');
    1255         div.textContent = text;
    1256         return div.innerHTML;
     1308        if (!text && text !== 0) return '';
     1309        return String(text)
     1310            .replace(/&/g, '&amp;')
     1311            .replace(/</g, '&lt;')
     1312            .replace(/>/g, '&gt;')
     1313            .replace(/"/g, '&quot;')
     1314            .replace(/'/g, '&#039;');
    12571315    }
    12581316
    … …  
    17261784                    </button>`
    17271785                : '';
    1728             const deleteBtn = `<button type="button" class="mxch-btn mxch-btn-ghost mxch-btn-sm mxch-btn-danger-ghost mxch-leads-delete-row" data-email="${escapeHtmlLeads(emailKey)}" title="Delete lead">
     1786            const deleteBtn = !canManage ? '' : `<button type="button" class="mxch-btn mxch-btn-ghost mxch-btn-sm mxch-btn-danger-ghost mxch-leads-delete-row" data-email="${escapeHtmlLeads(emailKey)}" title="Delete lead">
    17291787                    <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
    17301788                </button>`;
Note: See TracChangeset for help on using the changeset viewer.