/*
* ==========================================================
* MAIN SCRIPT
* ==========================================================
*
* Main Javascript file. This file is shared by frond-end and back-end. © 2017-2023 board.support. All rights reserved.
*
*/
'use strict';
(function ($) {
var version = '3.5.9';
var main;
var global;
var upload_target;
var admin = false;
var tickets = false;
var timeout = false;
var timeout_label_date = [false, false];
var interval = false;
var timeout_debounce = [];
var previous_search;
var sb_current_user = false;
var chat;
var chat_editor;
var chat_textarea;
var chat_header;
var chat_status;
var chat_emoji;
var chat_scroll_area;
var label_date;
var label_date_items = false;
var label_date_show = false;
var label_date_history = [9999999, ''];
var document_title = document.title;
var CHAT_SETTINGS = {};
var mobile = $(window).width() < 429;
var today = new Date();
var bot_id;
var force_action = '';
var dialogflow_human_takeover;
var agents_online = false;
var ND = 'undefined';
var cookies_supported = true;
var utc_offset = (new Date()).getTimezoneOffset() * 60000;
var cloud_data = false;
var articles_page = false;
var visibility_status = 'visible';
var ajax_calls;
/*
* ----------------------------------------------------------
* EXTERNAL PLUGINS
* ----------------------------------------------------------
*/
// Auto Expand Scroll Area | Schiocco
$.fn.extend({ manualExpandTextarea: function () { var t = this[0]; t.style.height = "auto", t.style.maxHeight = "25px"; window.getComputedStyle(t); t.style.height = (t.scrollHeight > 350 ? 350 : t.scrollHeight) + "px", t.style.maxHeight = "", $(t).trigger("textareaChanged") }, autoExpandTextarea: function () { var t = this[0]; t.addEventListener("input", function (e) { $(t).manualExpandTextarea() }, !1) } });
// autolink-js
(function () { var t = [].slice; String.prototype.autoLink = function () { var n, a, r, i, c, e, l; return e = /(^|[\s\n]|<[A-Za-z]*\/?>)((?:https?|ftp):\/\/[\-A-Z0-9+\u0026\u2019@#\/%?=()~_|!:,.;]*[\-A-Z0-9+\u0026@#\/%=~()_|])/gi, 0 < (c = 1 <= arguments.length ? t.call(arguments, 0) : []).length ? (i = c[0], n = i.callback, r = function () { var t; for (a in t = [], i) l = i[a], "callback" !== a && t.push(" " + a + "='" + l + "'"); return t }().join(""), this.replace(e, function (t, a, i) { return "" + a + (("function" == typeof n ? n(i) : void 0) || "" + i + "") })) : this.replace(e, "$1$2") } }).call(this);
/*
* ----------------------------------------------------------
* FUNCTIONS
* ----------------------------------------------------------
*/
var SBF = {
// Main Ajax function
ajax: function (data, onSuccess = false) {
if (ajax_calls) {
ajax_calls[0].push(data);
ajax_calls[1].push(onSuccess);
} else {
ajax_calls = [[data], [onSuccess]];
setTimeout(function () {
let onSuccessCalls = ajax_calls[1];
let data_auto = { 'login-cookie': SBF.loginCookie() }
if (activeUser()) {
data_auto['user_id'] = activeUser().id;
}
if (typeof SB_LANG != ND) {
data_auto['language'] = SB_LANG;
}
if (cloud_data) {
data_auto['cloud'] = cloud_data;
}
$.ajax({
method: 'POST',
url: SB_AJAX_URL,
data: $.extend({ function: 'ajax_calls', calls: ajax_calls[0] }, data_auto)
}).done((response) => {
let result;
if (Array.isArray(response)) {
result = response;
} else {
try {
result = JSON.parse(response);
} catch (e) {
SBChat.is_busy_update = false;
SBChat.busy(false);
console.log(response);
SBF.error(response.length > 500 ? response.substr(0, 500) + '... Check the console for more details.' : response, `SBF.ajax.${data['function']}`);
return;
}
}
for (var i = 0; i < result.length; i++) {
let result_sub = result[i];
if (!Array.isArray(result_sub)) result_sub = ['', '', '', result_sub];
if (result_sub[0] == 'success') {
onSuccess = onSuccessCalls[i];
if (onSuccess) onSuccess(result_sub[1]);
} else if (SBF.errorValidation(result_sub)) {
if (onSuccess) onSuccess(result_sub);
} else {
if (admin && result_sub[1] == 'security-error') {
setTimeout(() => { SBF.reset() }, 1000);
}
SBChat.is_busy_update = false;
SBChat.busy(false);
SBF.error(result_sub[1] + (SBF.null(result_sub[2]) ? '' : '\nFunction name: ' + result_sub[2]) + (SBF.null(result_sub[3]) ? '' : '\nError message: ' + (typeof result_sub[3] == 'string' ? result_sub[3] : ('error' in result_sub[3] && 'message' in result_sub[3]['error'] ? `${result_sub[3]['error']['message']} in function '${result_sub[3]['error']['function']}'` : result_sub[3]))), `SBF.ajax.${data['function']}`);
}
}
});
ajax_calls = false;
}, 100);
}
},
// Cors function
cors: function (method = 'GET', url, onSuccess) {
let xhr = new XMLHttpRequest();
if ('withCredentials' in xhr) {
xhr.open(method, url, true);
} else if (typeof XDomainRequest != ND) {
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
return false;
}
xhr.onload = function () {
onSuccess(xhr.responseText);
};
xhr.onerror = function () {
return false;
};
xhr.send();
},
// Uploads
upload: function (form, onSuccess) {
if (cloud_data) form.append('cloud', cloud_data);
jQuery.ajax({
url: SB_URL + '/include/upload.php',
cache: false,
contentType: false,
processData: false,
data: form,
type: 'POST',
success: function (response) {
onSuccess(response);
}
});
},
// UTC Time
UTC: function (datetime) {
return new Date(datetime).getTime() - utc_offset;
},
// Check if a variable is null or empty
null: function (obj) { if (typeof (obj) !== ND && obj !== null && obj !== 'null' && obj !== false && (obj.length > 0 || typeof (obj) == 'number' || typeof (obj.length) == ND) && obj !== ND) return false; else return true; },
// Deactivate and hide the elements
deactivateAll: function () {
global.find('.sb-popup, .sb-tooltip, .sb-list .sb-menu, .sb-select ul').sbActive(false);
},
// Deselect the content of the target
deselectAll: function () {
if (window.getSelection) { window.getSelection().removeAllRanges(); }
else if (document.selection) { document.selection.empty(); }
},
// Get URL parameters
getURL: function (name = false, url = false) {
if (!url) url = location.search;
if (name == false) {
var c = url.split('?').pop().split('&');
var p = {};
for (var i = 0; i < c.length; i++) {
var d = c[i].split('=');
p[d[0]] = SBF.escape(d[1]);
}
return p;
}
if (url.indexOf('?') > 0) {
url = url.substr(0, url.indexOf('?'));
}
return SBF.escape(decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(url) || [, ""])[1].replace(/\+/g, '%20') || ""));
},
URL: function () {
return window.location.href.substr(0, window.location.href.indexOf('?'));
},
// Convert a string to slug and inverse
stringToSlug: function (string) {
string = string.trim();
string = string.toLowerCase();
var from = "åàáãäâèéëêìíïîòóöôùúüûñç·/_,:;";
var to = "aaaaaaeeeeiiiioooouuuunc------";
for (var i = 0, l = from.length; i < l; i++) {
string = string.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
}
return string.replace(/[^a-z0-9 -]/g, '').replace(/\s+/g, '-').replace(/-+/g, '-').replace(/^-+/, '').replace(/-+$/, '').replace(/ /g, '');
},
slugToString: function (string) {
string = string.replace(/_/g, ' ').replace(/-/g, ' ');
return string.charAt(0).toUpperCase() + string.slice(1);
},
// Random string
random: function () {
let chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
let result = '';
for (var i = 5; i > 0; --i) result += chars[Math.floor(Math.random() * 62)];
return result;
},
// Check if a user type is an agent
isAgent: function (user_type) {
return user_type == 'agent' || user_type == 'admin' || user_type == 'bot';
},
// Get cors elapsed of a given date from now
beautifyTime: function (datetime, extended = false, future = false) {
let date;
if (datetime == '0000-00-00 00:00:00') return '';
if (datetime.indexOf('-') > 0) {
let arr = datetime.split(/[- :]/);
date = new Date(arr[0], arr[1] - 1, arr[2], arr[3], arr[4], arr[5]);
} else {
let arr = datetime.split(/[. :]/);
date = new Date(arr[2], arr[1] - 1, arr[0], arr[3], arr[4], arr[5]);
}
let date_string = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds()));
let diff_days = Math.round(((new Date()) - date_string) / (1000 * 60 * 60 * 24)) * (future ? -1 : 1);
let days = [sb_('Sunday'), sb_('Monday'), sb_('Tuesday'), sb_('Wednesday'), sb_('Thursday'), sb_('Friday'), sb_('Saturday')];
let time = date_string.toLocaleTimeString(navigator.language, { hour: '2-digit', minute: '2-digit' });
if (time.charAt(0) === '0' && (time.includes('PM') || time.includes('AM'))) time = time.substring(1);
if (diff_days < 1) {
return extended ? `${sb_('Today')} ${time}` : `${time}`;
} else if (diff_days < 7) {
return `${days[date_string.getDay()]}${extended ? ` ${time}` : ''}`;
} else {
return `${date_string.toLocaleDateString()}${extended ? ` ${time}` : ''}`;
}
},
// Get the unix timestamp value of a date string with format yyyy-mm-dd hh:mm:ss
unix: function (datetime) {
let arr = datetime.split(/[- :]/);
return Date.UTC(arr[0], arr[1] - 1, arr[2], arr[3], arr[4], arr[5]);
},
// Generate a string containing the agent location and time
getLocationTimeString: function (details, onSuccess) {
if ('timezone' in details) {
let location = {};
location['timezone'] = details['timezone']['value'];
location['country'] = 'country' in details ? details['country']['value'] : location['timezone'].split('/')[0].replace(/_/g, ' ');
location['city'] = 'city' in details ? details['city']['value'] : location['timezone'].split('/')[1].replace(/_/g, ' ');
SBF.cors('GET', 'https://worldtimeapi.org/api/timezone/' + location['timezone'], function (response) {
response = JSON.parse(response);
if (SBF.null(response) || !SBF.null(response['error'])) {
SBF.error(response, 'SBF.getLocationTimeString()');
onSuccess(responseonSuccess);
} else {
let datetime = response['datetime'].replace('T', ' ');
onSuccess(`${new Date(datetime.substr(0, datetime.indexOf('.'))).toLocaleString([], { hour: '2-digit', minute: '2-digit' })} ${sb_('in')} ${location['city'] ? location['city'] : ''}${location['country'] ? ', ' + location['country'] : ''}`);
}
});
}
},
// Date string
dateDB: function (date) {
if (date == 'now') {
date = (new Date).toISOString().replace('T', ' ');
if (date.indexOf('.') > 0) {
date = date.substr(0, date.indexOf('.'));
}
return date;
} else {
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
}
},
// Set and get users last activity
updateUsersActivity: function (user_id, return_user_id, onSuccess) {
if (SBPusher.active) {
onSuccess(SBPusher.online_ids.includes(return_user_id) ? 'online' : 'offline');
} else {
SBF.ajax({
function: 'update-users-last-activity',
user_id: user_id,
return_user_id: return_user_id,
check_slack: !admin && CHAT_SETTINGS['slack-active']
}, (response) => {
if (response === 'online') {
onSuccess('online');
} else {
onSuccess('offline');
}
});
}
},
// Search functions
search: function (search, searchFunction) {
search = search.toLowerCase();
if (search == previous_search) {
global.find('.sb-search-btn i').sbLoading(false);
return;
}
clearTimeout(timeout);
timeout = setTimeout(function () {
previous_search = search;
searchFunction();
}, 200);
},
searchClear: function (icon, onSuccess) {
let search = $(icon).next().val();
if (search) {
$(icon).next().val('');
onSuccess();
}
},
// Support Board error js reporting
error: function (message, function_name) {
if (admin && SBAdmin.is_logout) return;
if (message instanceof Error) message = message.message;
if (message[message.length - 1] == '.') message = message.slice(0, -1);
if (message.includes('Support Board Error')) message = message.replace('Support Board Error ', '').replace(']:', ']');
if (admin && message && !function_name.includes('update-users-last-activity') && !function_name.startsWith('security-error')) SBAdmin.dialog(`[Error] [${function_name}] ${message}. Check the console for more details.`, 'info', false, 'error');
global.find('.sb-loading').sbLoading(false);
SBF.event('SBError', { message: message, function_name: function_name });
throw new Error(`Support Board Error [${function_name}]: ${message}.`);
},
errorValidation: function (response, code = true) {
return Array.isArray(response) && response[0] === 'validation-error' && (code === true || response[1] == code);
},
// Login
loginForm: function (button, area = false, onSuccess = false) {
button = $(button);
if (!button.sbLoading()) {
if (area === false) area = button.closest('.sb-rich-login'); else area = $(area);
let email = $.trim(area.find('#email input').val());
let password = $.trim(area.find('#password input').val());
if (email == '' || password == '') {
area.find('.sb-info').html(sb_('Please insert email and password.')).sbActive(true);
} else {
SBF.ajax({
function: 'login',
email: email,
password: password
}, (response) => {
if (response && Array.isArray(response)) {
if (!admin && this.isAgent(response[0]['user_type'])) {
SBForm.showErrorMessage(area, 'You cannot sign in as an agent.');
SBChat.scrollBottom();
} else {
let user = new SBUser(response[0]);
user.set('conversation_id', SBChat.conversation ? SBChat.conversation.id : false);
this.loginCookie(response[1]);
this.event('SBLoginForm', user);
if (onSuccess) onSuccess(response);
}
if (SBF.setting('wp-users-system') == 'wp') {
SBApps.wordpress.ajax('wp-login', { user: email, password: password });
}
} else {
area.find('.sb-info').html(sb_('Invalid email or password.')).sbActive(true);
if (!admin) SBChat.scrollBottom();
}
button.sbLoading(false);
});
area.find('.sb-info').html('').sbActive(false);
button.sbLoading(true);
}
}
},
// Set the login cookie
loginCookie: function (value = false) {
if (value === false) return this.cookie('sb-login') ? this.cookie('sb-login') : storage('login');
if (CHAT_SETTINGS.cloud) {
storage('login', value);
} else {
this.cookie('sb-login', value, 3650, 'set');
}
},
// Login
login: function (email = '', password = '', user_id = '', token = '', onSuccess = false) {
SBF.ajax({
function: 'login',
email: email,
password: password,
user_id: user_id,
token: token
}, (response) => {
if (response != false && Array.isArray(response)) {
this.loginCookie(response[1]);
if (onSuccess) onSuccess(response);
return true;
} else {
return false;
}
});
},
// Logout
logout: function (reload = true) {
SBChat.stopRealTime();
this.cookie('sb-login', '', '', false);
this.cookie('sb-cloud', '', '', false);
storage('open-conversation', '');
storage('login', '');
SBChat.conversations = false;
activeUser(false);
if (typeof sb_beams_client !== ND) {
sb_beams_client.stop();
}
SBF.ajax({
function: 'logout'
}, () => {
SBF.event('SBLogout');
if (reload) {
setTimeout(() => { location.reload() }, 500);
}
});
},
// Return the active user
activeUser: function () {
return activeUser();
},
// Get the active user
getActiveUser: function (database = false, onSuccess) {
let app_login = SBApps.login();
if (!app_login && (storage('wp-login') || storage('whmcs-login') || storage('perfex-login') || storage('aecommerce-login'))) {
this.cookie('sb-login', '', '', 'delete');
activeUser(false);
storage('login', '');
storage('wp-login', '');
storage('whmcs-login', '');
storage('perfex-login', '');
storage('aecommerce-login', '');
}
SBF.ajax({
function: 'get-active-user',
db: database,
login_app: JSON.stringify(app_login),
user_token: SBF.getURL('token')
}, (response) => {
if (!response) {
onSuccess();
return false;
} else {
if ('cookie' in response) SBF.loginCookie(response['cookie']);
if ('user_type' in response) {
if (!admin && SBF.isAgent(response['user_type'])) {
let message = 'You are logged in as both agent and user. Logout or use another browser, Incognito or Private mode, to login as user. Force a logout by running the function SBF.reset() in the console.';
if (!storage('double-login-alert')) {
storage('double-login-alert', true);
alert(message);
}
console.warn('Support Board: ' + message);
SBF.event('SBDoubleLoginError');
} else {
activeUser(new SBUser(response, 'phone' in response ? { phone: response.phone } : {}));
SBPusher.start();
if (app_login) {
storage(app_login[1] + '-login', true);
}
onSuccess();
SBF.event('SBActiveUserLoaded', response);
}
}
}
});
},
// Clean
reset: function () {
let cookies = ['sb-login', 'sb-cloud', 'sb-dialogflow-disabled'];
for (var i = 0; i < cookies.length; i++) {
this.cookie(cookies[i], '', 0, false);
}
try { localStorage.removeItem('support-board') } catch (e) { }
this.logout();
},
// Lightbox
lightbox: function (content) {
let lightbox = $(admin ? global : main).find('.sb-lightbox-media');
lightbox.sbActive(true).find(' > div').html(content);
if (admin) SBAdmin.open_popup = lightbox;
},
// Manage the local storage
storage: function (key, value = ND) {
try { if (typeof localStorage == ND) return false } catch (e) { return false }
let settings = localStorage.getItem('support-board');
if (settings === null) {
settings = {};
} else {
settings = JSON.parse(settings);
}
if (value === ND) {
return key in settings ? settings[key] : false;
} else {
if (!value) {
delete settings[key];
} else {
settings[key] = value;
}
localStorage.setItem('support-board', JSON.stringify(settings));
}
},
// Save the current time or check if the saved time is older than now plus the given hours
storageTime: function (key, hours = false) {
if (hours === false) {
storage(key, today.getTime());
} else {
return storage(key) == false || (today.getTime() - storage(key)) > (3600000 * hours);
}
},
// Set or get a cookie
cookie: function (name, value = false, expiration_days = false, action = 'get', seconds = false) {
let cookie_https = location.protocol == 'https:' ? 'SameSite=None;Secure;' : '';
let settings = window[admin ? 'SB_ADMIN_SETTINGS' : 'CHAT_SETTINGS'];
let domain = settings && settings['cookie-domain'] ? 'domain=' + settings['cookie-domain'] + ';' : '';
if (action == 'get') {
if (!cookies_supported) {
return this.storage(name);
}
let cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i];
while (cookie.charAt(0) == ' ') {
cookie = cookie.substring(1);
}
if (cookie.indexOf(name) == 0) {
let value = cookie.substring(name.length + 1, cookie.length);
return this.null(value) ? false : value;
}
}
return false;
} else if (action == 'set') {
if (!cookies_supported) {
this.storage(name, value);
} else {
let date = new Date();
date.setTime(date.getTime() + (expiration_days * (seconds ? 1 : 86400) * 1000));
document.cookie = name + "=" + value + ";expires=" + date.toUTCString() + ";path=/;" + cookie_https + domain;
}
} else if (this.cookie(name)) {
if (!cookies_supported) {
this.storage(name, '');
} else {
document.cookie = name + "=" + value + ";expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/;" + cookie_https + domain;
}
}
},
// Return a front setting
setting: function (key) {
return typeof CHAT_SETTINGS !== ND && key in CHAT_SETTINGS ? CHAT_SETTINGS[key] : false;
},
// Return the shortcode array
shortcode: function (shortcode) {
return SBRichMessages.shortcode(shortcode);
},
// Events and webhooks
event: function (name, parameters) {
$(document).trigger(name, parameters);
let webhooks = admin ? (typeof SB_ADMIN_SETTINGS === ND ? false : SB_ADMIN_SETTINGS['webhooks']) : CHAT_SETTINGS['webhooks'];
let webhooks_list = { SBSMSSent: 'sms-sent', SBLoginForm: 'login', SBRegistrationForm: 'registration', SBUserDeleted: 'user-deleted', SBNewMessagesReceived: 'new-message', SBNewConversationReceived: 'new-conversation', SBSlackMessageSent: 'slack-message-sent', SBMessageDeleted: 'message-deleted', SBRichMessageSubmit: 'rich-message', SBNewEmailAddress: 'new-email-address' };
if (webhooks && name in webhooks_list) {
if (webhooks !== true) {
if (!Array.isArray(webhooks)) webhooks = webhooks.replace(/ /g, '').split(',');
if (!webhooks.includes(webhooks_list[name])) return;
}
SBF.ajax({
function: 'webhooks',
function_name: name,
parameters: parameters
});
}
},
// Translate a string
translate: function (string) {
if ((!admin && SBF.null(CHAT_SETTINGS)) || (admin && typeof SB_TRANSLATIONS === ND)) return string;
let translations = admin ? SB_TRANSLATIONS : CHAT_SETTINGS['translations'];
if (translations && string in translations) {
return translations[string] == '' ? string : translations[string];
} else {
return string;
}
},
// Escape a string
escape: function (string) {
if (!string) return string;
return string.replaceAll('