/** * Common JavaScript library for Rhymix, based on XpressEngine 1.x */ /** * ============= * Rhymix object * ============= */ const Rhymix = window.Rhymix = { addedDocument: [], langCodes: {}, loadedPopupMenus: [], openWindowList: {}, currentDebugData: null, pendingDebugData: [], showAjaxErrors: ['ALL'], unloading: false, modal: {}, state: {} }; /** * Check if the current device is a mobile device. * * @return bool */ Rhymix.isMobile = function() { return String(navigator.userAgent).match(/mobile/i); }; /** * Get the current color scheme * * @return string */ Rhymix.getColorScheme = function() { if ($('body').hasClass('color_scheme_dark')) { return 'dark'; } else { return 'light'; } }; /** * Set the color scheme * * @param string color_scheme * @return void */ Rhymix.setColorScheme = function(color_scheme) { if (color_scheme === 'dark' || color_scheme === 'light') { $('body').addClass('color_scheme_' + color_scheme).removeClass('color_scheme_' + (color_scheme === 'dark' ? 'light' : 'dark')); this.cookie.set('rx_color_scheme', color_scheme, { path: this.URI(default_url).pathname(), expires: 365 }); } else { this.cookie.remove('rx_color_scheme', { path: this.URI(default_url).pathname() }); color_scheme = (window.matchMedia && window.matchMedia('(prefers-color-scheme:dark)').matches) ? 'dark' : 'light'; $('body').addClass('color_scheme_' + color_scheme).removeClass('color_scheme_' + (color_scheme === 'dark' ? 'light' : 'dark')); } }; /** * Automatically detect the color scheme * * @return void */ Rhymix.detectColorScheme = function() { // Return if a color scheme is already selected. const body_element = $('body'); if(body_element.hasClass('color_scheme_light') || body_element.hasClass('color_scheme_dark')) { return; } // Detect the cookie. let color_scheme = this.cookie.get('rx_color_scheme'); // Detect the device color scheme. let match_media = window.matchMedia ? window.matchMedia('(prefers-color-scheme:dark)') : null; if (color_scheme !== 'light' && color_scheme !== 'dark') { color_scheme = (match_media && match_media.matches) ? 'dark' : 'light'; } // Set the body class according to the detected color scheme. body_element.addClass('color_scheme_' + color_scheme); // Add an event listener to detect changes to the device color scheme. match_media && match_media.addListener && match_media.addListener(function(e) { if (e.matches) { body_element.removeClass('color_scheme_light').addClass('color_scheme_dark'); } else { body_element.removeClass('color_scheme_dark').addClass('color_scheme_light'); } }); }; /** * Get the language * * @return string */ Rhymix.getLangType = function() { return window.current_lang; }; /** * Set the language * * @param string lang_type * @return void */ Rhymix.setLangType = function(lang_type) { const baseurl = this.getBaseUrl(); if (baseurl !== '/') { this.cookie.remove('lang_type', { path: '/' }); } this.cookie.set('lang_type', lang_type, { path: baseurl, expires: 365 }); }; /** * Get CSRF token for this document * * @return string|null */ Rhymix.getCSRFToken = function() { return $("meta[name='csrf-token']").attr("content"); }; /** * Set CSRF token for this document * * @param string token * @return void */ Rhymix.setCSRFToken = function(token) { $("meta[name='csrf-token']").attr("content", token); }; /** * Get the current rewrite level * * @return int */ Rhymix.getRewriteLevel = function() { return window.rewrite_level; }; /** * Get the base URL relative to the current origin * * @return string */ Rhymix.getBaseUrl = function() { if (!this.state.baseUrl) { this.state.baseUrl = this.URI(default_url).pathname(); } return this.state.baseUrl; }; /** * Get the full default URL * * @return string */ Rhymix.getDefaultUrl = function() { return window.default_url; }; /** * Get the current page's long URL * * @return string */ Rhymix.getCurrentUrl = function() { return window.current_url; }; /** * Get the current page prefix (mid) * * @return string */ Rhymix.getCurrentUrlPrefix = function() { return window.current_mid; }; /** * Check if a URL is identical to the current page URL except for the hash * * @param string url * @return bool */ Rhymix.isCurrentUrl = function(url) { const absolute_url = window.location.href; const relative_url = window.location.pathname + window.location.search; return url === absolute_url || url === relative_url || url.indexOf(absolute_url.replace(/#.+$/, "") + "#") === 0 || url.indexOf(relative_url.replace(/#.+$/, "") + "#") === 0; }; /** * Check if two URLs belong to the same origin * * @param string url1 * @param string url2 * @return bool */ Rhymix.isSameOrigin = function(url1, url2) { if(!url1 || !url2) { return false; } if (url1.match(/^\.?\/[^\/]*/) || url2.match(/^\.?\/[^\/]*/)) { return true; } if (url1.match(/^(https?:)?\/\/[^\/]*[^a-z0-9\/.:_-]/i) || url2.match(/^(https?:)?\/\/[^\/]*[^a-z0-9\/.:_-]/i)) { return false; } try { url1 = this.URI(url1).normalizePort().normalizeHostname().normalizePathname().origin(); url2 = this.URI(url2).normalizePort().normalizeHostname().normalizePathname().origin(); return (url1 === url2) ? true : false; } catch (err) { return false; } } /** * Check if a URL belongs to the same host as the current page * * Note that this function does not check the protocol. * It is therefore a weaker check than isSameOrigin(). * * @param string url * @return bool */ Rhymix.isSameHost = function(url) { if (typeof url !== 'string') { return false; } if (url.match(/^\.?\/[^\/]/)) { return true; } if (url.match(/^\w+:[^\/]*$/) || url.match(/^(https?:)?\/\/[^\/]*[^a-z0-9\/.:_-]/i)) { return false; } if (!this.state.partialOrigin) { let uri = this.URI(window.request_uri).normalizePort().normalizeHostname().normalizePathname(); this.state.partialOrigin = uri.hostname() + uri.directory(); } try { let target_url = this.URI(url).normalizePort().normalizeHostname().normalizePathname(); if (target_url.is('urn')) { return false; } if (!target_url.hostname()) { target_url = target_url.absoluteTo(window.request_uri); } target_url = target_url.hostname() + target_url.directory(); return target_url.indexOf(this.state.partialOrigin) === 0; } catch(err) { return false; } }; /** * Redirect to a URL, but reload instead if the target is the same as the current page * * @param string url * @return void */ Rhymix.redirectToUrl = function(url) { if (this.isCurrentUrl(url)) { window.location.href = url; window.location.reload(); } else { window.location.href = url; } }; /** * Open a new window and focus it * * @param string url * @param string target * @param string features * @return void */ Rhymix.openWindow = function(url, target, features) { // Fill default values if (typeof target === 'undefined') { target = '_blank'; } if (typeof features === 'undefined') { features = ''; } // Close any existing window with the same target name try { if (target !== '_blank' && this.openWindowList[target]) { this.openWindowList[target].close(); delete this.openWindowList[target]; } } catch(e) {} // Open using Blankshield if the target is a different site if (!this.isSameHost(url)) { window.blankshield.open(url, target, features); } else { const win = window.open(url, target, features); win.focus(); if (target !== '_blank') { this.openWindowList[target] = win; } } }; /** * Open a popup with standard features, for backward compatibility * * @param string url * @param string target * @return void */ Rhymix.openPopup = function(url, target) { const features = 'width=800,height=600,toolbars=no,scrollbars=yes,resizable=yes'; this.openWindow(url, target, features); }; /** * Save background scroll position * * @param bool pushState * @return void */ Rhymix.modal.saveBackgroundPosition = function(modal_id, pushState) { const body = $(document.body); if (!body.data('rx_scroll_position')) { body.data('rx_scroll_position', { left: $(window).scrollLeft(), top: $(window).scrollTop() }); } body.addClass('rx_modal_open'); if (pushState) { history.pushState({ modal: modal_id }, '', location.href); } }; /** * Open an HTML element as a modal * * @param string id * @return void */ Rhymix.modal.open = function(id) { this.saveBackgroundPosition(id, true); $('#' + id).addClass('active'); }; /** * Open an iframe as a modal * * @param string url * @param string target * @return void */ Rhymix.modal.openIframe = function(url, target) { const iframe = document.createElement('iframe'); const iframe_sequence = String(Date.now()) + Math.round(Math.random() * 1000000); const iframe_id = '_rx_iframe_' + iframe_sequence; iframe.setAttribute('id', iframe_id); iframe.setAttribute('class', 'rx_modal'); iframe.setAttribute('name', target || ('_rx_iframe_' + iframe_sequence)) iframe.setAttribute('src', url + '&iframe_sequence=' + iframe_sequence); iframe.setAttribute('width', '100%'); iframe.setAttribute('height', '100%'); iframe.setAttribute('style', 'position:fixed; top:0; left:0; width:100%; height:100%; border:0; z-index:999999999; background-color: #fff; overflow-y:auto'); this.saveBackgroundPosition(iframe_id, true); $(document.body).append(iframe); }; /** * Close currently open modal * * @param string id * @return void */ Rhymix.modal.close = function(id) { history.back(); /* if (typeof id === 'string') { $('#' + id).remove(); } else { $('.rx_modal').remove(); } */ }; /** * Make an AJAX request * * @param string action * @param object params * @param function success * @param function error * @return void */ Rhymix.ajax = function(action, params, success, error) { // Extract module and act let isFormData = params instanceof FormData; let module, act; if (!action) { if (isFormData) { module = params.get('module'); act = params.get('act'); if (module && act) { action = module + '.' + act; } else if (act) { action = act; } else { action = null; } } else { action = null; } } else { action = action.split('.'); params = params || {}; params.module = module = action[0]; params.act = act = action[1]; action = action.join('.'); } // Add action to URL if the current rewrite level supports it let url = this.URI(window.request_uri).pathname() + 'index.php'; if (act) { url = url + '?act=' + act; } /* if (this.getRewriteLevel() >= 2 && action !== null) { url = url + action.replace('.', '/'); } else { url = url + 'index.php'; } */ // Add a CSRF token to the header, and remove it from the parameters const headers = { 'X-CSRF-Token': getCSRFToken() }; if (isFormData && params.has('_rx_csrf_token') && params.get('_rx_csrf_token') === headers['X-CSRF-Token']) { params.delete('_rx_csrf_token'); } if (typeof params._rx_csrf_token !== 'undefined' && params._rx_csrf_token === headers['X-CSRF-Token']) { delete params._rx_csrf_token; } // Generate AJAX parameters const args = { type: 'POST', dataType: 'json', url: url, data: isFormData ? params : JSON.stringify(params), contentType: isFormData ? false : 'application/json; charset=UTF-8', processData: false, headers: headers, success: function(data, textStatus, xhr) { Rhymix._ajaxSuccessHandler(xhr, textStatus, action, data, params, success, error); }, error: function(xhr, textStatus, errorThrown) { Rhymix._ajaxErrorHandler(xhr, textStatus, action, url, params, success, error); } }; // Send the AJAX request try { $.ajax(args); } catch(e) { alert(e); } }; /** * Default success handler for AJAX requests * * @param object xhr * @param string textStatus * @param string action * @param object data * @param object params * @param function success * @param function errror * @return void */ Rhymix._ajaxSuccessHandler = function(xhr, textStatus, action, data, params, success, error) { // Add debug information. if (data._rx_debug) { data._rx_debug.page_title = "AJAX : " + action; if (this.addDebugData) { this.addDebugData(data._rx_debug); } else { this.pendingDebugData.push(data._rx_debug); } } // If the response contains a Rhymix error code, display the error message. if (typeof data.error !== 'undefined' && data.error != 0) { // If an error callback is defined, call it. Abort if it returns false. if ($.isFunction(error) && error(data, xhr) === false) { return; } // If an error message was supplied, display it. if (data.message) { let msg = data.message.replace(/\\n/g, "\n"); if (data.errorDetail) { msg += "\n\n" + data.errorDetail; } alert(msg); return; } // Rhymix should never return an error code without a message, but if someone does, we handle it here. let msg = 'AJAX error: ' + (action || 'form submission') + "\n\n" + xhr.responseText; if (msg.length > 1000) { msg = msg.substring(0, 1000) + '...'; } console.error(msg.trim().replace(/\n+/g, "\n")); if (this.showAjaxErrors.indexOf('ALL') >= 0 || this.showAjaxErrors.indexOf(xhr.status) >= 0) { alert(msg.trim()); } return; } // If a success callback was defined, call it. if ($.isFunction(success)) { success(data, xhr); return; } // If the response contains a redirect URL, follow the redirect. if (data.redirect_url) { this.redirectToUrl(data.redirect_url.replace(/&/g, '&')); return; } }; /** * Default error handler for AJAX requests * * @param object xhr * @param string textStatus * @param string action * @param string url * @param object params * @param function success * @param function errror * @return void */ Rhymix._ajaxErrorHandler = function(xhr, textStatus, action, url, params, success, error) { // If the user is navigating away, don't do anything. if (xhr.status == 0 && this.unloading) { return; } // If the response contains valid JSON, call the success callback instead. if (xhr.status >= 400 && xhr.responseText) { let data; try { data = JSON.parse(xhr.responseText); } catch (e) { } if (data && typeof data.error !== 'undefined') { this._ajaxSuccessHandler(xhr, textStatus, action, data, params, success, error); return; } } // If an error callback is defined, call it. Abort if it returns false. if ($.isFunction(error)) { let fakedata = { error: -3, message: textStatus }; if (error(fakedata, xhr) === false) { return; } } // Otherwise, generate a simple error message. let error_info, msg; if (xhr.status == 0) { error_info = 'Connection failed: ' + url + "\n\n" + (xhr.responseText || ''); } else { error_info = 'Response code: ' + xhr.status + "\n\n" + (xhr.responseText || ''); } msg = 'AJAX error: ' + (action || 'form submission') + "\n\n" + error_info; if (msg.length > 1000) { msg = msg.substring(0, 1000) + '...'; } // Print the error message. console.error(msg.trim().replace(/\n+/g, "\n")); if (this.showAjaxErrors.indexOf('ALL') >= 0 || this.showAjaxErrors.indexOf(xhr.status) >= 0) { alert(msg.trim()); } }; /** * Submit a form using AJAX instead of navigating away * * @param HTMLElement form * @param function success * @param function error * @return void */ Rhymix.ajaxForm = function(form, success, error) { const $form = $(form); // Get success and error callback functions. if (typeof success === 'undefined') { success = $form.data('callbackSuccess'); if (success && $.isFunction(success)) { // no-op } else if (success && window[success] && $.isFunction(window[success])) { success = window[success]; } else { success = function(data) { if (data.message && data.message !== 'success') { alert(data.message); } if (data.redirect_url) { Rhymix.redirectToUrl(data.redirect_url.replace(/&/g, '&')); } }; } } if (typeof error === 'undefined') { error = $form.data('callbackError'); if (error && $.isFunction(error)) { // no-op } else if (error && window[error] && $.isFunction(window[error])) { error = window[error]; } else { error = null; } } this.ajax(null, new FormData($form[0]), success, error); }; /** * Toggle all checkboxes that have the same name * * This is a legacy function. Do not write new code that relies on it. * * @param string name * @return void */ Rhymix.checkboxToggleAll = function(name) { if (!window[name]) { name='cart'; } let options = { wrap : null, checked : 'toggle', doClick : false }; if (arguments.length == 1) { if(typeof(arguments[0]) == 'string') { name = arguments[0]; } else { $.extend(options, arguments[0] || {}); name = 'cart'; } } else { name = arguments[0]; $.extend(options, arguments[1] || {}); } if (options.doClick === true) { options.checked = null; } if (typeof options.wrap === 'string') { options.wrap = '#' + options.wrap; } let obj; if (options.wrap) { obj = $(options.wrap).find('input[name="'+name+'"]:checkbox'); } else { obj = $('input[name="'+name+'"]:checkbox'); } if (options.checked === 'toggle') { obj.each(function() { $(this).prop('checked', $(this).prop('checked') ? false : true); }); } else { if(options.doClick === true) { obj.click(); } else { obj.prop('checked', options.checked); } } }; /** * Display a popup menu for members, documents, etc. * * @param object ret_obj * @param object response_tags * @param object params * @return void */ Rhymix.displayPopupMenu = function(ret_obj, response_tags, params) { const menu_id = params.menu_id; const menus = ret_obj.menus; let html = ""; if (this.loadedPopupMenus[menu_id]) { html = this.loadedPopupMenus[menu_id]; } else { if (menus) { let item = menus.item || menus; if (typeof item.length === 'undefined' || item.length < 1) { item = new Array(item); } if (item.length) { for (let i = 0; i < item.length; i++) { var url = item[i].url; var str = item[i].str; var classname = item[i]['class']; var icon = item[i].icon; var target = item[i].target; // Convert self to _self #2154 if (target === 'self') { target = '_self'; } var actmatch = url.match(/\bact=(\w+)/) || url.match(/\b((?:disp|proc)\w+)/); var act = actmatch ? actmatch[1] : null; var classText = 'class="' + (classname ? classname : (act ? (act + ' ') : '')); var styleText = ""; var click_str = ""; var matches = []; if (target === 'popup') { if (this.isMobile()) { click_str = 'onclick="openModalIframe(this.href, \''+target+'\'); return false;"'; } else { click_str = 'onclick="popopen(this.href, \''+target+'\'); return false;"'; } classText += 'popup '; } else if (target === 'javascript') { click_str = 'onclick="'+url+'; return false; "'; classText += 'javascript '; url = '#'; } else if (target.match(/^_(self|blank|parent|top)$/)) { click_str = 'target="' + target + '"'; classText += 'frame_' + target + ' '; } else if (matches = target.match(/^i?frame:([a-zA-Z0-9_]+)$/)) { click_str = 'target="' + matches[1] + '"'; classText += 'frame_' + matches[1] + ' '; } else { click_str = 'target="_blank"'; } classText = classText.trim() + '" '; html += '
';
html += '';
}
return html;
}
/**
* Convert rgb(r,g,b) to HEX
*
* @param string value
* @return string
*/
function transRGB2Hex(value) {
if (!value) {
return value;
}
if (value.indexOf('#') > -1) {
return value.replace(/^#/, '');
}
if (value.toLowerCase().indexOf('rgb') < 0) {
return value;
}
value = value.replace(/^rgb\(/i, '').replace(/\)$/, '');
value_list = value.split(',');
var hex = '';
for (var i = 0; i < value_list.length; i++) {
var color = parseInt(String(value_list[i]).trim(), 10).toString(16);
if (color.length == 1) {
color = '0' + color;
}
hex += color;
}
return hex;
}
/**
* Send an email
*
* @deprecated
* @param string email_address
* @return void
*/
function sendMailTo(email_address) {
location.href = 'mailto:' + email_address;
}
/**
* View skin information
*
* @deprecated
* @param string module
* @param string skin
* @return void
*/
function viewSkinInfo(module, skin) {
const url = './?module=module&act=dispModuleSkinInfo&selected_module=' + module + '&skin=' + skin;
popopen(url, 'SkinInfo');
}
/**
* Sleep for seconds
*
* @deprecated
* @param float sec
* @return void
*/
function xSleep(sec) {
sec = sec / 1000;
var now = new Date();
var sleep = new Date();
while (sleep.getTime() - now.getTime() < sec) {
sleep = new Date();
}
}
/**
* Check if any argument is a defined variable
*
* @deprecated
* @param mixed arguments
* @return bool
*/
function isDef() {
for (let i = 0; i < arguments.length; ++i) {
if (typeof(arguments[i]) == 'undefined') {
return false;
}
}
return true;
}
/**
* Check if a variable is defined and not null
*
* This seems to be similar to isset() in PHP
*
* @deprecated
* @param mixed v
* @return bool
*/
function is_def(v) {
return typeof(v) != 'undefined' && v !== null;
}
/**
* Convert the first character of a string to uppercase
*
* @deprecated
* @param string str
* @return string
*/
function ucfirst(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
/**
* Get an element by ID
*
* @deprecated
* @param string id
* @return HTMLElement|null
*/
function get_by_id(id) {
return document.getElementById(id);
}
/**
* Get the left position of an object
*
* @deprecated
* @param HTMLElement obj
* @return int
*/
function GetObjLeft(obj) {
return $(obj).offset().left;
}
/**
* Get the top position of an object
*
* @deprecated
* @param HTMLElement obj
* @return int
*/
function GetObjTop(obj) {
return $(obj).offset().top;
}
/**
* Get the outer HTML of an object
*
* @deprecated
* @param HTMLElement obj
* @return string
*/
function getOuterHTML(obj) {
return $(obj).html().trim();
}
/**
* Replace the entire object with the given HTML
*
* @deprecated
* @param HTMLElement obj
* @param string html
* @return void
*/
function replaceOuterHTML(obj, html) {
$(obj).replaceWith(html);
}
/**
* Show or hide an element
*
* @deprecated
* @param string id
* @return void
*/
function toggleDisplay(id) {
$('#' + id).toggle();
}
/**
* Toggle between HTTP and HTTPS
*
* @deprecated
* @return void
*/
function toggleSecuritySignIn() {
var href = location.href;
if (/https:\/\//i.test(href)) {
location.href = href.replace(/^https/i,'http');
} else {
location.href = href.replace(/^http/i,'https');
}
}
/**
* Display a message and reload the page
*
* @deprecated
* @param object ret_obj
* @return void
*/
function completeMessage(ret_obj) {
alert(ret_obj.message);
location.reload();
}
/**
* Just reload the current page
*
* @deprecated
* @return void
*/
function reloadDocument() {
location.reload();
}
/**
* Open a calendar popup
*
* @deprecated
* @param string fo_id
* @param string day_str
* @param function callback_func
* @return void
*/
function open_calendar(fo_id, day_str, callback_func) {
console.warn('open_calendar() is a no-op in Rhymix');
}
/**
* Display a popup menu
*
* @deprecated
* @param object ret_obj
* @param object response_tags
* @param object params
* @return void
*/
function displayPopupMenu(ret_obj, response_tags, params) {
Rhymix.displayPopupMenu(ret_obj, response_tags, params);
}
/**
* Create a popup menu
*
* @deprecated
* @return void
*/
function createPopupMenu() {
console.warn('createPopupMenu() is a no-op in Rhymix');
}
/**
* Check (?) a popup menu
*
* @deprecated
* @return void
*/
function chkPopupMenu() {
console.warn('chkPopupMenu() is a no-op in Rhymix');
}
/**
* These functions were used in xpresseditor
*/
function zbxe_folder_open(id) {
$("#folder_open_" + id).hide();
$("#folder_close_" + id).show();
$("#folder_" + id).show();
}
function zbxe_folder_close(id) {
$("#folder_open_" + id).show();
$("#folder_close_" + id).hide();
$("#folder_" + id).hide();
}
function svc_folder_open(id) {
$("#_folder_open_" + id).hide();
$("#_folder_close_" + id).show();
$("#_folder_" + id).show();
}
function svc_folder_close(id) {
$("#_folder_open_" + id).show();
$("#_folder_close_" + id).hide();
$("#_folder_" + id).hide();
}
/**
* Shims for old variable names and functions
*/
var loaded_popup_menus = Rhymix.loadedPopupMenus;
var objectExtend = $.extend;
var ssl_actions = [];
if (typeof(resizeImageContents) == 'undefined') {
window.resizeImageContents = function() {};
}
if (typeof(activateOptionDisabled) == 'undefined') {
window.activateOptionDisabled = function() {};
}
/**
* Shim for Modernizr if it is not loaded
*/
if (!window.Modernizr) {
window.Modernizr = {
audio: true,
video: true,
canvas: true,
history: true,
postmessage: true,
geolocation: ('geolocation' in navigator),
touch: ('ontouchstart' in window) || (navigator.maxTouchPoints > 0),
webgl: !!window.WebGLRenderingContext
};
}
/**
* Shim for base64 encoding and decoding
*
* http://www.webtoolkit.info/
*/
const Base64 = {
// private property
_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
// public method for encoding
encode : function (input) {
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
input = Base64._utf8_encode(input);
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output +
this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
}
return output;
},
// public method for decoding
decode : function (input) {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
while (i < input.length) {
enc1 = this._keyStr.indexOf(input.charAt(i++));
enc2 = this._keyStr.indexOf(input.charAt(i++));
enc3 = this._keyStr.indexOf(input.charAt(i++));
enc4 = this._keyStr.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
}
output = Base64._utf8_decode(output);
return output;
},
// private method for UTF-8 encoding
_utf8_encode : function (string) {
string = string.replace(/\r\n/g,"\n");
var utftext = "";
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
}
else if((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
}
else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
},
// private method for UTF-8 decoding
_utf8_decode : function (utftext) {
var string = "";
var i = 0;
var c = 0, c1 = 0, c2 = 0, c3 = 0;
while ( i < utftext.length ) {
c = utftext.charCodeAt(i);
if (c < 128) {
string += String.fromCharCode(c);
i++;
}
else if((c > 191) && (c < 224)) {
c2 = utftext.charCodeAt(i+1);
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
i += 2;
}
else {
c2 = utftext.charCodeAt(i+1);
c3 = utftext.charCodeAt(i+2);
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
i += 3;
}
}
return string;
}
};