<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">/* Turn some errors into warnings until we fix them. */
/* eslint no-param-reassign: "warn" */
/* eslint no-extend-native: "warn" */

/* global amplify */
(function ($) {
    var invalidStrings = new RegExp('/(unknown|asdf|(.)\2{3,})/');

    $.extend(_, {

        autocompleteIgnoreKeys: [
            13,     // Enter
            9,      // Tab
            16,     // Shift
            37,     // Left
            38,     // Up
            39,     // Right
            40,     // Down
            20,     // Caps Lock
            144,     // Num Lock
        ],

        empty: function (obj) {
            var isEmpty = false;
            if (_.isString(obj)) {
                isEmpty = !obj.length;
            } else if (_.isObject(obj)) {
                isEmpty = _.objIsEmpty(obj);
            } else {
                isEmpty = !obj;
            }

            return isEmpty;
        },

        safeTrim: function (value) {
            if (value === null || value === undefined) return '';
            return typeof value === 'string' ? value.trim() : '' + value;
        },

        objIsEmpty: function (obj) {
            for (var i in obj) {
                if (obj.hasOwnProperty(i)) {
                    return false;
                }
            }

            return true;
        },

        // Items is an array / keys of integer values, assumes there is a max size to all the values
        chunkByWeight: function (items, segments) {
            var keys = _.keys(items);
            var chunks = new Array(segments);
            var chunkSize = new Array(segments);
            var cursor = 0;
            var min; var max; var pick; var index; var i;
            var limit = keys.length;
            var count = 0;

            for (i = 0; i &lt; segments; i++) {
                chunkSize[i] = 0;
                chunks[i] = [];
            }

            while (keys.length) {
                pick = _.toArray(_.pick(items, keys));
                max = _.max(pick);
                index = _.indexOf(pick, max);

                min = _.min(chunkSize);
                cursor = _.indexOf(chunkSize, min);

                chunks[cursor].push(keys[index]);
                chunkSize[cursor] += max;

                keys.splice(index, 1);

                if (count++ &gt; limit) { break; }
            }

            // TODO: Decide whether to reorder the chunks based on final size
            return chunks;
        },

        // Let's you know if the string is not just fluff to display, test strings such as asdf, unknown, etc..
        validString: function (str) {
            return !_.empty(str) &amp;&amp; !invalidStrings.test(str);
        },

        // Because isDate doesn't tell you if it's a garbage date
        isValidDate: function validDate(date) {
            return _.isDate(date) &amp;&amp; _.isFinite(date.getTime());
        },

        /**
         * The best way to escape HTML in Javascript
         *
         * @see http://shebang.brandonmintern.com/foolproof-html-escaping-in-javascript/
         */
        escapeHTML: function (text) {
            var div = document.createElement('div');
            div.appendChild(document.createTextNode(text));
            return div.innerHTML;
        },

        escapeQuotes: function (text) {
            // Check that this is actually a string - numeric values cannot be escaped
            if (!(typeof text === 'string' || text instanceof String)) {
                return text;
            }

            text = text || '';
            var map = {
                '"': '&amp;quot;',
                "'": '&amp;#039;',
            };

            return text.replace(/["']/g, function (m) { return map[m]; });
        },

        escapeQuotesAndHTML: function (text) {
            text = text || '';
            var map = {
                '&amp;': '&amp;amp;',
                '&lt;': '&amp;lt;',
                '&gt;': '&amp;gt;',
                '"': '&amp;quot;',
                "'": '&amp;#039;',
            };

            return text.replace(/[&amp;&lt;&gt;"']/g, function (m) { return map[m]; });
        },

        unescapeQuotesAndHTML: function (text) {
            text = (text || '').toString();
            var map = {
                '&amp;amp;': '&amp;',
                '&amp;lt;': '&lt;',
                '&amp;gt;': '&gt;',
                '&amp;quot;': '"',
                '&amp;#039;': "'",
            };

            return text.replace(/(?:&amp;amp;)|(?:&amp;lt;)|(?:&amp;gt;)|(?:&amp;quot;)|(?:&amp;#039;)/g, function (m) { return map[m]; });
        },

        // For formatting strings for popover titles and such
        htmlAttrString: function (text) {
            if (!_.isString(text) || !text.length) {
                return '';
            }
            return text.replace(/\r?\n/g, '&lt;br /&gt;').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
        },

        // Good for escaping text inputs
        addslashes: function ( str ) {
            return (str + '').replace(/[\\"]/g, '\\$&amp;').replace(/\u0000/g, '\\0');
        },

        // Formats a number with grouped thousands
        number_format: function (number, decimals, decPoint, thousandsSep) {
            function toFixedFix(n, prec) {
                var k = Math.pow(10, prec);
                return '' + Math.round(n * k) / k;
            }
            const strippedNumber = (number + '').replace(/[^0-9+\-Ee.]/g, '');
            var n = !isFinite(+strippedNumber) ? 0 : +strippedNumber;
            var prec = !isFinite(+decimals) ? 0 : Math.abs(decimals);
            var sep = (typeof thousandsSep === 'undefined') ? ',' : thousandsSep;
            var dec = (typeof decPoint === 'undefined') ? '.' : decPoint;
            var s = '';
            // Fix for IE parseFloat(0.55).toFixed(0) = 0;
            s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
            if (s[0].length &gt; 3) {
                s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
            }
            if ((s[1] || '').length &lt; prec) {
                s[1] = s[1] || '';
                s[1] += new Array(prec - s[1].length + 1).join('0');
            }
            return s.join(dec);
        },

        number_abbr: function (n, uppercase) {
            var divisor = 1;
            var suffix = '';
            var shorthand = n;
            var uppercase = uppercase ? 1 : 0;

            if (n &lt; 1000) {
                divisor = 1;
            } else if (n &lt; 1000 * 1000) { // k
                divisor = 1000;
                suffix = 'k';
            } else if (n &lt; 1000 * 1000 * 1000) { // m
                divisor = 1000 * 1000;
                suffix = 'm';
            } else { // t
                divisor = 1000 * 1000 * 1000;
                suffix = 't';
            }

            shorthand = shorthand / divisor;
            shorthand = _.number_format(shorthand, (shorthand &gt; divisor &amp;&amp; shorthand % 1) ? 1 : 0);
            shorthand += uppercase ? suffix.toUpperCase() : suffix;

            return shorthand;
        },

        // pop the pathname if the `url` matches the `domain`
        urlToFragment: function (url, domain) {
            var page = url;
            var urlMatch = new RegExp('([http|https]://)?(.*\.)?(' + domain + ')(.*)', 'i');
            var matches = urlMatch.exec(url);

            if (matches &amp;&amp; matches.length) {
                page = matches.pop();
            }

            return page;
        },

        extractHostname: function (url) {
            // From `http://google.com/path1` extract the fragment `google.com`
            var matches = url.match(/^(?:https?:)?(?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:\/\n]+)/i);
            var hostname = matches &amp;&amp; matches[1];
            return hostname;
        },

        getQueryStringValue: function (key) {
            return decodeURIComponent(window.location.search.replace(new RegExp('^(?:.*[&amp;\\?]' + encodeURIComponent(key).replace(/[\.\+\*]/g, '\\$&amp;') + '(?:\\=([^&amp;]*))?)?.*$', 'i'), '$1'));
        },

        highlight: function (string, start, length) {
            var str = string;

            if (start &gt;= 0 ) {
                var beg; var mid; var end;
                beg = string.substr(0, start);
                mid = string.substr(start, length);
                end = string.substr(start + length, string.length - (start + length));

                str = [beg, '&lt;span class="highlight"&gt;', mid, '&lt;/span&gt;', end].join('');
            }

            return str;
        },

        getUsers: function (type) {
            if (type === 'all') {
                return app.users;
            } else if (type === 'agency') {
                return _.pick(app.users, app.agencyUsers);
            }
            return _.pick(app.users, app.companyUsers);
        },

        roles: { 'ADMIN': 1, 'COMPANY_MANAGER': 2, 'COMPANY_MARKETING': 4, 'COMPANY_SALES': 8, 'COMPANY_CALL_CENTER': 16, 'CALL_CENTER_MGR': 32, 'SALESPERSON': 64, 'CALL_CENTER_REP': 128, 'JRSALESPERSON': 256 },

        roleInfo: {
            1: {'key': 'administrator', icon: 'icon-key'},
            2: {'key': 'company_manager', icon: 'icon-briefcase'},
            4: {'key': 'company_marketing_manager', icon: 'icon-megaphone'},
            8: {'key': 'company_sales_manager', icon: 'icon-line-chart'},
            6: {'key': 'company_marketing_manager', icon: 'icon-megaphone'},
            10: {'key': 'company_sales_manager', icon: 'icon-line-chart'},
            14: {'key': 'company_manager', icon: 'icon-briefcase'}, // Sales and Marketing Checked
            // 16 : {'key' : 'call_center'},
            // 32 : {'key' : 'call_center_manager'},
            64: {'key': 'salesperson', icon: 'icon-line-chart'},
            // 128 : {'key' : 'call_center_rep'},
            256: {'key': 'jr_salesperson', icon: 'icon-line-chart'},
        },

        hasRole: function (role, flags) {
            return role &amp; flags;
        },

        offerings: {
            'PRO': 1,
            'ESP': 2,
            'SUP': 4,
            'CRM': 8,
            'VID': 16,
            'BETA': 32,
            'CRM_ZERO': 64,
            'CRM_ZERO_INT': 128,
            'CRM_ONE': 256,
            'CRM_TWO': 512,
        },

        hasOffering: function (offering, flags) {
            flags = flags || app.company.productOffering;
            return offering &amp; flags;
        },

        hasRecentFavorites: function (favoriteType, lastSeen) {
            var faves = _.objToArray(app.user.favorites[favoriteType]);
            var count = 0;

            for (var i = 0; i &lt; faves.length; i++) {
                if (!lastSeen || lastSeen &lt; faves[i]) {
                    count++;
                }
            }

            return count;
        },

        hasFeature: function () {
            var features = app.getFeatures();
            for (var i = 0; i &lt; arguments.length; i++) {
                if (!features.hasOwnProperty(arguments[i])) {
                    return false;
                }
            }

            return true;
        },

        featureConstraintActive: function (constraint) {
            var featureConstraints = app.getFeatureConstraints();

            if (_.isEmpty(featureConstraints)) {
                return true;
            }

            var constraintValue = featureConstraints[constraint];

            if (constraintValue === undefined) {
                window.console.error('Error in featureConstraintActive: "' + constraint + '" not found');
            }

            return constraintValue;
        },

        hasSettingFlag: function (flag) {
            var settingsFlags = app.company.flags;
            return flag &amp; settingsFlags;
        },

        leadURL: function (id) {
            return (_.hasFeature('leads') ? '/lead/' : '/contact/') + id;
        },

        getFeatureSettings: function (feature) {
            var settings = app.getFeatureSettings();
            if (this.hasFeature(feature) &amp;&amp; settings.hasOwnProperty(feature)) {
                return settings[feature];
            }
            return null;
        },

        mediaTypes: [
            { name: 'FILE', icon: 'icon-file-alt' },
            { name: 'LINK', icon: 'icon-globe' },
            { name: 'YOUTUBE', icon: 'icon-youtube' },
            { name: 'VIMEO', icon: 'icon-vimeo' },
            { name: 'FACEBOOK', icon: 'icon-facebook' },
            { name: 'GOOGLE_DOCS', icon: 'icon-google-drive' },
        ],

        mimeTypes: [
            { name: 'OTHER', icon: 'icon-file-text' },
            { name: 'MIME_TYPE_PDF', icon: 'icon-file-pdf' },
            { name: 'MIME_TYPE_XLS', icon: 'icon-file-excel' },
            { name: 'MIME_TYPE_PPT', icon: 'icon-file-powerpoint' },
            { name: 'MIME_TYPE_HTML', icon: 'icon-file-xml' },
            { name: 'MIME_TYPE_CSV', icon: 'icon-file-text' },
            { name: 'MIME_TYPE_RTF', icon: 'icon-file-word' },
        ],

        interval: {'day': (24 * 3600 * 1000), 'week': (24 * 3600 * 1000 * 7), 'month': (30 * 24 * 3600000), 'year': (31556952000)},

        keyboard: {ENTER: 13, SEMICOLON: 186, ESC: 27, COMMAND: 91, SHIFT: 16, SPACE: 32, ALT: 18, TAB: 9},

        email_domains: [''],

        us_regions: (function () {
            var regions = {'division': {}, 'region': {}};

            // Region 1
            regions['New England'] = regions.division[1] = ['Maine', 'New Hampshire', 'Vermont', 'Massachusetts', 'Rhode Island', 'Connecticut'];
            regions['Mid-Atlantic'] = regions.division[2] = ['New York', 'Pennsylvania', 'New Jersey'];
            regions.Northeast = regions.region[1] = regions.division[1].concat(regions.division[2]);

            // Region 2
            regions['East North Central'] = regions.division[3] = ['Wisconsin', 'Michigan', 'Illinois', 'Indiana', 'Ohio'];
            regions['West North Central'] = regions.division[4] = ['Missouri', 'North Dakota', 'South Dakota', 'Nebraska', 'Kansas', 'Minnesota', 'Iowa'];
            regions.Midwest = regions.region[2] = regions.division[3].concat(regions.division[4]);

            // Region 3
            regions['South Atlantic'] = regions.division[5] = ['Delaware', 'Maryland', 'District of Columbia', 'Virginia', 'West Virginia', 'North Carolina', 'South Carolina', 'Georgia', 'Florida'];
            regions['East South Central'] = regions.division[6] = ['Kentucky', 'Tennessee', 'Mississippi', 'Alabama'];
            regions['West South Central'] = regions.division[7] = ['Oklahoma', 'Texas', 'Arkansas', 'Louisiana'];
            regions.South = regions.region[3] = regions.division[5].concat(regions.division[6], regions.division[7]);

            // Region 4
            regions.Mountain = regions.division[8] = ['Idaho', 'Montana', 'Wyoming', 'Nevada', 'Utah', 'Colorado', 'Arizona', 'New Mexico'];
            regions.Pacific = regions.division[9] = ['Alaska', 'Washington', 'Oregon', 'California', 'Hawaii'];
            regions.West = regions.region[4] = regions.division[8].concat(regions.division[9]);

            return regions;
        })(),

        quartersByMonth: [0, 0, 0, 3, 3, 3, 6, 6, 6, 9, 9, 9],
        getQuarter: function (date) {
            var currentMonth = date.getMonth();
            var quarterMonth = _.quartersByMonth[currentMonth];

            var from = date.clone();
            from.setMonth(quarterMonth);
            from.moveToFirstDayOfMonth();

            var to = from.clone();
            to.addMonths(2);
            to.moveToLastDayOfMonth();

            var values = {from: from, to: to};

            return values;
        },

        isNumber: function (n) {
            return !isNaN(parseFloat(n)) &amp;&amp; isFinite(n);
        },

        parseURL: function (url) {
            if (!url || !_.isString(url)) {
                return null;
            }

            url = _.ensureHttp(url);
            var a = document.createElement('a');
            a.href = url;
            return a;
        },

        splatURL: function (url) {
            var parsed = _.parseURL(url);
            var getParams = [];
            var param; var query;
            var get = {};

            var parts = url.split('?');
            if (parts[1]) {
                parts = parts[1].split('#'); // maked sure not to include the hash
                query = parts[0];
                getParams = query.split('&amp;');
                for (param in getParams) {
                    if (getParams.hasOwnProperty(param)) {
                        param = getParams[param].split('=');
                        get[param[0]] = param[1] ? param[1] : '';
                    }
                }
            }

            var urlParts = {
                protocol: parsed.protocol,
                host: parsed.host,
                hash: parsed.hash,
                pathname: parsed.pathname,
                port: parsed.port,
                query: query,
                get: get,
            };

            return urlParts;
        },

        sqlDateToUTCDate: function (date) {
            var standardDate = date.replace(/\s/, 'T');
            var dateObject = new Date(standardDate);
            var utcTimestamp = dateObject.getTime() + dateObject.getTimezoneOffset() * 60 * 1000;
            return new Date(utcTimestamp);
        },

        prettyDate: function (time, showtime, pastSuffixIn, futureSuffixIn, toLower) {
            var now = new Date();
            var date = this.parseDate(time);
            var pastSuffix = pastSuffixIn === 'old' ? t('datehelper_old') : pastSuffixIn || t('datehelper_ago');
            var futureSuffix = futureSuffixIn || '';

            if (!date) {
                return t('datehelper_unknown');
            }

            return toLower ? this.getTimeDiffAsString(now, date, pastSuffix, futureSuffix).toLowerCase() :
                this.getTimeDiffAsString(now, date, pastSuffix, futureSuffix);
        },
        // used in legacy contactManager on cst4
        prettyDateCst4: function (time, showtime, pastSuffixIn, futureSuffixIn, toLower) {
            var now = new Date();
            var date = this.parseDateCst4(time);
            var pastSuffix = pastSuffixIn === 'old' ? t('datehelper_old') : pastSuffixIn || t('datehelper_ago');
            var futureSuffix = futureSuffixIn || '';

            if (!date) {
                return t('datehelper_unknown');
            }
            return toLower ? this.getTimeDiffAsString(now, date, pastSuffix, futureSuffix).toLowerCase() :
                this.getTimeDiffAsString(now, date, pastSuffix, futureSuffix);
        },

        // some models may return localized dates(e.g. dueDate field in userTasks)
        // prettyDate additionally localizes the date
        prettyDateNotLocalized: function (time, showtime, pastSuffixIn, futureSuffixIn, toLower) {
            var now = new Date();
            if (_.isString(time)) {
                time = time.replace(/-/g, '/');
            }
            var date = _.isValidDate(new Date(time)) ? new Date(time) : null;
            var pastSuffix = pastSuffixIn === 'old' ? t('datehelper_old') : pastSuffixIn || t('datehelper_ago');
            var futureSuffix = futureSuffixIn || '';

            if (!date) {
                return t('datehelper_unknown');
            }
            return toLower ? this.getTimeDiffAsString(now, date, pastSuffix, futureSuffix).toLowerCase() :
                this.getTimeDiffAsString(now, date, pastSuffix, futureSuffix);
        },

        getTimeDiffAsString: function (now, date, pastSuffix, futureSuffix) {
            // Convert dates to UNIX time (ms)
            now = now.getTime();
            date = date.getTime();

            var diff = (now - date) / 1000; // seconds
            var dayDiff = Math.floor(Math.abs(diff) / 86400);
            var isPast = (diff &gt; 0);
            var suffix = isPast ? pastSuffix : futureSuffix;

            diff = Math.abs(diff);
            dayDiff = Math.abs(dayDiff);

            if ( isNaN(dayDiff) || dayDiff &lt; 0 ) {
                return 'unknown';
            }

            var string = (
                dayDiff === 0 &amp;&amp; (// eslint-disable-next-line no-trailing-spaces
                    // less than one minute
                    diff &lt; 60 &amp;&amp; (isPast ? t('datehelper_justnow') : t('datehelper_just')) ||
                    // between 1 and 2 minutes
                    diff &lt; 120 &amp;&amp; t('datehelper_oneminute') + ' ' + suffix ||
                    // between 2 minutes and 1 hour
                    diff &lt; 3600 &amp;&amp; Math.floor(diff / 60) + ' ' + t('datehelper_minutes') + ' ' + suffix ||
                    // between 1 hour and 2 hours
                    diff &lt; 7200 &amp;&amp; t('datehelper_onehour') + ' ' + suffix ||
                    // between 2 hours and 1 day
                    diff &lt; 86400 &amp;&amp; Math.floor( diff / 3600 ) + ' ' + t('datehelper_hours') + ' ' + suffix
                // eslint-disable-next-line no-trailing-spaces
                ) ||
                // exactly one day
                dayDiff === 1 &amp;&amp; t('datehelper_aday') + ' ' + suffix ||
                // between 1 day and two weeks
                dayDiff &lt; 14 &amp;&amp; dayDiff + ' ' + t('datehelper_days') + ' ' + suffix ||
                // between two weeks and 3 months
                dayDiff &lt;= 90 &amp;&amp; Math.ceil(dayDiff / 7) + ' ' + t('datehelper_weeks') + ' ' + suffix ||
                // between 3 months and two years
                dayDiff &lt;= 730 &amp;&amp; Math.ceil(dayDiff / 30) + ' ' + t('datehelper_months') + ' ' + suffix ||
                // more than two years
                dayDiff &gt; 730 &amp;&amp; Math.ceil(dayDiff / 365) + ' ' + t('datehelper_years') + ' ' + suffix
            );

            return string;
        },

        // Hack to deal with difference between the server timezone and the browser timezone
        localDateOffset: function (x) {
            var timezoneOffset = ((app.user.userTimezoneOffset || 0) + (new Date()).getTimezoneOffset()) * 60 * 1000;
            return new Date(x.getTime() - timezoneOffset);
        },

        parseDate: function (date) {
            var parsedTime;
            if (!date) {
                return null;
            }

            if (_.isFinite(date)) {
                return new Date(date);
            }

            if (_.isString(date)) {
                date = date.replace(/-/g, '/');
            }

            parsedTime = new Date(date);
            if (_.isValidDate(parsedTime)) {
                return this.localDateOffset(parsedTime);
            }
            return null;
        },

        // used in legacy contactManager on cst4
        parseDateCst4: function (date) {
            var parsedTime;
            if (!date) {
                return null;
            }

            if (_.isFinite(date)) {
                return new Date(date);
            }

            if (_.isString(date)) {
                date = date.replace(/-/g, '/');
            }

            var momentDate = moment.utc(date);
            parsedTime = new Date(momentDate.valueOf());
            if (_.isValidDate(parsedTime)) {
                return parsedTime;
            }
            return null;
        },

        prettyDateLabel: function (time, showtime, pastSuffix, futureSuffix, toLower)  {
            var string = this.prettyDate(time, showtime, pastSuffix, futureSuffix, toLower);
            var timezone = app.user.userTimezone !== null ? app.user.userTimezone : app.company.salesOfficialTimezone;
            var momentTimezone = moment.tz(timezone).format('ZZ').slice(0, 3);
            var zoneDiffInt = Number(momentTimezone);
            var momentUtcOffset = moment(time).utcOffset(zoneDiffInt);
            var title = moment(momentUtcOffset).format('LLL');
            return '&lt;span class="tip tool-tip-anchor" title="' + title + '" style="padding-top: 5px;"&gt;' + string + '&lt;/span&gt;';
        },
        // used in legacy contactManager on cst4
        prettyDateLabelCst4: function (time, showtime, pastSuffix, futureSuffix, toLower)  {
            var string = this.prettyDateCst4(time, showtime, pastSuffix, futureSuffix, toLower);
            var niceTime = this.parseDateCst4(time);
            var timezone = app.user.userTimezone !== null ? app.user.userTimezone : app.company.salesOfficialTimezone;
            var momentTimezone = moment.tz(timezone).format('ZZ').slice(0, 3);
            var zoneDiffInt = Number(momentTimezone);
            var momentUtcOffset = moment(niceTime).utcOffset(zoneDiffInt);
            var title = moment(momentUtcOffset).format('LLL');
            return '&lt;span class="tip tool-tip-anchor" title="' + title + '" style="padding-top: 5px;"&gt;' + string + '&lt;/span&gt;';
        },

        prettyDateLabelNoTzConversion: function (time, showtime, pastSuffix, futureSuffix, toLower) {
            var string = this.prettyDate(time, showtime, pastSuffix, futureSuffix, toLower);
            var title = moment(time).format('LLL');
            return '&lt;span class="tip tool-tip-anchor" title="' + title + '" style="padding-top: 5px;"&gt;' + string + '&lt;/span&gt;';
        },

        // Takes a date and returns a date value formated m/d/Y for the previous month
        getPreviousMonth: function (date, format) {
            if (!format) {
                format = 'm/d/yyyy';
            }
            date = new Date(Date.parse(date + ' 00:00:00'));

            // Decrement the month using the setMonth function so that it accounts for changing years.
            var month = date.getMonth();
            month -= 1;
            date.setMonth(month);

            return date.format(format);
        },

        // Takes a date and returns a date value formated m/d/Y for the next month
        getNextMonth: function (date, format) {
            if (!format) {
                format = 'm/d/yyyy';
            }
            date = new Date(Date.parse(date + ' 00:00:00'));

            // Increment the month using the setMonth function so that it accounts for changing years.
            var month = date.getMonth();
            month += 1;
            date.setMonth(month);

            return date.format(format);
        },

        prettyNumber: function (value, showUnits) {
            showUnits = true;
            var thousand = 1000;
            var million = 1000000;
            var billion = 1000000000;
            var trillion = 1000 * billion;

            if (value &lt; thousand * 10) {
                return _.number_format(value);
            } else if (value &lt; million) {
                return Math.round(value / 1000) + (showUnits ? ' thousand' : '');
            } else if (value &lt; billion) {
                return Math.round(value / million) + (showUnits ? ' million' : '');
            } else if (value &lt; trillion) {
                return Math.round(value / billion) + (showUnits ? ' billion' : '');
            }
            return _.number_format(Math.round(value / trillion)) + (showUnits ? ' trillion' : '');
        },

        prettyNumberRange: function (range, prefix) {
            var str = '';
            prefix = _.isString(prefix) ? prefix : '$';

            if (!range) {
                return t('unknown');
            }

            if (range.length == 1) {
                return 'Over ' + prefix + _.prettyNumber(range[0]);
            }

            for (var i = 0; i &lt; range.length; i++) {
                if (i &gt; 0 &amp;&amp; str.length) {
                    str += ' - ';
                }

                str += (range[i] ? (prefix + _.prettyNumber(range[i], !range[i + 1] &amp;&amp; (Math.abs(range[i]) - Math.abs(range[i + 1])) &lt; 1000)) : '');
            }

            return str;
        },

        dateParts: function (initialSeconds) {
            var seconds = initialSeconds;

            var year = 86400 * 365;
            var month = 86400 * 30;
            var week = 86400 * 7;
            var day = 86400;
            var hour = 3600;
            var minute = 60;

            // results
            var years = 0;
            var months = 0;
            var weeks = 0;
            var days = 0;
            var hours = 0;
            var minutes = 0;

            if (seconds &gt;= year) {
                years = Math.floor(seconds / year);
                seconds = seconds % year;
            }

            if (seconds &gt;= month) {
                months = Math.floor(seconds / month);
                seconds = seconds % month;
            }

            if (seconds &gt;= week) {
                weeks = Math.floor(seconds / week);
                seconds = seconds % week;
            }

            if (seconds &gt;= day) {
                days = Math.floor(seconds / day);
                seconds = seconds % day;
            }

            if (seconds &gt;= hour) {
                hours = Math.floor(seconds / hour);
                seconds = seconds % hour;
            }

            if (seconds &gt;= minute) {
                minutes = Math.floor(seconds / minute);
                seconds = seconds % minute;
            }

            return {'years': years, 'months': months, 'weeks': weeks, 'days': days, 'hours': hours, 'minutes': minutes};
        },

        prettyDateParts: function (seconds) {
            var diff = ((seconds - (new Date()).getTime()) / 1000);

            var parts = _.dateParts(diff);
            var str = '';

            // product says they only want the two largest units of time that x number of seconds break down into
            var maximumParts = 2;
            var currentParts = 0;

            // check each piece of `parts` starting with years and working down to minutes
            // if a section of `parts` has a value, and we have not hit the maximum parts yet
            // then build up a piece of the final return string for the `part` and increment the current amount of parts
            // in the string, once we hit the maximum parts, the rest of the conditions will fail and we fall through
            // to the return statement
            if ( parts.years &amp;&amp; currentParts &lt; maximumParts) {
                var yearStr = parts.years &gt; 1 ? t('datehelper_years') : t('datehelper_year');
                str += parts.years + ' ' + yearStr + ' ';
                currentParts += 1;
            }
            if (parts.months &amp;&amp; currentParts &lt; maximumParts) {
                var monthStr = parts.months &gt; 1 ? t('datehelper_months') : t('datehelper_month');

                if (currentParts &gt; 0) {
                    str += 'and ';
                }

                str += parts.months + ' ' + monthStr + ' ';
                currentParts += 1;
            }

            if (parts.weeks &amp;&amp; currentParts &lt; maximumParts) {
                var weekStr = parts.weeks &gt; 1 ? t('datehelper_weeks') : t('datehelper_week');

                if (currentParts &gt; 0) {
                    str += 'and ';
                }

                str += parts.weeks  + ' ' + weekStr + ' ';
                currentParts += 1;
            }

            if (parts.days &amp;&amp; currentParts &lt; maximumParts) {
                var dayStr = parts.days &gt; 1 ? t('datehelper_days') : t('datehelper_day');

                if (currentParts &gt; 0) {
                    str += 'and ';
                }

                str += parts.days + ' ' + dayStr + ' ';
                currentParts += 1;
            }

            if (parts.hours &amp;&amp; currentParts &lt; maximumParts) {
                var hourStr = parts.hours &gt; 1 ? t('datehelper_hours') : t('datehelper_hour');

                if (currentParts &gt; 0) {
                    str += 'and ';
                }

                str += parts.hours + ' ' + hourStr + ' ';
                currentParts += 1;
            }

            if (parts.minutes &amp;&amp; currentParts &lt; maximumParts) {
                var minuteStr = parts.minutes &gt; 1 ? t('datehelper_minutes') : t('datehelper_minute');

                if (currentParts &gt; 0) {
                    str += 'and ';
                }

                str += parts.minutes + ' ' + minuteStr + ' ';
                currentParts += 1;
            }
            return str.trim();
        },

        prettyDateFormat: function (date, format) {
            try {
                var date = Date.parse(date);
                var format = format || 'fullDate';

                if (_.isNumber(date)) {
                    date = new Date(date);
                }

                return date.format(format);
            } catch (err) {
                return '';
            }
        },

        prettyDateTime: function (date, format) {
            try {
                var date = Date.parse(date);
                var format = format || 'fullDate';

                if (_.isNumber(date)) {
                    date = new Date(date);
                }

                return date.format(format) + ' ' + date.format('shortTime');
            } catch (err) {
                return '';
            }
        },


        prettyElapsedTime: function (date1, date2) {
            if (_.isNumber(date1)) {
                date1 = new Date(date1);
                date2 = new Date(date2);
            } else {
                date1 = (Date.parse(date1));
                date2 = (Date.parse(date2));
            }
            // Convert to seconds
            // If date1 and date2 are in ISO format (eg: 2018-05-01T12:21+04:00), Date.parse converts
            // it to time. So check if it is a number because using getTime again gives an error.
            var date1Seconds = _.isNumber(date1) ? date1 / 1000 : date1.getTime() / 1000;
            var date2Seconds = _.isNumber(date2) ? date2 / 1000 : date2.getTime() / 1000;
            var diff = (date2Seconds &gt; date1Seconds ? (date2Seconds - date1Seconds) : (date1Seconds - date2Seconds));
            var dayDiff = Math.floor(diff / 86400);

            var pretty = t('datehelper_unknown');

            if (diff &lt; 0) {
                return pretty;
            }

            if (dayDiff === 0) {
                if (diff &lt; 10) {
                    pretty = diff + ' ' + t('datehelper_seconds');
                } else if (diff &lt; 60) {
                    pretty = diff  + ' ' + t('datehelper_seconds');
                } else if (diff &lt; 120) {
                    pretty = t('datehelper_aminute');
                } else if (diff &lt; 3600) {
                    pretty = Math.floor(diff / 60) + ' ' + t('datehelper_minutes');
                } else if (diff &lt; 7200) {
                    pretty = t('datehelper_onehour');
                } else if (diff &lt; 86400) {
                    pretty = Math.floor(diff / 3600) + ' ' + t('datehelper_hours');
                }
            } else if (dayDiff === 1) {
                pretty = t('datehelper_oneday');
            } else if (dayDiff &lt; 7) {
                pretty = dayDiff + ' ' + t('datehelper_days');
            } else if (dayDiff &lt;= 90) {
                pretty = Math.ceil(dayDiff / 7) + ' ' + t('datehelper_weeks');
            } else if (dayDiff &lt;= 730) {
                pretty = Math.ceil(dayDiff / 30) + ' ' + t('datehelper_months');
            } else if (dayDiff &gt; 730) {
                pretty = Math.ceil(dayDiff / 365) + ' ' + t('datehelper_years');
            }

            return pretty;
        },


        prettyLabel: function (field) {
            var field = field + '';

            field = field.replace(/([a-z])([A-Z])/, '$1 $2');
            field = field.replace('__c', '');
            field = field.replace('i__', '');
            field = field.replace('_', '');
            field = field = _.safeTrim(field);
            field = field.charAt(0).toUpperCase() + field.slice(1);

            return field;
        },

        prettyOp: function (op) {
            var operator;

            switch (op) {
                case 'eq':
                    operator = 'equals';
                    break;

                case 'gte':
                    operator = 'greater than or equal to';
                    break;

                case 'lte':
                    operator = 'less than or equal to';
                    break;

                case 'neq':
                    operator = 'not equal to';
                    break;

                case 'cn':
                    operator = 'contains';
                    break;
                case 'nempt':
                    operator = 'provided';
                    break;
                default:
                    operator = '';
            }

            return operator;
        },

        prettyLocation: function prettyLocation(locationInfo, requestedGlue) {
            const street = _.escape(locationInfo.street);
            const city = _.escape(locationInfo.city);
            const state = _.escape(locationInfo.state);
            const zipcode = _.escape(locationInfo.zipcode);
            const country = _.escape(locationInfo.country);

            var location = [];
            var glue = requestedGlue || '&lt;br/&gt;';

            if (!_.empty(street)) {
                location.push(street);
            }

            if (!_.empty(city) &amp;&amp; !_.empty(state)) {
                location.push(city + ', ' + state);
            } else if (!_.empty(state)) {
                location.push(state);
            } else if (!_.empty(city)) {
                location.push(city);
            }

            if (!_.empty(zipcode)) {
                location.push(zipcode);
            }

            if (!_.empty(country)) {
                location.push(country);
            }

            location = location.join(glue);

            return _.orEqual(location, t('datehelper_just_location_not_provided'));
        },
        prettyPhone: function (phone) {
            var phoneNum = '' + phone;
            var isInternational = window.app.company.country !== 'US';
            var startsWithPlus = phoneNum.charAt(0) === '+';
            var s2 = phoneNum.replace(/\D/g, '');
            var m;
            if (!startsWithPlus) {
                m = s2.match(/^1?(\d{3})(\d{3})(\d{4})$/);
            }
            return isInternational | (!m) ? phone : '(' + m[1] + ') ' + m[2] + '-' + m[3];
        },

        // cleanup number to use inside a callto: link
        // this doesn't generate the link itself
        skypePhone: function (phone, prefix) {
            var phoneNum = _.escape('' + phone);
            var startsWithPlus = phoneNum.charAt(0) === '+';
            var s2 = phoneNum.replace(/\D/g, '');
            var m;

            if (!startsWithPlus) {
                m = s2.match(/^1?(\d{3})(\d{3})(\d{4})$/);
                s2 = (!m) ? phone : '' + m[1] + '-' + m[2] + '-' + m[3];
            }

            if (s2) {
                if (prefix) {
                    s2 = prefix + s2;
                }
                if (startsWithPlus) {
                    s2 = '+' + s2;
                }
            }
            return s2;
        },

        ensureHttp: function (url) {
            var protomatch = /^(https?|ftp):\/\//;
            return 'http://' + url.replace(protomatch, '');
        },

        toWebLink: function (url, fallback) {
            var link = fallback || t('datehelper_just_website_not_provided');

            if (!_.empty(url)) {
                var safeUrl = _.escape(url);
                var goto = _.ensureHttp(url + '');
                link = '&lt;a href="' + goto + '" target="_blank"&gt;' + safeUrl + '&lt;/a&gt;';
            }

            return link;
        },

        toFriendlyWebLink: function (url, text, fallback) {
            var link = fallback || t('datehelper_just_website_not_provided');
            var safeText = _.escape(text);

            if (!_.empty(url)) {
                link = '&lt;a id="accountSummaryLink" href="' + url + '"&gt;' + safeText + '&lt;/a&gt;';
            }

            return link;
        },

        // Note: uses sales dialer handler
        toPhoneLink: function (phone, fallback, tel, hasSalesDialerSeat) {
            var link = fallback || t('datehelper_just_phone_not_provided');

            if (!_.empty(phone)) {
                var safePhone = _.escape(phone);
                if (phone.match(/[a-z]/i)) {
                    link = '&lt;span style="font-weight:bold; color:red;"&gt;' + safePhone + '&lt;/span&gt;';
                } else {
                    var telNum = '+' + (tel || safePhone);
                    var prettyPhone = _.prettyPhone(safePhone);
                    link = '&lt;a href="tel:' + telNum + '"'
                        + ' onClick="_.handleCallClick(event, ' + (hasSalesDialerSeat ? 1 : 0) + ', \'' + telNum + '\')" '
                        + '&gt;' + prettyPhone + '&lt;/a&gt;';
                }
            }
            return link;
        },


        handleCallClick: function (event, hasSalesDialerSeat, phoneNumber) {
            if (!hasSalesDialerSeat) {
                return;
            }

            var INITIATE_NOTE_CALL_MESSAGE = 'initiateNoteCall';
            var LC_INITIATE_CALL_TO_NUMBER = 'initiateCallToNumber';
            window.parent.localStorage.setItem(LC_INITIATE_CALL_TO_NUMBER, phoneNumber || '');
            var initiateEvent = new CustomEvent(INITIATE_NOTE_CALL_MESSAGE);
            window.parent.dispatchEvent(initiateEvent);
            event.preventDefault();
        },

        displayPhoneEditOptions: function (phone) {
            var editOptions = true;

            if (!_.empty(phone)) {
                if (phone.match(/[a-z]/i)) {
                    editOptions = false;
                }
            } else {
                editOptions = false;
            }
            return editOptions;
        },

        toEmailLink: function (email, fallback) {
            var link = fallback || t('datehelper_just_email_not_provided');

            if (!_.empty(email)) {
                var safeEmail = _.escape(email);
                link = '&lt;a href="mailto:' + encodeURI(_.safeTrim(email)) + '"&gt;' + _.safeTrim(safeEmail) + '&lt;/a&gt;';
            }

            return link;
        },

        objToString: function (obj, justValues) {
            var justValues = justValues || true;
            var strings = [];
            for (var prop in obj) {
                strings.push(obj[prop]);
            }

            return strings.join(', ');
        },

        dateToFormat: function (dateString, format) {
            var format = format ? format : 'm/d/yyyy';
            var date = new Date(dateString);
            return date.format(format);
        },


        getDocType: function (node) {
            var doctype = node;

            if (node &amp;&amp; node.name) {
                doctype =  '&lt;!DOCTYPE '
                    + node.name
                    + (node.publicId ? ' PUBLIC "' + node.publicId + '"' : '')
                    + (!node.publicId &amp;&amp; node.systemId ? ' SYSTEM' : '')
                    + (node.systemId ? ' "' + node.systemId + '"' : '')
                    + '&gt;';
            } else {
                doctype = '&lt;!DOCTYPE html&gt;';
            }

            return doctype;
        },

        orEqual: function (item1, item2) {
            if (_.isString(item1)) {
                item1 = _.safeTrim(item1);
                return item1.length ? item1 : item2;
            }

            return item1 || item2;
        },

        toKeys: function (values) {
            var keys = [];
            for (var i in values) {
                if (values.hasOwnProperty(i)) {
                    keys.push(values[i] + '');
                }
            }

            return keys;
        },

        commaSepStringContains: function (needle, haystack) {
            var safeNeedle = needle.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&amp;');
            var r = new RegExp('(^|, ?)' + safeNeedle + '(,|$)');
            return haystack.match(r) !== null;
        },

        // flatten an group of objects values at a property
        pickValues: function (object, keys, valueKey) {
            var object = _.pick(object, keys);
            var values = [];

            for (var o in object) {
                if (object[o].hasOwnProperty(valueKey)) {
                    values.push(object[o][valueKey]);
                }
            }

            return values;
        },


        // get the value of an object by safely descending, returns the value or null if not a valid chain
        valueAt: function (obj, chain) {
            var value = obj;
            var chain = _.isArray(chain) ? chain : Array.prototype.slice.call(arguments, 1);
            var index;

            while (chain.length) {
                index = chain.shift();

                if (value &amp;&amp; value.hasOwnProperty(index)) {
                    value = value[index];
                } else {
                    return null;
                }
            }

            return value;
        },

        valuesAt: function (obj, chain) {
            var values = [];
            var chain = _.isArray(chain) ? chain : Array.prototype.slice.call(arguments, 1);

            for (var index in obj) {
                if (obj.hasOwnProperty(index)) {
                    values.push(_.valueAt(obj[index], chain.concat()));
                }
            }

            return values;
        },

        setValueAt: function (obj, value, chain) {
            var cursor = obj;
            var chain = _.isArray(chain) ? chain : Array.prototype.slice.call(arguments, 2);
            var index;

            try {
                while (chain.length &gt; 1) {
                    index = chain.shift();

                    if (cursor.hasOwnProperty(index)) {
                        cursor = cursor[index];
                    } else {
                        cursor[index] = {};
                        cursor = cursor[index];
                    }
                }

                index = chain.shift();
                cursor[index] = value;

                return true;
            } catch (error) {
                return false;
            }
        },

        parseIntArray: function (arr, radix) {
            var result = [];
            var radix = radix || 10;

            for (var i = 0; i &lt; arr.length; i++) {
                result.push(parseInt(arr[i], radix));
            }

            return result;
        },

        objToArray: function (obj, sort) {
            var payload = [];
            for (var i in obj) {
                if (obj.hasOwnProperty(i)) {
                    payload.push(obj[i]);
                }
            }

            if (sort &amp;&amp; typeof sort === 'function') {
                payload.sort(sort);
            }

            return payload;
        },

        arrayToObj: function (arr, field) {
            var payload = {};

            for (var i in arr) {
                if (arr.hasOwnProperty(i)) {
                    payload[arr[i][field]] = arr[i];
                }
            }

            return payload;
        },

        fill: function (obj, keys, value) {
            for (var i in keys) {
                if (keys.hasOwnProperty(i)) {
                    obj[keys[i]] = value;
                }
            }

            return obj;
        },

        sortOnProperty: function (property, direction, forceNumeric = null) {
            return function (a, b) {
                if (forceNumeric) {
                    var an;
                    var bn;
                    if (forceNumeric === 'int') {
                        an = parseInt(a[property], 10);
                        bn = parseInt(b[property], 10);
                    } else {
                        an = parseFloat(a[property]);
                        bn = parseFloat(b[property]);
                    }
                    if (an &gt; bn) {
                        return (direction === 'asc' ? 1 : -1);
                    } else if (bn &gt; an) {
                        return (direction === 'asc' ? -1 : 1);
                    }
                    return 0;
                }
                if (_.isString(a[property]) &amp;&amp; _.isString(b[property])) {
                    var at = a[property].toLowerCase();
                    var bt = b[property].toLowerCase();

                    if (direction === 'asc') {
                        return at.localeCompare(bt);
                    }
                    return bt.localeCompare(at);
                }

                if (a[property] &gt; b[property]) {
                    return (direction === 'asc' ? 1 : -1);
                } else if (b[property] &gt; a[property]) {
                    return (direction === 'asc' ? -1 : 1);
                }
                return 0;
            };
        },

        gravatar: function (email, size, fullContactURL) {
            var imgSrc = '';
            if (email &amp;&amp; email.length) {
                var avatarSize = size || 32;
                imgSrc = 'https://secure.gravatar.com/avatar/' + _.md5(email.trim().toLowerCase()) + '/?size=' + avatarSize;
                if (fullContactURL &amp;&amp; _.validURL(fullContactURL)) {
                    imgSrc += '&amp;default=' + encodeURI(fullContactURL);
                } else {
                    imgSrc += '&amp;default=' + encodeURI('https://app.sharpspring.com/includes/img/avatars/default-avatar-128.png');
                }
            } else if (fullContactURL &amp;&amp; _.validURL(fullContactURL)) {
                imgSrc = fullContactURL;
            } else {
                imgSrc = '/includes/img/avatars/default-avatar-128.png';
            }
            return imgSrc;
        },

        getUploadThumb: function (url) {
            if (_.isString(url)) {
                return url.replace(/uploads\/(.*\/)?([^\/]*)/, 'uploads/$1mcith/mcith_$2');
            }
            return '';
        },

        getEmailThumbnailPath: function (email) {
            return (email.hasSnapshot &amp;&amp; email.thumbnail ?  email.thumbnail : 'https://s3.amazonaws.com/ss-usa/basic.png');
        },

        getEmailThumbnailorPlaceholder: function (email) {
            return (
                email.hasSnapshot &amp;&amp; email.thumbnail ?
                    '&lt;img src="' + email.thumbnail + '"/&gt;' :
                    '&lt;span class="preview-no-thumb"&gt;&lt;i class="icon-image"&gt;&lt;/i&gt;' + t('noThumbnail') + '&lt;/span&gt;'
            );
        },

        plainTextToHTML: function (plainText, defaultText) {
            var html = '';
            var defaultText = defaultText || '';
            var plainText = plainText == 'null' ? null : plainText;
            plainText = _.orEqual(plainText, defaultText);

            if (plainText &amp;&amp; plainText.length) {
                html = plainText.replace(/[\r\n]{2,}/g, '&lt;/p&gt;&lt;p&gt;');
                html = html.replace(/[\r\n]/g, '&lt;br/&gt;');
                html = ('&lt;p&gt;' + html + '&lt;/p&gt;');
            } else if (typeof plainText === 'number') {
                html = plainText;
            }

            return html;
        },

        decay: function (score, timestamp, requestedHalflife, requestedHalfLives) {
            var now = new Date();
            var then = new Date(timestamp);
            var halflife = _.orEqual(requestedHalflife, 5184000);
            var halfLives = _.orEqual(requestedHalfLives, 2);

            // 2592000 = 1 month, 5184000 = 2 months, 7776000 = 3 months, 15552000 = 6 months
            const elapsed = Math.min(now.getTime() / 1000 - then.getTime() / 1000, halflife * halfLives);
            const decayedScore = score * Math.pow(2, -(elapsed / halflife));

            return Math.floor(decayedScore);
        },

        hasPicklist: function (fieldType) {
            var hasPicklist = false;
            switch (fieldType) {
                case 'picklist':
                case 'multipicklist':
                case 'radio':
                case 'checkbox':
                    hasPicklist = true;
                    break;
            }

            return hasPicklist;
        },

        getFieldTypeIcon: function (fieldType) {
            var icon = '';
            switch (fieldType) {
                case 'currency':
                    icon = 'icon-dollar';
                    break;

                case 'double':
                case 'int':
                    icon = 'icon-hash';
                    break;

                case 'date':
                    icon = 'icon-calendar';
                    break;

                case 'picklist':
                case 'multipicklist':
                case 'state':
                    icon = 'icon-dropmenu';
                    break;

                case 'phone':
                case 'phone number':
                case 'mobile phone':
                case 'office phone number':
                    icon = 'icon-phone';
                    break;

                case 'radio':
                    icon = 'icon-radio-checked';
                    break;

                case 'checkbox':
                    icon = 'icon-checkbox-checked';
                    break;

                case 'textarea':
                    icon = 'icon-paragraph-justify';
                    break;

                case 'email':
                    icon = 'icon-email';
                    break;

                case 'url':
                    icon = 'icon-link';
                    break;

                case 'hidden':
                    icon = 'icon-radio-unchecked';
                    break;

                default:
                    icon = 'icon-input';
                    break;
            }

            return icon;
        },

        parser: document.createElement('a'),

        validURL: function (url) {
            _.parser.href = url;
            var collapse = _.parser.protocol + '//' + _.parser.hostname;
            return (url.indexOf(collapse) === 0);
        },

        validateURLByRegex: function (url) {
            var regex = /^(http(s)?)(:\/\/)[^\s^.]+(\.[^\s]+)+$/;
            return url.match(regex);
        },

        pageHasChanges: false,
        preventPageChange: function (change) {
            // SRSP-26289: Other functionality has been moved into dirtyFlag.js as part of global dirty state.
            if ($) {
                $('.btn-updatable').toggleClass('btn-success', !!change);
            }
            _.pageHasChanges = (change ? true : false);
        },

        preventPageBack: function (doc) {
            // Prevent back from going back in iframes
            $(doc).off('keydown').on('keydown', function (event) {
                var doPrevent = false;
                if (event.keyCode === 8) {
                    var d = event.srcElement || event.target;
                    if ((d.tagName.toUpperCase() === 'INPUT' &amp;&amp; (d.type.toUpperCase() === 'TEXT' || d.type.toUpperCase() === 'PASSWORD' || d.type.toUpperCase() === 'FILE'))
                        || d.tagName.toUpperCase() === 'TEXTAREA') {
                        doPrevent = d.readOnly || d.disabled;
                    } else if (!d.classList.contains('inline-editor-wrap')) {
                        doPrevent = true;
                    }
                }

                if (doPrevent) {
                    event.preventDefault();
                }
            });
        },

        leftPad: function (base, minLength, paddingChar) {
            if (typeof base !== 'string') {
                base = String(base);
            }
            if (base.length &gt;= minLength) {
                return base;
            }

            var padLength = minLength - base.length;
            return (new Array(padLength + 1)).join(paddingChar) + base;
        },

        rightPad: function (base, minLength, paddingChar) {
            if (typeof base !== 'string') {
                base = String(base);
            }
            if (base.length &gt;= minLength) {
                return base;
            }

            var padLength = minLength - base.length;
            return base + (new Array(padLength + 1)).join(paddingChar);
        },

        // Make sure all SFDC links are build correctly
        sfdcLink: function (id, endpoint, tooltip) {
            var className = "class = 'tip'";
            if (!_.valueAt(tooltip)) {
                className = '';
            }

            return '&lt;a href="https://' + endpoint + '/' + id + "\" target = '_blank'" + className + " title='View in SalesForce'&gt;"
                + '&lt;i class="icon-new-window"&gt;&lt;/i&gt;' + t('contacts_contactrow_viewinsfdc') + '&lt;/a&gt;';
        },

        // If it goes to an empty string, it doesn't have a Datepicker equivalent
        phpDateFormatReplacements: {
            'd': 'dd',      // Day of the month, 2 digits with leading zeros
            'D': 'D',       // A textual representation of a day, three letters
            'j': 'd',       // Day of the month without leading zeros
            'l': 'DD',      // A full textual representation of the day of the week
            'N': '',        // ISO-8601 numeric representation of the day of the week
            'S': '',        // English ordinal suffix for the day of the month, 2 characters
            'w': '',        // Numeric representation of the day of the week
            'z': 'o',       // The day of the year (starting from 0)
            'W': '',        // ISO-8601 week number of year, weeks starting on Monday
            'F': 'MM',      // A full textual representation of a month, such as January or March
            'm': 'mm',      // Numeric representation of a month, with leading zeros
            'M': 'M',       // A short textual representation of a month, three letters
            'n': 'm',       // Numeric representation of a month, without leading zeros
            't': '',        // Number of days in the given month
            'L': '',        // Whether it's a leap year
            'o': 'yy',      // ISO-8601 year number. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead.
            'Y': 'yy',      // A full numeric representation of a year, 4 digits
            'y': 'y',       // A two digit representation of a year
        },

        dateFormatPhpToDatepicker(format) {
            if (!format) {
                return '';
            }
            const split = format.trim().split('');
            for (let i = 0; i &lt; split.length; i++) {
                if (split[i] in _.phpDateFormatReplacements) {
                    split[i] = _.phpDateFormatReplacements[split[i]];
                }
            }
            return split.join('');
        },

        // Amplify shortcuts
        pub: amplify.publish,
        sub: amplify.subscribe,
        unsub: amplify.unsubscribe,
        req: amplify.request,
        store: amplify.store,

    });


    var _Templating = _.template;
    _.template = function () {
        try {
            arguments[0] = _.safeTrim(arguments[0]);
            return _Templating.apply(_, arguments);
        } catch (err) {
            console.error('Failed to parse template', err, arguments);
        }

        return function () { return '';};
    };

    String.prototype.capitalize = function () {
        return this.charAt(0).toUpperCase() + this.slice(1);
    };


    /*
     * Date Format 1.2.3
     * (c) 2007-2009 Steven Levithan &lt;stevenlevithan.com&gt;
     * MIT license
     *
     * Includes enhancements by Scott Trenda &lt;scott.trenda.net&gt;
     * and Kris Kowal &lt;cixar.com/~kris.kowal/&gt;
     *
     * Accepts a date, a mask, or a date and a mask.
     * Returns a formatted version of the given date.
     * The date defaults to the current date/time.
     * The mask defaults to dateFormat.masks.default.
     */

    var dateFormat = (function () {
        var	token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g;
        var timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g;
        var timezoneClip = /[^-+\dA-Z]/g;
        var pad = function (initial, requestedLen) {
            let val = String(initial);
            const len = requestedLen || 2;
            while (val.length &lt; len) val = '0' + val;
            return val;
        };

        // Regexes and supporting functions are cached through closure
        return function (date, mask, utc) {
            var dF = dateFormat;
            var dateObj;

            // You can't provide utc if you skip other args (use the "UTC:" mask prefix)
            if (arguments.length === 1 &amp;&amp; Object.prototype.toString.call(date) === '[object String]' &amp;&amp; !/\d/.test(date)) {
                mask = date;
                date = undefined;
            }

            // Passing date through Date applies Date.parse, if necessary
            date = date ? new Date(date) : new Date();

            if (isNaN(date)) {
                // Is this the right thing to throw here?
                throw new SyntaxError('invalid date');
            }

            mask = String(dF.masks[mask] || mask || dF.masks.default);

            // Allow setting the utc argument via the mask
            if (mask.slice(0, 4) === 'UTC:') {
                mask = mask.slice(4);
                utc = true;
            }

            var	_ = utc ? 'getUTC' : 'get';
            var d = date[_ + 'Date']();
            var D = date[_ + 'Day']();
            var m = date[_ + 'Month']();
            var y = date[_ + 'FullYear']();
            var H = date[_ + 'Hours']();
            var M = date[_ + 'Minutes']();
            var s = date[_ + 'Seconds']();
            var L = date[_ + 'Milliseconds']();
            var o = utc ? 0 : date.getTimezoneOffset();
            var flags = {
                d: d,
                dd: pad(d),
                ddd: dF.i18n.dayNames[D],
                dddd: dF.i18n.dayNames[D + 7],
                m: m + 1,
                mm: pad(m + 1),
                mmm: dF.i18n.monthNames[m],
                mmmm: dF.i18n.monthNames[m + 12],
                yy: String(y).slice(2),
                yyyy: y,
                h: H % 12 || 12,
                hh: pad(H % 12 || 12),
                H: H,
                HH: pad(H),
                M: M,
                MM: pad(M),
                s: s,
                ss: pad(s),
                l: pad(L, 3),
                L: pad(L &gt; 99 ? Math.round(L / 10) : L),
                t: H &lt; 12 ? 'a'  : 'p',
                tt: H &lt; 12 ? 'am' : 'pm',
                T: H &lt; 12 ? 'A'  : 'P',
                TT: H &lt; 12 ? 'AM' : 'PM',
                Z: utc ? 'UTC' : (String(date).match(timezone) || ['']).pop().replace(timezoneClip, ''),
                o: (o &gt; 0 ? '-' : '+') + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
                S: ['th', 'st', 'nd', 'rd'][d % 10 &gt; 3 ? 0 : (d % 100 - d % 10 !== 10) * d % 10],
            };

            return mask.replace(token, function ($0) {
                return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
            });
        };
    }());

    // Some common format strings
    dateFormat.masks = {
        'default': 'ddd mmm dd yyyy HH:MM:ss',
        shortDate: 'm/d/yy',
        mediumDate: 'mmm d, yyyy',
        longDate: 'mmmm d, yyyy',
        longDateTime: 'mmmm d, yyyy h:MM TT Z',
        fullDate: 'dddd, mmmm d, yyyy',
        shortTime: 'h:MM TT',
        mediumTime: 'h:MM:ss TT',
        longTime: 'h:MM:ss TT Z',
        isoDate: 'yyyy-mm-dd',
        isoTime: 'HH:MM:ss',
        isoDateTime: "yyyy-mm-dd'T'HH:MM:ss",
        isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'",
    };

    // Internationalization strings
    dateFormat.i18n = {
        dayNames: [
            'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat',
            'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',
        ],
        monthNames: [
            'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
            'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December',
        ],
    };

    // For convenience...
    Date.prototype.format = function (mask, utc) {
        return dateFormat(this, mask, utc);
    };

    Date.prototype.max = function (d1, d2) {
        return (d1 &lt; d2) ? d2 : d1;
    };

    Date.prototype.min = function (d1, d2) {
        return (d1 &lt; d2) ? d1 : d2;
    };

    Date.prototype.toSQL = function (includeTime) {
        return this.format(includeTime ? 'yyyy-mm-dd HH:MM:ss' : 'yyyy-mm-dd');
    };

    var origParse = Date.parse; var numericKeys = [ 1, 4, 5, 6, 7, 10, 11 ];
    Date.parse = function (date) {
        var timestamp; var struct; var minutesOffset = 0;

        // ES5 Â§15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string
        // before falling back to any implementation-specific date parsing, so thatâ€™s what we do, even if native
        // implementations could be faster
        //              1 YYYY                2 MM       3 DD           4 HH    5 mm       6 ss        7 msec        8 Z 9 Â±    10 tzHH    11 tzmm
        if ((struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(date))) {
            // avoid NaN timestamps caused by â€œundefinedâ€ values being passed to Date.UTC
            for (var i = 0, k; (k = numericKeys[i]); ++i) {
                struct[k] = +struct[k] || 0;
            }

            // allow undefined days and months
            struct[2] = (+struct[2] || 1) - 1;
            struct[3] = +struct[3] || 1;

            if (struct[8] !== 'Z' &amp;&amp; struct[9] !== undefined) {
                minutesOffset = struct[10] * 60 + struct[11];

                if (struct[9] === '+') {
                    minutesOffset = 0 - minutesOffset;
                }
            }

            timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]);
        } else {
            timestamp = origParse ? origParse(date) : NaN;
        }

        return timestamp;
    };


    // MD5 ---------------------------------------------------------


    function md5cycle(x, k) {
        var a = x[0]; var b = x[1]; var c = x[2]; var d = x[3];

        a = ff(a, b, c, d, k[0], 7, -680876936);
        d = ff(d, a, b, c, k[1], 12, -389564586);
        c = ff(c, d, a, b, k[2], 17,  606105819);
        b = ff(b, c, d, a, k[3], 22, -1044525330);
        a = ff(a, b, c, d, k[4], 7, -176418897);
        d = ff(d, a, b, c, k[5], 12,  1200080426);
        c = ff(c, d, a, b, k[6], 17, -1473231341);
        b = ff(b, c, d, a, k[7], 22, -45705983);
        a = ff(a, b, c, d, k[8], 7,  1770035416);
        d = ff(d, a, b, c, k[9], 12, -1958414417);
        c = ff(c, d, a, b, k[10], 17, -42063);
        b = ff(b, c, d, a, k[11], 22, -1990404162);
        a = ff(a, b, c, d, k[12], 7,  1804603682);
        d = ff(d, a, b, c, k[13], 12, -40341101);
        c = ff(c, d, a, b, k[14], 17, -1502002290);
        b = ff(b, c, d, a, k[15], 22,  1236535329);

        a = gg(a, b, c, d, k[1], 5, -165796510);
        d = gg(d, a, b, c, k[6], 9, -1069501632);
        c = gg(c, d, a, b, k[11], 14,  643717713);
        b = gg(b, c, d, a, k[0], 20, -373897302);
        a = gg(a, b, c, d, k[5], 5, -701558691);
        d = gg(d, a, b, c, k[10], 9,  38016083);
        c = gg(c, d, a, b, k[15], 14, -660478335);
        b = gg(b, c, d, a, k[4], 20, -405537848);
        a = gg(a, b, c, d, k[9], 5,  568446438);
        d = gg(d, a, b, c, k[14], 9, -1019803690);
        c = gg(c, d, a, b, k[3], 14, -187363961);
        b = gg(b, c, d, a, k[8], 20,  1163531501);
        a = gg(a, b, c, d, k[13], 5, -1444681467);
        d = gg(d, a, b, c, k[2], 9, -51403784);
        c = gg(c, d, a, b, k[7], 14,  1735328473);
        b = gg(b, c, d, a, k[12], 20, -1926607734);

        a = hh(a, b, c, d, k[5], 4, -378558);
        d = hh(d, a, b, c, k[8], 11, -2022574463);
        c = hh(c, d, a, b, k[11], 16,  1839030562);
        b = hh(b, c, d, a, k[14], 23, -35309556);
        a = hh(a, b, c, d, k[1], 4, -1530992060);
        d = hh(d, a, b, c, k[4], 11,  1272893353);
        c = hh(c, d, a, b, k[7], 16, -155497632);
        b = hh(b, c, d, a, k[10], 23, -1094730640);
        a = hh(a, b, c, d, k[13], 4,  681279174);
        d = hh(d, a, b, c, k[0], 11, -358537222);
        c = hh(c, d, a, b, k[3], 16, -722521979);
        b = hh(b, c, d, a, k[6], 23,  76029189);
        a = hh(a, b, c, d, k[9], 4, -640364487);
        d = hh(d, a, b, c, k[12], 11, -421815835);
        c = hh(c, d, a, b, k[15], 16,  530742520);
        b = hh(b, c, d, a, k[2], 23, -995338651);

        a = ii(a, b, c, d, k[0], 6, -198630844);
        d = ii(d, a, b, c, k[7], 10,  1126891415);
        c = ii(c, d, a, b, k[14], 15, -1416354905);
        b = ii(b, c, d, a, k[5], 21, -57434055);
        a = ii(a, b, c, d, k[12], 6,  1700485571);
        d = ii(d, a, b, c, k[3], 10, -1894986606);
        c = ii(c, d, a, b, k[10], 15, -1051523);
        b = ii(b, c, d, a, k[1], 21, -2054922799);
        a = ii(a, b, c, d, k[8], 6,  1873313359);
        d = ii(d, a, b, c, k[15], 10, -30611744);
        c = ii(c, d, a, b, k[6], 15, -1560198380);
        b = ii(b, c, d, a, k[13], 21,  1309151649);
        a = ii(a, b, c, d, k[4], 6, -145523070);
        d = ii(d, a, b, c, k[11], 10, -1120210379);
        c = ii(c, d, a, b, k[2], 15,  718787259);
        b = ii(b, c, d, a, k[9], 21, -343485551);

        x[0] = add32(a, x[0]);
        x[1] = add32(b, x[1]);
        x[2] = add32(c, x[2]);
        x[3] = add32(d, x[3]);
    }

    function cmn(q, a, b, x, s, t) {
        a = add32(add32(a, q), add32(x, t));
        return add32((a &lt;&lt; s) | (a &gt;&gt;&gt; (32 - s)), b);
    }

    function ff(a, b, c, d, x, s, t) {
        return cmn((b &amp; c) | ((~b) &amp; d), a, b, x, s, t);
    }

    function gg(a, b, c, d, x, s, t) {
        return cmn((b &amp; d) | (c &amp; (~d)), a, b, x, s, t);
    }

    function hh(a, b, c, d, x, s, t) {
        return cmn(b ^ c ^ d, a, b, x, s, t);
    }

    function ii(a, b, c, d, x, s, t) {
        return cmn(c ^ (b | (~d)), a, b, x, s, t);
    }

    /* there needs to be support for Unicode here,
     * unless we pretend that we can redefine the MD-5
     * algorithm for multi-byte characters (perhaps
     * by adding every four 16-bit characters and
     * shortening the sum to 32 bits). Otherwise
     * I suggest performing MD-5 as if every character
     * was two bytes--e.g., 0040 0025 = @%--but then
     * how will an ordinary MD-5 sum be matched?
     * There is no way to standardize text to something
     * like UTF-8 before transformation; speed cost is
     * utterly prohibitive. The JavaScript standard
     * itself needs to look at this: it should start
     * providing access to strings as preformed UTF-8
     * 8-bit unsigned value arrays.
     */
    function md5blk(s) { /* I figured global was faster.   */
        var md5blks = []; var i; /* Andy King said do it this way. */
        for (i = 0; i &lt; 64; i += 4) {
            md5blks[i &gt;&gt; 2] = s.charCodeAt(i)
                + (s.charCodeAt(i + 1) &lt;&lt; 8)
                + (s.charCodeAt(i + 2) &lt;&lt; 16)
                + (s.charCodeAt(i + 3) &lt;&lt; 24);
        }
        return md5blks;
    }

    function md51(s) {
        var n = s.length;
        var state = [1732584193, -271733879, -1732584194, 271733878]; var i;
        for (i = 64; i &lt;= s.length; i += 64) {
            md5cycle(state, md5blk(s.substring(i - 64, i)));
        }
        s = s.substring(i - 64);
        var tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
        for (i = 0; i &lt; s.length; i++) {tail[i &gt;&gt; 2] |= s.charCodeAt(i) &lt;&lt; ((i % 4) &lt;&lt; 3);}
        tail[i &gt;&gt; 2] |= 0x80 &lt;&lt; ((i % 4) &lt;&lt; 3);
        if (i &gt; 55) {
            md5cycle(state, tail);
            for (i = 0; i &lt; 16; i++) tail[i] = 0;
        }
        tail[14] = n * 8;
        md5cycle(state, tail);
        return state;
    }

    var hex_chr = '0123456789abcdef'.split('');

    function rhex(n) {
        var s = ''; var j = 0;
        for (; j &lt; 4; j++) {
            s += hex_chr[(n &gt;&gt; (j * 8 + 4)) &amp; 0x0F]
                + hex_chr[(n &gt;&gt; (j * 8)) &amp; 0x0F];
        }
        return s;
    }

    function hex(x) {
        for (var i = 0; i &lt; x.length; i++) {x[i] = rhex(x[i]);}
        return x.join('');
    }

    function md5(s) {
        return hex(md51(s));
    }

    /* this function is much faster,
     so if possible we use it. Some IEs
     are the only ones I know of that
     need the idiotic second function,
     generated by an if clause.  */

    function add32(a, b) {
        return (a + b) &amp; 0xFFFFFFFF;
    }

    if (md5('hello') != '5d41402abc4b2a76b9719d911017c592') {
        function add32(x, y) {
            var lsw = (x &amp; 0xFFFF) + (y &amp; 0xFFFF);
            var msw = (x &gt;&gt; 16) + (y &gt;&gt; 16) + (lsw &gt;&gt; 16);
            return (msw &lt;&lt; 16) | (lsw &amp; 0xFFFF);
        }
    }

    _.md5 = md5;
})(jQuery);
</pre></body></html>