/* ---------------------------------------------------------------------------- * Easy!Appointments - Open Source Web Scheduler * * @package EasyAppointments * @author A.Tselegidis <alextselegidis@gmail.com> * @copyright Copyright (c) 2013 - 2020, Alex Tselegidis * @license http://opensource.org/licenses/GPL-3.0 - GPLv3 * @link http://easyappointments.org * @since v1.0.0 * ---------------------------------------------------------------------------- */ window.GeneralFunctions = window.GeneralFunctions || {}; /** * General Functions Module * * It contains functions that apply both on the front and back end of the application. * * @module GeneralFunctions */ (function (exports) { 'use strict'; /** * This functions displays a message box in the admin array. It is useful when user * decisions or verifications are needed. * * @param {String} title The title of the message box. * @param {String} message The message of the dialog. * @param {Array} buttons Contains the dialog buttons along with their functions. */ exports.displayMessageBox = function (title, message, buttons) { if (!title) { title = '- No Title Provided -'; } if (!message) { message = '- No Message Provided -'; } if (!buttons) { buttons = [ { text: EALang.close, click: function () { $('#message-box').dialog('close'); } } ]; } // Destroy previous dialog instances. $('#message-box') .dialog('destroy') .remove(); // Create the html of the message box. $('<div/>', { 'id': 'message-box', 'title': title, 'html': [ $('<p/>', { 'html': message }) ] }) .appendTo('body'); $("#message-box").dialog({ autoOpen: false, modal: true, resize: 'auto', width: 'auto', height: 'auto', resizable: false, buttons: buttons, closeOnEscape: true }); $('#message-box').dialog('open'); $('.ui-dialog .ui-dialog-buttonset button').addClass('btn btn-outline-secondary'); $('#message-box .ui-dialog-titlebar-close').hide(); }; /** * This method centers a DOM element vertically and horizontally on the page. * * @param {Object} elementHandle The object that is going to be centered. */ exports.centerElementOnPage = function (elementHandle) { // Center main frame vertical middle $(window).resize(function () { var elementLeft = ($(window).width() - elementHandle.outerWidth()) / 2; var elementTop = ($(window).height() - elementHandle.outerHeight()) / 2; elementTop = elementTop > 0 ? elementTop : 20; elementHandle.css({ position: 'absolute', left: elementLeft, top: elementTop }); }); $(window).resize(); }; /** * This function retrieves a parameter from a "GET" formed url. * * @link http://www.netlobo.com/url_query_string_javascript.html * * @param {String} url The selected url. * @param {String} parameterName The parameter name. * @return {String} Returns the parameter value. */ exports.getUrlParameter = function (url, parameterName) { var parsedUrl = url.substr(url.indexOf('?')).slice(1).split('&'); for (var index in parsedUrl) { var parsedValue = parsedUrl[index].split('='); if (parsedValue.length === 1 && parsedValue[0] === parameterName) { return ''; } if (parsedValue.length === 2 && parsedValue[0] === parameterName) { return decodeURIComponent(parsedValue[1]); } } return ''; }; /** * Convert date to ISO date string. * * This function creates a RFC 3339 date string. This string is needed by the Google Calendar API * in order to pass dates as parameters. * * @param {Date} date The given date that will be transformed. * @return {String} Returns the transformed string. */ exports.ISODateString = function (date) { function pad(n) { return n < 10 ? '0' + n : n; } return date.getUTCFullYear() + '-' + pad(date.getUTCMonth() + 1) + '-' + pad(date.getUTCDate()) + 'T' + pad(date.getUTCHours()) + ':' + pad(date.getUTCMinutes()) + ':' + pad(date.getUTCSeconds()) + 'Z'; }; /** * Clone JS Object * * This method creates and returns an exact copy of the provided object. It is very useful whenever changes need to * be made to an object without modifying the original data. * * @link https://stackoverflow.com/a/728694 * * @param {Object} originalObject Object to be copied. * @return {Object} Returns an exact copy of the provided element. */ exports.clone = function (originalObject) { // Handle the 3 simple types, and null or undefined if (!originalObject || typeof originalObject !== 'object') { return originalObject; } var copy; // Handle Date if (originalObject instanceof Date) { copy = new Date(); copy.setTime(originalObject.getTime()); return copy; } // Handle Array if (originalObject instanceof Array) { copy = []; for (var i = 0, len = originalObject.length; i < len; i++) { copy[i] = GeneralFunctions.clone(originalObject[i]); } return copy; } // Handle Object if (originalObject instanceof Object) { copy = {}; for (var attr in originalObject) { if (originalObject.hasOwnProperty(attr)) copy[attr] = GeneralFunctions.clone(originalObject[attr]); } return copy; } throw new Error('Unable to copy obj! Its type isn\'t supported.'); }; /** * Validate Email Address * * This method validates an email address. If the address is not on the proper * form then the result is FALSE. * * @link http://badsyntax.co/post/javascript-email-validation-rfc822 * * @param {String} email The email address to be checked. * @return {Boolean} Returns the validation result. */ exports.validateEmail = function (email) { var re = /^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*$/; return re.test(email); }; /** * Makes the first letter of the string upper case. * * @param {String} value The string to be converted. * * @return {String} Returns the capitalized string. */ exports.upperCaseFirstLetter = function (value) { return value.charAt(0).toUpperCase() + value.slice(1); }; /** * Enable Language Selection * * Enables the language selection functionality. Must be called on every page has a language selection button. * This method requires the global variable 'availableLanguages' to be initialized before the execution. * * @param {Object} $element Selected element button for the language selection. */ exports.enableLanguageSelection = function ($element) { // Select Language var $languageList = $('<ul/>', { 'id': 'language-list', 'html': availableLanguages.map(function (availableLanguage) { return $('<li/>', { 'class': 'language', 'data-language': availableLanguage, 'text': GeneralFunctions.upperCaseFirstLetter(availableLanguage) }) }) }); $element.popover({ placement: 'top', title: 'Select Language', content: $languageList[0], html: true, container: 'body', trigger: 'manual' }); $element.on('click', function (event) { event.stopPropagation(); if ($('#language-list').length === 0) { $(this).popover('show'); } else { $(this).popover('hide'); } $(this).toggleClass('active'); }); $(document).on('click', 'li.language', function () { // Change language with ajax call and refresh page. var url = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_change_language'; var data = { csrfToken: GlobalVariables.csrfToken, language: $(this).attr('data-language') }; $.post(url, data) .done(function () { document.location.reload(true); }) .fail(GeneralFunctions.ajaxFailureHandler); }); $(document).on('click', function() { $element.popover('hide'); }); }; /** * AJAX Failure Handler * * @param {jqXHR} jqXHR * @param {String} textStatus * @param {Object} errorThrown */ exports.ajaxFailureHandler = function (jqXHR, textStatus, errorThrown) { console.error('Unexpected HTTP Error: ', jqXHR, textStatus, errorThrown); var response; try { response = JSON.parse(jqXHR.responseText); // JSON response } catch(error) { response = { message: jqXHR.responseText }; // String response } if (!response) { return; } GeneralFunctions.displayMessageBox(EALang.unexpected_issues, EALang.unexpected_issues_message); $('<div/>', { 'class': 'card', 'html': [ $('<div/>', { 'class': 'card-body', 'html': response.message || '→ No error information provided.' }) ] }) .appendTo('#message-box'); }; /** * Escape JS HTML string values for XSS prevention. * * @param {String} content String to be escaped. * * @return {String} Returns the escaped string. */ exports.escapeHtml = function (content) { return $('<div/>').text(content).html(); }; /** * Format a given date according to the date format setting. * * @param {String} date The date to be formatted. * @param {String} dateFormatSetting The setting provided by PHP must be one of the "DMY", "MDY" or "YMD". * @param {Boolean} addHours (optional) Whether to add hours to the result. * @return {String} Returns the formatted date string. */ exports.formatDate = function (date, dateFormatSetting, addHours) { var timeFormat = GlobalVariables.timeFormat === 'regular' ? 'h:mm tt' : 'HH:mm'; var hours = addHours ? ' ' + timeFormat : ''; var result; switch (dateFormatSetting) { case 'DMY': result = Date.parse(date).toString('dd/MM/yyyy' + hours); break; case 'MDY': result = Date.parse(date).toString('MM/dd/yyyy' + hours); break; case 'YMD': result = Date.parse(date).toString('yyyy/MM/dd' + hours); break; default: throw new Error('Invalid date format setting provided!', dateFormatSetting); } return result; }; /** * Get the Id of a Weekday using the US week format and day names (Sunday=0) as used in the JS code of the * application, case insensitive, short and long names supported. * * @param {String} weekDayName The weekday name among Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, * Saturday. * @return {Number} Returns the ID of the weekday. */ exports.getWeekDayId = function (weekDayName) { var result; switch (weekDayName.toLowerCase()) { case 'sunday': case 'sun': result = 0; break; case 'monday': case 'mon': result = 1; break; case 'tuesday': case 'tue': result = 2; break; case 'wednesday': case 'wed': result = 3; break; case 'thursday': case 'thu': result = 4; break; case 'friday': case 'fri': result = 5; break; case 'saturday': case 'sat': result = 6; break; default: throw new Error('Invalid weekday name provided!', weekDayName); } return result; }; /** * Get the name in lowercase of a Weekday using its Id. * * @param {Number} weekDayId The Id (From 0 for sunday to 6 for saturday). * @return {String} Returns the name of the weekday. */ exports.getWeekdayName = function (weekDayId) { var result; switch (weekDayId) { case 0: result = 'sunday'; break; case 1: result = 'monday'; break; case 2: result = 'tuesday'; break; case 3: result = 'wednesday'; break; case 4: result = 'thursday'; break; case 5: result = 'friday'; break; case 6: result = 'saturday'; break; default: throw new Error('Invalid weekday Id provided!', weekDayId); } return result; }; /** * Sort a dictionary where keys are weekdays * * @param {Object} weekDictionary A dictionary with weekdays as keys. * @param {Number} startDayId Id of the first day to start sorting (From 0 for sunday to 6 for saturday). * @return {Object} Returns a sorted dictionary */ exports.sortWeekDictionary = function (weekDictionary, startDayId) { var sortedWeekDictionary={}; for (var i = startDayId; i < startDayId+7; i++) { var weekdayName = GeneralFunctions.getWeekdayName(i % 7); sortedWeekDictionary[weekdayName] = weekDictionary[weekdayName]; } return sortedWeekDictionary; }; /** * Render a map icon that links to Google maps. * * @param {Object} user Should have the address, city, etc properties. * * @return {string} The rendered HTML. */ exports.renderMapIcon = function (user) { var data = []; if (user.address) { data.push(user.address); } if (user.city) { data.push(user.city); } if (user.state) { data.push(user.state); } if (user.zip_code) { data.push(user.zip_code); } if (!data.length) { return ''; } return $('<div/>', { 'html': [ $('<a/>', { 'href': 'https://www.google.com/maps/place/' + data.join(','), 'target': '_blank', 'html': [ $('<span/>', { 'class': 'fas fa-map-marker-alt' }) ] }) ] }) .html(); }; /** * Render a mail icon. * * @param {String} email * * @return {string} The rendered HTML. */ exports.renderMailIcon = function (email) { return $('<div/>', { 'html': [ $('<a/>', { 'href': 'mailto:' + email, 'target': '_blank', 'html': [ $('<span/>', { 'class': 'far fa-envelope' }) ] }) ] }) .html(); }; /** * Render a phone icon. * * @param {String} phone * * @return {string} The rendered HTML. */ exports.renderPhoneIcon = function (phone) { return $('<div/>', { 'html': [ $('<a/>', { 'href': 'tel:' + phone, 'target': '_blank', 'html': [ $('<span/>', { 'class': 'fas fa-phone-alt' }) ] }) ] }) .html(); }; /** * Format a given date according to ISO 8601 date format string yyyy-mm-dd * * @param {String} date The date to be formatted. * @param {String} dateFormatSetting The setting provided by PHP must be one of the "DMY", "MDY" or "YMD". * * @return {String} Returns the formatted date string. */ exports.ISO8601DateString = function (date, dateFormatSetting) { var dayArray; // It's necessary to manually parse the date because Date.parse() not support some formats tha instead are // supported by Easy!Appointments. The unsupported format is dd/MM/yyyy. switch (dateFormatSetting) { case 'DMY': dayArray = date.split('/'); date = dayArray[2] + '-' + dayArray[1] + '-' + dayArray[0]; break; case 'MDY': dayArray = date.split('/'); date = dayArray[2] + '-' + dayArray[0] + '-' + dayArray[1]; break; case 'YMD': date = date.replace('/','-'); break; default: throw new Error('Invalid date format setting provided:' + dateFormatSetting); } return date; } })(window.GeneralFunctions);