PageSourceSearch

https://my.hostarmada.com/templates/hexa/assets/js/whmcs.js

js hostarmada.com collected 2026-10-02 07:44:33 UTC 117,148 bytes, 3,399 lines download raw bytes

1/**
2 * WHMCS core JS library reference
3 *
4 * @copyright Copyright (c) WHMCS Limited 2005-2017
5 * @license http://www.whmcs.com/license/ WHMCS Eula
6 */
7
8
9(function (window, factory) {
10    if (typeof window.WHMCS !== 'object') {
11        window.WHMCS = factory;
12    }
13}(
14    window,
15    {
16        hasModule: function (name) {
17            return (typeof WHMCS[name] !== 'undefined'
18                && Object.getOwnPropertyNames(WHMCS[name]).length > 0);
19        },
20        loadModule: function (name, module) {
21            if (this.hasModule(name)) {
22                return;
23            }
24
25            WHMCS[name] = {};
26            if (typeof module === 'function') {
27                (module).apply(WHMCS[name]);
28            } else {
29                for (var key in module) {
30                    if (module.hasOwnProperty(key)) {
31                        WHMCS[name][key] = {};
32                        (module[key]).apply(WHMCS[name][key]);
33                    }
34                }
35            }
36        }
37    }
38));
39jQuery(document).ready(function() {
40    jQuery(document).on('click', '.disable-on-click', function () {
41        jQuery(this).addClass('disabled');
42
43        if (jQuery(this).hasClass('spinner-on-click')) {
44            var icon = $(this).find('i.fas,i.far,i.fal,i.fab');
45
46            jQuery(icon)
47                .removeAttr('class')
48                .addClass('fas fa-spinner fa-spin');
49        }
50    });
51});
52/**
53 * WHMCS authentication module
54 *
55 * @copyright Copyright (c) WHMCS Limited 2005-2017
56 * @license http://www.whmcs.com/license/ WHMCS Eula
57 */
58
59(function(module) {
60    if (!WHMCS.hasModule('authn')) {
61        WHMCS.loadModule('authn', module);
62    }
63})({
64provider: function () {
65    var callbackFired = false;
66
67    /**
68     * @return {jQuery}
69     */
70    this.feedbackContainer = function () {
71        return jQuery(".providerLinkingFeedback");
72    };
73
74    /**
75     * @returns {jQuery}
76     */
77    this.btnContainer = function () {
78        return jQuery(".providerPreLinking");
79    };
80
81    this.feedbackMessage = function (context) {
82        if (typeof context === 'undefined') {
83            context = 'complete_sign_in';
84        }
85        var msgContainer = jQuery('p.providerLinkingMsg-preLink-' + context);
86        if (msgContainer.length) {
87            return msgContainer.first().html();
88        }
89
90        return '';
91    };
92
93    this.showProgressMessage = function(callback) {
94        this.feedbackContainer().fadeIn('fast', function () {
95            if (typeof callback === 'function' && !callbackFired) {
96                callbackFired = true;
97                callback();
98            }
99        });
100    };
101
102    this.preLinkInit = function (callback) {
103        var icon = '<i class="fas fa-fw fa-spinner fa-spin"></i> ';
104
105        this.feedbackContainer()
106            .removeClass('alert-danger alert-success')
107            .addClass('alert alert-info')
108            .html(icon + this.feedbackMessage())
109            .hide();
110
111        var btnContainer = this.btnContainer();
112        if (btnContainer.length) {
113            if (btnContainer.data('hideOnPrelink')) {
114                var self = this;
115                btnContainer.fadeOut('false', function ()
116                {
117                    self.showProgressMessage(callback)
118                });
119            } else if (btnContainer.data('disableOnPrelink')) {
120                btnContainer.find('.btn').addClass('disabled');
121                this.showProgressMessage(callback);
122            } else {
123                this.showProgressMessage(callback);
124            }
125        } else {
126            this.showProgressMessage(callback);
127        }
128    };
129
130    this.displayError = function (provider, errorCondition, providerErrorText){
131        jQuery('#providerLinkingMessages .provider-name').html(provider);
132
133        var feedbackMsg = this.feedbackMessage('connect_error');
134        if (errorCondition) {
135            var errorMsg = this.feedbackMessage(errorCondition);
136            if (errorMsg) {
137                feedbackMsg = errorMsg
138            }
139        }
140
141        if (providerErrorText && jQuery('.btn-logged-in-admin').length > 0) {
142            feedbackMsg += ' Error: ' + providerErrorText;
143        }
144
145        this.feedbackContainer().removeClass('alert-info alert-success')
146            .addClass('alert alert-danger')
147            .html(feedbackMsg).slideDown();
148    };
149
150    this.displaySuccess = function (data, context, provider) {
151        var icon = provider.icon;
152        var htmlTarget = context.htmlTarget;
153        var targetLogin = context.targetLogin;
154        var targetRegister = context.targetRegister;
155        var displayName = provider.name;
156        var feedbackMsg = '';
157
158        switch (data.result) {
159            case "logged_in":
160            case "2fa_needed":
161                feedbackMsg = this.feedbackMessage('2fa_needed');
162                this.feedbackContainer().removeClass('alert-danger alert-warning alert-success')
163                    .addClass('alert alert-info')
164                    .html(feedbackMsg);
165                window.location = data.redirect_url
166                ? decodeURIComponent(data.redirect_url)
167                : decodeURIComponent(context.redirectUrl);
168
169                break;
170
171            case "linking_complete":
172                var accountInfo = '';
173                if (data.remote_account.email) {
174                    accountInfo = data.remote_account.email;
175                } else {
176                    accountInfo = data.remote_account.firstname + " " + data.remote_account.lastname;
177                }
178
179                accountInfo = accountInfo.trim();
180
181                feedbackMsg = this.feedbackMessage('linking_complete').trim().replace(':displayName', displayName);
182                if (accountInfo) {
183                    feedbackMsg = feedbackMsg.replace(/\.$/, ' (' + accountInfo + ').');
184                }
185
186                this.feedbackContainer().removeClass('alert-danger alert-warning alert-info')
187                    .addClass('alert alert-success')
188                    .html(icon + feedbackMsg);
189                break;
190
191            case "login_to_link":
192                if (htmlTarget === targetLogin) {
193                    feedbackMsg = this.feedbackMessage('login_to_link-signin-required');
194                    this.feedbackContainer().removeClass('alert-danger alert-success alert-info')
195                        .addClass('alert alert-warning')
196                        .html(icon + feedbackMsg);
197                } else {
198                    var emailField = jQuery("input[name=email]");
199                    var firstNameField = jQuery("input[name=firstname]");
200                    var lastNameField = jQuery("input[name=lastname]");
201
202                    if (emailField.val() === "") {
203                        emailField.val(data.remote_account.email);
204                    }
205
206                    if (firstNameField.val() === "") {
207                        firstNameField.val(data.remote_account.firstname);
208                    }
209
210                    if (lastNameField.val() === "") {
211                        lastNameField.val(data.remote_account.lastname);
212                    }
213
214                    if (htmlTarget === targetRegister) {
215                        if (typeof WHMCS.client.registration === 'object') {
216                            WHMCS.client.registration.prefillPassword();
217                        }
218                        feedbackMsg = this.feedbackMessage('login_to_link-registration-required');
219                        this.feedbackContainer().fadeOut('slow', function () {
220                            jQuery(this).removeClass('alert-danger alert-success alert-info')
221                                .addClass('alert alert-warning')
222                                .html(icon + feedbackMsg).fadeIn('fast');
223                        });
224
225                    } else {
226                        // this is checkout
227                        if (typeof WHMCS.client.registration === 'object') {
228                            WHMCS.client.registration.prefillPassword();
229                        }
230
231                        var self = this;
232                        this.feedbackContainer().each(function (i, el) {
233                            var container = jQuery(el);
234                            var linkContext = container.siblings('div .providerPreLinking').data('linkContext');
235
236                            container.fadeOut('slow', function () {
237                                if (linkContext === 'checkout-new') {
238                                    feedbackMsg = self.feedbackMessage('checkout-new');
239                                } else {
240                                    feedbackMsg = self.feedbackMessage('login_to_link-signin-required');
241                                }
242                                container.removeClass('alert-danger alert-success alert-info')
243                                    .addClass('alert alert-warning')
244                                    .html(icon + feedbackMsg).fadeIn('fast');
245                            });
246                        });
247                    }
248                }
249
250                break;
251
252            case "other_user_exists":
253                feedbackMsg = this.feedbackMessage('other_user_exists');
254                this.feedbackContainer().removeClass('alert-info alert-success')
255                    .addClass('alert alert-danger')
256                    .html(icon + feedbackMsg).slideDown();
257                break;
258
259            case "already_linked":
260                feedbackMsg = this.feedbackMessage('already_linked');
261                this.feedbackContainer().removeClass('alert-info alert-success')
262                    .addClass('alert alert-danger')
263                    .html(icon + feedbackMsg).slideDown();
264                break;
265
266            default:
267                feedbackMsg = this.feedbackMessage('default');
268                this.feedbackContainer().removeClass('alert-info alert-success')
269                    .addClass('alert alert-danger')
270                    .html(icon + feedbackMsg).slideDown();
271                break;
272        }
273    };
274
275    this.signIn = function (config, context, provider, providerDone, providerError) {
276        jQuery.ajax(config).done(function(data) {
277            providerDone();
278            WHMCS.authn.provider.displaySuccess(data, context, provider);
279            var table = jQuery('#tableLinkedAccounts');
280            if (table.length) {
281                WHMCS.ui.dataTable.getTableById('tableLinkedAccounts').ajax.reload();
282            }
283        }).error(function() {
284            providerError();
285            WHMCS.authn.provider.displayError();
286        });
287    };
288
289    return this;
290}});
291
292/**
293 * WHMCS client module
294 *
295 * @copyright Copyright (c) WHMCS Limited 2005-2017
296 * @license http://www.whmcs.com/license/ WHMCS Eula
297 */
298(function(module) {
299    if (!WHMCS.hasModule('client')) {
300        WHMCS.loadModule('client', module);
301    }
302})({
303registration: function () {
304    this.prefillPassword = function (params) {
305        params = params || {};
306        if (typeof params.hideContainer === 'undefined') {
307            var id = (jQuery('#inputSecurityQId').attr('id')) ? '#containerPassword' : '#containerNewUserSecurity';
308            params.hideContainer = jQuery(id);
309            params.hideInputs = true;
310        } else if (typeof params.hideContainer === 'string' && params.hideContainer.length) {
311            params.hideContainer = jQuery(params.hideContainer);
312        }
313
314        if (typeof params.form === 'undefined') {
315            params.form = {
316                password: [
317                    {id: 'inputNewPassword1'},
318                    {id: 'inputNewPassword2'}
319                ]
320            };
321        }
322
323        var prefillFunc = function () {
324            var $randomPasswd = WHMCS.utils.simpleRNG();
325            for (var i = 0, len = params.form.password.length; i < len; i++) {
326                jQuery('#' + params.form.password[i].id)
327                    .val($randomPasswd).trigger('keyup');
328            }
329        };
330
331        if (params.hideInputs) {
332            params.hideContainer.slideUp('fast', prefillFunc);
333        } else {
334            prefillFunc();
335        }
336    };
337
338    return this;
339}});
340
341/**
342 * WHMCS HTTP module
343 *
344 * @copyright Copyright (c) WHMCS Limited 2005-2018
345 * @license http://www.whmcs.com/license/ WHMCS Eula
346 */
347(function(module) {
348    if (!WHMCS.hasModule('http')) {
349        WHMCS.loadModule('http', module);
350    }
351})({
352jqClient: function () {
353    _getSettings = function (url, data, success, dataType)
354    {
355        if (typeof url === 'object') {
356            /*
357                Settings may be the only argument
358             */
359            return url;
360        }
361
362        if (typeof data === 'function') {
363            /*
364                If 'data' is omitted, 'success' will come in its place
365             */
366            success = data;
367            data = null;
368        }
369
370        return {
371            url: url,
372            data: data,
373            success: success,
374            dataType: dataType
375        };
376    };
377
378    /**
379     * @param url
380     * @param data
381     * @param success
382     * @param dataType
383     * @returns {*}
384     */
385    this.get = function (url, data, success, dataType)
386    {
387        return WHMCS.http.client.request(
388            jQuery.extend(
389                _getSettings(url, data, success, dataType),
390                {
391                    type: 'GET'
392                }
393            )
394        );
395    };
396
397    /**
398     * @param url
399     * @param data
400     * @param success
401     * @param dataType
402     * @returns {*}
403     */
404    this.post = function (url, data, success, dataType)
405    {
406        return WHMCS.http.client.request(
407            jQuery.extend(
408                _getSettings(url, data, success, dataType),
409                {
410                    type: 'POST'
411                }
412            )
413        );
414    };
415
416    /**
417     * @param options
418     * @returns {*}
419     */
420    this.jsonPost = function (options) {
421        options = options || {};
422        this.post(options.url, options.data, function(response) {
423            if (response.warning) {
424                console.log('[WHMCS] Warning: ' + response.warning);
425                if (typeof options.warning === 'function') {
426                    options.warning(response.warning);
427                }
428            } else if (response.error) {
429                console.log('[WHMCS] Error: ' + response.error);
430                if (typeof options.error === 'function') {
431                    options.error(response.error);
432                }
433            } else {
434                if (typeof options.success === 'function') {
435                    options.success(response);
436                }
437            }
438        }, 'json').error(function(xhr, errorMsg){
439            console.log('[WHMCS] Error: ' + errorMsg);
440            if (typeof options.fail === 'function') {
441                options.fail(errorMsg);
442            }
443        }).always(function() {
444            if (typeof options.always === 'function') {
445                options.always();
446            }
447        });
448    };
449
450    return this;
451},
452
453client: function () {
454    var methods = ['get', 'post', 'put', 'delete'];
455    var client = this;
456
457    _beforeRequest = function (settings)
458    {
459        /*
460            Enforcing dataType was found to break many invocations expecting HTML back.
461            If/when those are refactored, this may be uncommented to enforce a safer
462            data transit.
463         */
464        /*if (typeof settings.dataType === 'undefined') {
465            settings.dataType = 'json';
466        }*/
467
468        if (typeof settings.type === 'undefined') {
469            // default request type is GET
470            settings.type = 'GET';
471        }
472
473        /*
474            Add other preprocessing here if required
475         */
476
477        return settings;
478    };
479
480    this.request = function (settings)
481    {
482        settings = _beforeRequest(settings || {});
483        return jQuery.ajax(settings);
484    };
485
486    /*
487        Create shortcut methods for methods[] array above
488     */
489    jQuery.each(methods, function(index, method) {
490        client[method] = (function(method, client) {
491            return function (settings)
492            {
493                settings = settings || {};
494
495                settings.type = method.toUpperCase();
496
497                return client.request(settings);
498            }
499        })(method, client);
500    });
501
502    return this;
503}
504
505});
506
507/**
508 * WHMCS UI module
509 *
510 * @copyright Copyright (c) WHMCS Limited 2005-2017
511 * @license http://www.whmcs.com/license/ WHMCS Eula
512 */
513(function(module) {
514    if (!WHMCS.hasModule('ui')) {
515        WHMCS.loadModule('ui', module);
516    }
517})({
518/**
519 * Confirmation PopUp
520 */
521confirmation: function () {
522
523    /**
524     * @type {Array} Registered confirmation root selectors
525     */
526    var toggles = [];
527
528    /**
529     * Register/Re-Register all confirmation elements with jQuery
530     * By default all elements of data toggle "confirmation" will be registered
531     *
532     * @param {(string|undefined)} rootSelector
533     * @return {Array} array of registered toggles
534     */
535    this.register = function (rootSelector) {
536        if (typeof rootSelector === 'undefined') {
537            rootSelector = '[data-toggle=confirmation]';
538        }
539        if (toggles.indexOf(rootSelector) < 0) {
540            toggles.push(rootSelector);
541        }
542
543        jQuery(rootSelector).confirmation({
544            rootSelector: rootSelector
545        });
546
547        return toggles;
548    };
549
550    return this;
551},
552
553/**
554 * Data Driven Table
555 */
556dataTable: function () {
557
558    /**
559     * @type {{}}
560     */
561    this.tables = {};
562
563    /**
564     * Register all tables on page with the class "data-driven"
565     */
566    this.register = function () {
567        var self = this;
568        jQuery('table.data-driven').each(function (i, table) {
569            self.getTableById(table.id, undefined);
570        });
571    };
572
573    /**
574     * Get a table by id; create table object on fly as necessary
575     *
576     * @param {string} id
577     * @param {({}|undefined)} options
578     * @returns {DataTable}
579     */
580    this.getTableById = function (id, options) {
581        var self = this;
582        var el = jQuery('#' + id);
583        if (typeof self.tables[id] === 'undefined') {
584            if (typeof options === 'undefined') {
585                options = {
586                    dom: '<"listtable"ift>pl',
587                    paging: false,
588                    lengthChange: false,
589                    searching: false,
590                    ordering: true,
591                    info: false,
592                    autoWidth: true,
593                    language: {
594                        emptyTable: (el.data('lang-empty-table')) ? el.data('lang-empty-table') : "No records found"
595                    }
596                };
597            }
598            var ajaxUrl = el.data('ajax-url');
599            if (typeof ajaxUrl !== 'undefined') {
600                options.ajax = {
601                    url: ajaxUrl
602                };
603            }
604            var dom = el.data('dom');
605            if (typeof dom !== 'undefined') {
606                options.dom = dom;
607            }
608            var searching = el.data('searching');
609            if (typeof searching !== 'undefined') {
610                options.searching = searching;
611            }
612            var responsive = el.data('responsive');
613            if (typeof responsive !== 'undefined') {
614                options.responsive = responsive;
615            }
616            var ordering = el.data('ordering');
617            if (typeof ordering !== 'undefined') {
618                options["ordering"] = ordering;
619            }
620            var order = el.data('order');
621            if (typeof order !== 'undefined' && order) {
622                options["order"] = order;
623            }
624            var colCss = el.data('columns');
625            if (typeof colCss !== 'undefined' && colCss) {
626                options["columns"] = colCss;
627            }
628            var autoWidth = el.data('auto-width');
629            if (typeof autoWidth !== 'undefined') {
630                options["autoWidth"] = autoWidth;
631            }
632            var paging = el.data('paging');
633            if (typeof paging !== 'undefined') {
634                options["paging"] = paging;
635            }
636            var lengthChange = el.data('length-change');
637            if (typeof lengthChange !== 'undefined') {
638                options["lengthChange"] = lengthChange;
639            }
640            var pageLength = el.data('page-length');
641            if (typeof pageLength !== 'undefined') {
642                options["pageLength"] = pageLength;
643            }
644
645            self.tables[id] = self.initTable(el, options);
646        } else if (typeof options !== 'undefined') {
647            var oldTable = self.tables[id];
648            var initOpts = oldTable.init();
649            var newOpts = jQuery.extend( initOpts, options);
650            oldTable.destroy();
651            self.tables[id] = self.initTable(el, newOpts);
652        }
653
654        return self.tables[id];
655    };
656
657    this.initTable = function (el, options) {
658        var table = el.DataTable(options);
659        var self = this;
660        if (el.data('on-draw')) {
661            table.on('draw.dt', function (e, settings) {
662                var namedCallback = el.data('on-draw');
663                if (typeof window[namedCallback] === 'function') {
664                    window[namedCallback](e, settings);
665                }
666            });
667        } else if (el.data('on-draw-rebind-confirmation')) {
668            table.on('draw.dt', function (e) {
669                self.rebindConfirmation(e);
670            });
671        }
672
673        return table;
674    };
675
676    this.rebindConfirmation = function (e) {
677        var self = this;
678        var tableId = e.target.id;
679        var toggles = WHMCS.ui.confirmation.register();
680        for(var i = 0, len = toggles.length; i < len; i++ ) {
681            jQuery(toggles[i]).on(
682                'confirmed.bs.confirmation',
683                function (e)
684                {
685                    e.preventDefault();
686                    WHMCS.http.jqClient.post(
687                        jQuery(e.target).data('target-url'),
688                        {
689                            'token': csrfToken
690                        }
691                    ).done(function (data)
692                    {
693                        if (data.status === 'success' || data.status === 'okay') {
694                            self.getTableById(tableId, undefined).ajax.reload();
695                        }
696                    });
697
698                }
699            );
700        }
701    };
702
703    return this;
704},
705
706clipboard: function() {
707    this.copy = function(e) {
708        e.preventDefault();
709
710        var trigger = $(e.currentTarget);
711        var contentElement = $(trigger).data('clipboard-target');
712        var container = $(contentElement).parent();
713
714        try {
715            var tempElement = $('<textarea>')
716                .css('position', 'fixed')
717                .css('opacity', '0')
718                .css('width', '1px')
719                .css('height', '1px')
720                .val($(contentElement).val());
721
722            container.append(tempElement);
723            tempElement.focus().select();
724            document.execCommand('copy');
725        } finally {
726            tempElement.remove();
727        }
728
729        trigger.tooltip({
730            trigger: 'click',
731            placement: 'bottom'
732        });
733        WHMCS.ui.toolTip.setTip(trigger, 'Copied!');
734        WHMCS.ui.toolTip.hideTip(trigger);
735    };
736
737    return this;
738},
739
740/**
741 * ToolTip and Clipboard behaviors
742 */
743toolTip: function () {
744    this.setTip = function (btn, message) {
745        var tip = btn.data('bs.tooltip');
746        if (tip.hoverState !== 'in') {
747            tip.hoverState = 'in';
748        }
749        btn.attr('data-original-title', message);
750        tip.show();
751
752        return tip;
753    };
754
755    this.hideTip = function (btn) {
756        return setTimeout(function() {
757            btn.data('bs.tooltip').hide()
758        }, 2000);
759    }
760},
761
762jsonForm: function() {
763    this.managedElements = 'input,textarea,select';
764
765    this.initFields = function (form) {
766        var self = this;
767        $(form).find(self.managedElements).each(function () {
768            var field = this;
769
770            $(field).on('keypress change', function () {
771                if (self.fieldHasError(field)) {
772                    self.clearFieldError(field);
773                }
774            });
775        });
776    };
777
778    this.init = function (form) {
779        var self = this;
780
781        self.initFields(form);
782
783        $(form).on('submit', function(e) {
784            e.preventDefault();
785            e.stopPropagation();
786
787            self.clearErrors(form);
788
789            var formModal = $(form).parents('.modal[role="dialog"]').first();
790
791            if ($(formModal).length) {
792                $(formModal).on('show.bs.modal hidden.bs.modal', function() {
793                    self.clearErrors(form);
794                });
795
796                /*
797                 * Make this optional if the form is used for editing
798                 */
799                $(formModal).on('show.bs.modal', function() {
800                    $(form)[0].reset();
801                });
802            }
803
804            WHMCS.http.client.post({
805                url: $(form).attr('action'),
806                data: $(form).serializeArray(),
807            })
808                .done(function (response) {
809                    self.onSuccess(form, response);
810                })
811                .fail(function (jqXHR) {
812                    self.onError(form, jqXHR);
813                })
814                .always(function (data) {
815                    self.onRequestComplete(form, data);
816                });
817        });
818    };
819
820    this.initAll = function () {
821        var self = this;
822
823        $('form[data-role="json-form"]').each(function() {
824            var formElement = this;
825            self.init(formElement);
826        });
827    };
828
829    this.markFieldErrors = function (form, fields)
830    {
831        var self = this;
832        var errorMessage = null;
833        var field, fieldLookup;
834
835        for (var fieldName in fields) {
836            if (fields.hasOwnProperty(fieldName)) {
837                errorMessage = fields[fieldName];
838            }
839
840            fieldLookup = self.managedElements.split(',').map(function(element) {
841                return element + '[name="' + fieldName + '"]';
842            }).join(',');
843
844            field = $(form).find(fieldLookup);
845
846            if (errorMessage) {
847                $(field).parents('.form-group').addClass('has-error');
848                $(field).attr('title', errorMessage);
849                $(field).tooltip();
850            }
851        }
852
853        $(form).find('.form-group.has-error input[title]').first().tooltip('show');
854    };
855
856    this.fieldHasError = function (field) {
857        return $(field).parents('.form-group').hasClass('has-error');
858    };
859
860    this.clearFieldError = function (field) {
861        $(field).tooltip('destroy');
862        $(field).parents('.form-group').removeClass('has-error');
863    };
864
865    this.onSuccess = function (form, response) {
866        var formOnSuccess = $(form).data('on-success');
867
868        if (typeof formOnSuccess === 'function') {
869            formOnSuccess(response.data);
870        }
871    };
872
873    this.onError = function (form, jqXHR) {
874        if (jqXHR.responseJSON && jqXHR.responseJSON.fields && typeof jqXHR.responseJSON.fields === 'object') {
875            this.markFieldErrors(form, jqXHR.responseJSON.fields);
876        } else {
877            // TODO: replace with client-accessible generic error messaging
878            console.log('Unknown error - please try again later.');
879        }
880
881        var formOnError = $(form).data('on-error');
882
883        if (typeof formOnError === 'function') {
884            formOnError(jqXHR);
885        }
886    };
887
888    this.clearErrors = function (form) {
889        var self = this;
890
891        $(form).find(self.managedElements).each(function () {
892            self.clearFieldError(this);
893        })
894    };
895
896    this.onRequestComplete = function (form, data) {
897        // implement as needed
898    };
899
900    return this;
901},
902
903effects: function () {
904    this.errorShake = function (element) {
905        /**
906         * Shake effect without jQuery UI inspired by Hiren Patel | ninty9notout:
907         * @see https://github.com/ninty9notout/jquery-shake/blob/51f3dcf625970c78505bcac831fd9e28fc85d374/jquery.ui.shake.js
908         */
909        options = options || {};
910        var options = $.extend({
911            direction: "left",
912            distance: 8,
913            times: 3,
914            speed: 90
915        }, options);
916
917        return element.each(function () {
918            var el = $(this), props = {
919                position: el.css("position"),
920                top: el.css("top"),
921                bottom: el.css("bottom"),
922                left: el.css("left"),
923                right: el.css("right")
924            };
925
926            el.css("position", "relative");
927
928            var ref = (options.direction === "up" || options.direction === "down") ? "top" : "left";
929            var motion = (options.direction === "up" || options.direction === "left") ? "pos" : "neg";
930
931            var animation = {}, animation1 = {}, animation2 = {};
932            animation[ref] = (motion === "pos" ? "-=" : "+=") + options.distance;
933            animation1[ref] = (motion === "pos" ? "+=" : "-=") + options.distance * 2;
934            animation2[ref] = (motion === "pos" ? "-=" : "+=") + options.distance * 2;
935
936            el.animate(animation, options.speed);
937            for (var i = 1; i < options.times; i++) {
938                el.animate(animation1, options.speed).animate(animation2, options.speed);
939            }
940
941            el.animate(animation1, options.speed).animate(animation, options.speed / 2, function () {
942                el.css(props);
943            });
944        });
945    };
946
947}
948});
949
950/**
951* Form module
952*
953* @copyright Copyright (c) WHMCS Limited 2005-2017
954* @license http://www.whmcs.com/license/ WHMCS Eula
955*/
956(function(module) {
957  if (!WHMCS.hasModule('form')) {
958      WHMCS.loadModule('form', module);
959  }
960})(
961  function () {
962      this.checkAllBound = false;
963
964      this.register = function () {
965          if (!this.checkAllBound) {
966              this.bindCheckAll();
967              this.checkAllBound = true;
968          }
969      };
970
971  this.bindCheckAll = function ()
972  {
973      var huntSelector = '.btn-check-all';
974      jQuery(huntSelector).click(function (e) {
975          var btn = jQuery(e.target);
976          var targetInputs = jQuery(
977              '#' + btn.data('checkbox-container') + ' input[type="checkbox"]'
978          );
979          if (btn.data('btn-check-toggle')) {
980              // one control that changes
981              var textDeselect = 'Deselect All';
982              var textSelect = 'Select All';
983              if (btn.data('label-text-deselect')) {
984                  textDeselect = btn.data('label-text-deselect');
985              }
986              if (btn.data('label-text-select')) {
987                  textSelect = btn.data('label-text-select');
988              }
989
990              if (btn.hasClass('toggle-active')) {
991                  targetInputs.prop('checked',false);
992                  btn.text(textSelect);
993                  btn.removeClass('toggle-active');
994              } else {
995                  targetInputs.prop('checked',true);
996                  btn.text(textDeselect);
997                  btn.addClass('toggle-active');
998              }
999          } else {
1000              // two controls that are static
1001              if (btn.data('btn-toggle-on')) {
1002                  targetInputs.prop('checked',true);
1003              } else {
1004                  targetInputs.prop('checked',false);
1005              }
1006          }
1007      });
1008  };
1009
1010  return this;
1011});
1012
1013/**
1014 * reCaptcha module
1015 *
1016 * @copyright Copyright (c) WHMCS Limited 2005-2018
1017 * @license http://www.whmcs.com/license/ WHMCS Eula
1018 */
1019var recaptchaLoadComplete = false;
1020
1021(function(module) {
1022    if (!WHMCS.hasModule('recaptcha')) {
1023        WHMCS.loadModule('recaptcha', module);
1024    }
1025})(
1026    function () {
1027
1028        this.register = function () {
1029            if (recaptchaLoadComplete) {
1030                return;
1031            }
1032            var postLoad = [];
1033            var recaptchaForms = jQuery(".btn-recaptcha").parents('form');
1034            recaptchaForms.each(function (i, el){
1035                if (typeof recaptchaSiteKey === 'undefined') {
1036                    console.log('Recaptcha site key not defined');
1037                    return;
1038                }
1039                var frm = jQuery(el);
1040                var btnRecaptcha = frm.find(".btn-recaptcha");
1041                var isInvisible = btnRecaptcha.hasClass('btn-recaptcha-invisible'),
1042                    required = (typeof requiredText !== 'undefined') ? requiredText : 'Required';
1043
1044                // if no recaptcha element, make one
1045                var recaptchaContent = frm.find("#divDynamicRecaptcha .g-recaptcha"),
1046                    recaptchaElement = frm.find('.recaptcha-container'),
1047                    appendElement = frm;
1048
1049                if (recaptchaElement.length) {
1050                    appendElement = recaptchaElement;
1051                }
1052                if (!recaptchaContent.length) {
1053                    appendElement.append('<div id="divDynamicRecaptcha" class="g-recaptcha" data-toggle="tooltip" data-placement="bottom" data-trigger="manual" title="' + required + '"></div>');
1054                    recaptchaContent = appendElement.find("#divDynamicRecaptcha");
1055                }
1056                // propagate invisible recaptcha if necessary
1057                if (isInvisible) {
1058                    if (recaptchaContent.data('size') !== 'invisible') {
1059                        recaptchaContent.attr('data-size', 'invisible');
1060                    }
1061                } else {
1062                    recaptchaContent.hide()
1063                }
1064
1065                // ensure site key is available to grecaptcha
1066                recaptchaContent.attr('data-sitekey', recaptchaSiteKey);
1067
1068
1069                // alter form to work around JS behavior on .submit() when there
1070                // there is an input with the name 'submit'
1071                var btnSubmit = frm.find("input[name='submit']");
1072                if (btnSubmit.length) {
1073                    var action = frm.prop('action');
1074                    frm.prop('action', action + '&submit=1');
1075                    btnSubmit.remove();
1076                }
1077
1078                // make callback for grecaptcha to invoke after
1079                // injecting token & make it known via data-callback
1080                var funcName = 'recaptchaCallback' + i;
1081                window[funcName] = function () {
1082                    if (isInvisible) {
1083                        frm.submit();
1084                    }
1085                };
1086                recaptchaContent.attr('data-callback', funcName);
1087
1088                // alter submit button to integrate invisible recaptcha
1089                // otherwise setup a callback to twiddle UI after grecaptcha
1090                // has inject DOM
1091                if (isInvisible) {
1092                    btnRecaptcha.on('click', function (event) {
1093                        if (!grecaptcha.getResponse().trim()) {
1094                            event.preventDefault();
1095                            grecaptcha.execute();
1096                        }
1097                    });
1098                } else {
1099                    postLoad.push(function () {
1100                        recaptchaContent.slideDown('fast', function() {
1101                            // just in case there's a delay in DOM; rare
1102                            recaptchaContent.find(':first').addClass('center-block');
1103                        });
1104                    });
1105                    postLoad.push(function() {
1106                        recaptchaContent.find(':first').addClass('center-block');
1107                    });
1108                }
1109            });
1110
1111            // fetch/invoke the grecaptcha lib
1112            if (recaptchaForms.length) {
1113                var gUrl = "https://www.google.com/recaptcha/api.js";
1114                jQuery.getScript(gUrl, function () {
1115                    for(var i = postLoad.length -1; i >= 0 ; i--){
1116                        postLoad[i]();
1117                    }
1118                });
1119            }
1120            recaptchaLoadComplete = true;
1121        };
1122
1123        return this;
1124    });
vendor: 16,403 bytes, lines 1125-1713
1125
1126    /**
1127 * microplugin.js
1128 * Copyright (c) 2013 Brian Reavis & contributors
1129 *
1130 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
1131 * file except in compliance with the License. You may obtain a copy of the License at:
1132 * http://www.apache.org/licenses/LICENSE-2.0
1133 *
1134 * Unless required by applicable law or agreed to in writing, software distributed under
1135 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
1136 * ANY KIND, either express or implied. See the License for the specific language
1137 * governing permissions and limitations under the License.
1138 *
1139 * @author Brian Reavis <[email protected]>
1140 */
1141
1142(function(root, factory) {
1143	if (typeof define === 'function' && define.amd) {
1144		define(factory);
1145	} else if (typeof exports === 'object') {
1146		module.exports = factory();
1147	} else {
1148		root.MicroPlugin = factory();
1149	}
1150}(this, function() {
1151	var MicroPlugin = {};
1152
1153	MicroPlugin.mixin = function(Interface) {
1154		Interface.plugins = {};
1155
1156		/**
1157		 * Initializes the listed plugins (with options).
1158		 * Acceptable formats:
1159		 *
1160		 * List (without options):
1161		 *   ['a', 'b', 'c']
1162		 *
1163		 * List (with options):
1164		 *   [{'name': 'a', options: {}}, {'name': 'b', options: {}}]
1165		 *
1166		 * Hash (with options):
1167		 *   {'a': { ... }, 'b': { ... }, 'c': { ... }}
1168		 *
1169		 * @param {mixed} plugins
1170		 */
1171		Interface.prototype.initializePlugins = function(plugins) {
1172			var i, n, key;
1173			var self  = this;
1174			var queue = [];
1175
1176			self.plugins = {
1177				names     : [],
1178				settings  : {},
1179				requested : {},
1180				loaded    : {}
1181			};
1182
1183			if (utils.isArray(plugins)) {
1184				for (i = 0, n = plugins.length; i < n; i++) {
1185					if (typeof plugins[i] === 'string') {
1186						queue.push(plugins[i]);
1187					} else {
1188						self.plugins.settings[plugins[i].name] = plugins[i].options;
1189						queue.push(plugins[i].name);
1190					}
1191				}
1192			} else if (plugins) {
1193				for (key in plugins) {
1194					if (plugins.hasOwnProperty(key)) {
1195						self.plugins.settings[key] = plugins[key];
1196						queue.push(key);
1197					}
1198				}
1199			}
1200
1201			while (queue.length) {
1202				self.require(queue.shift());
1203			}
1204		};
1205
1206		Interface.prototype.loadPlugin = function(name) {
1207			var self    = this;
1208			var plugins = self.plugins;
1209			var plugin  = Interface.plugins[name];
1210
1211			if (!Interface.plugins.hasOwnProperty(name)) {
1212				throw new Error('Unable to find "' +  name + '" plugin');
1213			}
1214
1215			plugins.requested[name] = true;
1216			plugins.loaded[name] = plugin.fn.apply(self, [self.plugins.settings[name] || {}]);
1217			plugins.names.push(name);
1218		};
1219
1220		/**
1221		 * Initializes a plugin.
1222		 *
1223		 * @param {string} name
1224		 */
1225		Interface.prototype.require = function(name) {
1226			var self = this;
1227			var plugins = self.plugins;
1228
1229			if (!self.plugins.loaded.hasOwnProperty(name)) {
1230				if (plugins.requested[name]) {
1231					throw new Error('Plugin has circular dependency ("' + name + '")');
1232				}
1233				self.loadPlugin(name);
1234			}
1235
1236			return plugins.loaded[name];
1237		};
1238
1239		/**
1240		 * Registers a plugin.
1241		 *
1242		 * @param {string} name
1243		 * @param {function} fn
1244		 */
1245		Interface.define = function(name, fn) {
1246			Interface.plugins[name] = {
1247				'name' : name,
1248				'fn'   : fn
1249			};
1250		};
1251	};
1252
1253	var utils = {
1254		isArray: Array.isArray || function(vArg) {
1255			return Object.prototype.toString.call(vArg) === '[object Array]';
1256		}
1257	};
1258
1259	return MicroPlugin;
1260}));
1261
1262/**
1263 * sifter.js
1264 * Copyright (c) 2013 Brian Reavis & contributors
1265 *
1266 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
1267 * file except in compliance with the License. You may obtain a copy of the License at:
1268 * http://www.apache.org/licenses/LICENSE-2.0
1269 *
1270 * Unless required by applicable law or agreed to in writing, software distributed under
1271 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
1272 * ANY KIND, either express or implied. See the License for the specific language
1273 * governing permissions and limitations under the License.
1274 *
1275 * @author Brian Reavis <[email protected]>
1276 */
1277
1278(function(root, factory) {
1279	if (typeof define === 'function' && define.amd) {
1280		define(factory);
1281	} else if (typeof exports === 'object') {
1282		module.exports = factory();
1283	} else {
1284		root.Sifter = factory();
1285	}
1286}(this, function() {
1287
1288	/**
1289	 * Textually searches arrays and hashes of objects
1290	 * by property (or multiple properties). Designed
1291	 * specifically for autocomplete.
1292	 *
1293	 * @constructor
1294	 * @param {array|object} items
1295	 * @param {object} items
1296	 */
1297	var Sifter = function(items, settings) {
1298		this.items = items;
1299		this.settings = settings || {diacritics: true};
1300	};
1301
1302	/**
1303	 * Splits a search string into an array of individual
1304	 * regexps to be used to match results.
1305	 *
1306	 * @param {string} query
1307	 * @returns {array}
1308	 */
1309	Sifter.prototype.tokenize = function(query) {
1310		query = trim(String(query || '').toLowerCase());
1311		if (!query || !query.length) return [];
1312
1313		var i, n, regex, letter;
1314		var tokens = [];
1315		var words = query.split(/ +/);
1316
1317		for (i = 0, n = words.length; i < n; i++) {
1318			regex = escape_regex(words[i]);
1319			if (this.settings.diacritics) {
1320				for (letter in DIACRITICS) {
1321					if (DIACRITICS.hasOwnProperty(letter)) {
1322						regex = regex.replace(new RegExp(letter, 'g'), DIACRITICS[letter]);
1323					}
1324				}
1325			}
1326			tokens.push({
1327				string : words[i],
1328				regex  : new RegExp(regex, 'i')
1329			});
1330		}
1331
1332		return tokens;
1333	};
1334
1335	/**
1336	 * Iterates over arrays and hashes.
1337	 *
1338	 * ```
1339	 * this.iterator(this.items, function(item, id) {
1340	 *    // invoked for each item
1341	 * });
1342	 * ```
1343	 *
1344	 * @param {array|object} object
1345	 */
1346	Sifter.prototype.iterator = function(object, callback) {
1347		var iterator;
1348		if (is_array(object)) {
1349			iterator = Array.prototype.forEach || function(callback) {
1350				for (var i = 0, n = this.length; i < n; i++) {
1351					callback(this[i], i, this);
1352				}
1353			};
1354		} else {
1355			iterator = function(callback) {
1356				for (var key in this) {
1357					if (this.hasOwnProperty(key)) {
1358						callback(this[key], key, this);
1359					}
1360				}
1361			};
1362		}
1363
1364		iterator.apply(object, [callback]);
1365	};
1366
1367	/**
1368	 * Returns a function to be used to score individual results.
1369	 *
1370	 * Good matches will have a higher score than poor matches.
1371	 * If an item is not a match, 0 will be returned by the function.
1372	 *
1373	 * @param {object|string} search
1374	 * @param {object} options (optional)
1375	 * @returns {function}
1376	 */
1377	Sifter.prototype.getScoreFunction = function(search, options) {
1378		var self, fields, tokens, token_count, nesting;
1379
1380		self        = this;
1381		search      = self.prepareSearch(search, options);
1382		tokens      = search.tokens;
1383		fields      = search.options.fields;
1384		token_count = tokens.length;
1385		nesting     = search.options.nesting;
1386
1387		/**
1388		 * Calculates how close of a match the
1389		 * given value is against a search token.
1390		 *
1391		 * @param {mixed} value
1392		 * @param {object} token
1393		 * @return {number}
1394		 */
1395		var scoreValue = function(value, token) {
1396			var score, pos;
1397
1398			if (!value) return 0;
1399			value = String(value || '');
1400			pos = value.search(token.regex);
1401			if (pos === -1) return 0;
1402			score = token.string.length / value.length;
1403			if (pos === 0) score += 0.5;
1404			return score;
1405		};
1406
1407		/**
1408		 * Calculates the score of an object
1409		 * against the search query.
1410		 *
1411		 * @param {object} token
1412		 * @param {object} data
1413		 * @return {number}
1414		 */
1415		var scoreObject = (function() {
1416			var field_count = fields.length;
1417			if (!field_count) {
1418				return function() { return 0; };
1419			}
1420			if (field_count === 1) {
1421				return function(token, data) {
1422					return scoreValue(getattr(data, fields[0], nesting), token);
1423				};
1424			}
1425			return function(token, data) {
1426				for (var i = 0, sum = 0; i < field_count; i++) {
1427					sum += scoreValue(getattr(data, fields[i], nesting), token);
1428				}
1429				return sum / field_count;
1430			};
1431		})();
1432
1433		if (!token_count) {
1434			return function() { return 0; };
1435		}
1436		if (token_count === 1) {
1437			return function(data) {
1438				return scoreObject(tokens[0], data);
1439			};
1440		}
1441
1442		if (search.options.conjunction === 'and') {
1443			return function(data) {
1444				var score;
1445				for (var i = 0, sum = 0; i < token_count; i++) {
1446					score = scoreObject(tokens[i], data);
1447					if (score <= 0) return 0;
1448					sum += score;
1449				}
1450				return sum / token_count;
1451			};
1452		} else {
1453			return function(data) {
1454				for (var i = 0, sum = 0; i < token_count; i++) {
1455					sum += scoreObject(tokens[i], data);
1456				}
1457				return sum / token_count;
1458			};
1459		}
1460	};
1461
1462	/**
1463	 * Returns a function that can be used to compare two
1464	 * results, for sorting purposes. If no sorting should
1465	 * be performed, `null` will be returned.
1466	 *
1467	 * @param {string|object} search
1468	 * @param {object} options
1469	 * @return function(a,b)
1470	 */
1471	Sifter.prototype.getSortFunction = function(search, options) {
1472		var i, n, self, field, fields, fields_count, multiplier, multipliers, get_field, implicit_score, sort;
1473
1474		self   = this;
1475		search = self.prepareSearch(search, options);
1476		sort   = (!search.query && options.sort_empty) || options.sort;
1477
1478		/**
1479		 * Fetches the specified sort field value
1480		 * from a search result item.
1481		 *
1482		 * @param  {string} name
1483		 * @param  {object} result
1484		 * @return {mixed}
1485		 */
1486		get_field = function(name, result) {
1487			if (name === '$score') return result.score;
1488			return getattr(self.items[result.id], name, options.nesting);
1489		};
1490
1491		// parse options
1492		fields = [];
1493		if (sort) {
1494			for (i = 0, n = sort.length; i < n; i++) {
1495				if (search.query || sort[i].field !== '$score') {
1496					fields.push(sort[i]);
1497				}
1498			}
1499		}
1500
1501		// the "$score" field is implied to be the primary
1502		// sort field, unless it's manually specified
1503		if (search.query) {
1504			implicit_score = true;
1505			for (i = 0, n = fields.length; i < n; i++) {
1506				if (fields[i].field === '$score') {
1507					implicit_score = false;
1508					break;
1509				}
1510			}
1511			if (implicit_score) {
1512				fields.unshift({field: '$score', direction: 'desc'});
1513			}
1514		} else {
1515			for (i = 0, n = fields.length; i < n; i++) {
1516				if (fields[i].field === '$score') {
1517					fields.splice(i, 1);
1518					break;
1519				}
1520			}
1521		}
1522
1523		multipliers = [];
1524		for (i = 0, n = fields.length; i < n; i++) {
1525			multipliers.push(fields[i].direction === 'desc' ? -1 : 1);
1526		}
1527
1528		// build function
1529		fields_count = fields.length;
1530		if (!fields_count) {
1531			return null;
1532		} else if (fields_count === 1) {
1533			field = fields[0].field;
1534			multiplier = multipliers[0];
1535			return function(a, b) {
1536				return multiplier * cmp(
1537					get_field(field, a),
1538					get_field(field, b)
1539				);
1540			};
1541		} else {
1542			return function(a, b) {
1543				var i, result, a_value, b_value, field;
1544				for (i = 0; i < fields_count; i++) {
1545					field = fields[i].field;
1546					result = multipliers[i] * cmp(
1547						get_field(field, a),
1548						get_field(field, b)
1549					);
1550					if (result) return result;
1551				}
1552				return 0;
1553			};
1554		}
1555	};
1556
1557	/**
1558	 * Parses a search query and returns an object
1559	 * with tokens and fields ready to be populated
1560	 * with results.
1561	 *
1562	 * @param {string} query
1563	 * @param {object} options
1564	 * @returns {object}
1565	 */
1566	Sifter.prototype.prepareSearch = function(query, options) {
1567		if (typeof query === 'object') return query;
1568
1569		options = extend({}, options);
1570
1571		var option_fields     = options.fields;
1572		var option_sort       = options.sort;
1573		var option_sort_empty = options.sort_empty;
1574
1575		if (option_fields && !is_array(option_fields)) options.fields = [option_fields];
1576		if (option_sort && !is_array(option_sort)) options.sort = [option_sort];
1577		if (option_sort_empty && !is_array(option_sort_empty)) options.sort_empty = [option_sort_empty];
1578
1579		return {
1580			options : options,
1581			query   : String(query || '').toLowerCase(),
1582			tokens  : this.tokenize(query),
1583			total   : 0,
1584			items   : []
1585		};
1586	};
1587
1588	/**
1589	 * Searches through all items and returns a sorted array of matches.
1590	 *
1591	 * The `options` parameter can contain:
1592	 *
1593	 *   - fields {string|array}
1594	 *   - sort {array}
1595	 *   - score {function}
1596	 *   - filter {bool}
1597	 *   - limit {integer}
1598	 *
1599	 * Returns an object containing:
1600	 *
1601	 *   - options {object}
1602	 *   - query {string}
1603	 *   - tokens {array}
1604	 *   - total {int}
1605	 *   - items {array}
1606	 *
1607	 * @param {string} query
1608	 * @param {object} options
1609	 * @returns {object}
1610	 */
1611	Sifter.prototype.search = function(query, options) {
1612		var self = this, value, score, search, calculateScore;
1613		var fn_sort;
1614		var fn_score;
1615
1616		search  = this.prepareSearch(query, options);
1617		options = search.options;
1618		query   = search.query;
1619
1620		// generate result scoring function
1621		fn_score = options.score || self.getScoreFunction(search);
1622
1623		// perform search and sort
1624		if (query.length) {
1625			self.iterator(self.items, function(item, id) {
1626				score = fn_score(item);
1627				if (options.filter === false || score > 0) {
1628					search.items.push({'score': score, 'id': id});
1629				}
1630			});
1631		} else {
1632			self.iterator(self.items, function(item, id) {
1633				search.items.push({'score': 1, 'id': id});
1634			});
1635		}
1636
1637		fn_sort = self.getSortFunction(search, options);
1638		if (fn_sort) search.items.sort(fn_sort);
1639
1640		// apply limits
1641		search.total = search.items.length;
1642		if (typeof options.limit === 'number') {
1643			search.items = search.items.slice(0, options.limit);
1644		}
1645
1646		return search;
1647	};
1648
1649	// utilities
1650	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1651
1652	var cmp = function(a, b) {
1653		if (typeof a === 'number' && typeof b === 'number') {
1654			return a > b ? 1 : (a < b ? -1 : 0);
1655		}
1656		a = asciifold(String(a || ''));
1657		b = asciifold(String(b || ''));
1658		if (a > b) return 1;
1659		if (b > a) return -1;
1660		return 0;
1661	};
1662
1663	var extend = function(a, b) {
1664		var i, n, k, object;
1665		for (i = 1, n = arguments.length; i < n; i++) {
1666			object = arguments[i];
1667			if (!object) continue;
1668			for (k in object) {
1669				if (object.hasOwnProperty(k)) {
1670					a[k] = object[k];
1671				}
1672			}
1673		}
1674		return a;
1675	};
1676
1677	/**
1678	 * A property getter resolving dot-notation
1679	 * @param  {Object}  obj     The root object to fetch property on
1680	 * @param  {String}  name    The optionally dotted property name to fetch
1681	 * @param  {Boolean} nesting Handle nesting or not
1682	 * @return {Object}          The resolved property value
1683	 */
1684	var getattr = function(obj, name, nesting) {
1685	    if (!obj || !name) return;
1686	    if (!nesting) return obj[name];
1687	    var names = name.split(".");
1688	    while(names.length && (obj = obj[names.shift()]));
1689	    return obj;
1690	};
1691
1692	var trim = function(str) {
1693		return (str + '').replace(/^\s+|\s+$|/g, '');
1694	};
1695
1696	var escape_regex = function(str) {
1697		return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
1698	};
1699
1700	var is_array = Array.isArray || (typeof $ !== 'undefined' && $.isArray) || function(object) {
1701		return Object.prototype.toString.call(object) === '[object Array]';
1702	};
1703
1704	var DIACRITICS = {
1705		'a': '[aḀḁĂăÂâǍǎȺⱥȦȧẠạÄäÀàÁáĀāÃãÅåąĄÃąĄ]',
1706		'b': '[b␢βΒB฿𐌁ᛒ]',
1707		'c': '[cĆćĈĉČčĊċC̄c̄ÇçḈḉȻȼƇƈɕᴄCc]',
1708		'd': '[dĎďḊḋḐḑḌḍḒḓḎḏĐđD̦d̦ƉɖƊɗƋƌᵭᶁᶑȡᴅDdð]',
1709		'e': '[eÉéÈèÊêḘḙĚěĔĕẼẽḚḛẺẻĖėËëĒēȨȩĘęᶒɆɇȄȅẾếỀềỄễỂểḜḝḖḗḔḕȆȇẸẹỆệⱸᴇEeɘǝƏƐε]',
1710		'f': '[fƑƒḞḟ]',
1711		'g': '[gɢ₲ǤǥĜĝĞğĢģƓɠĠġ]',
1712		'h': '[hĤĥĦħḨḩẖẖḤḥḢḣɦʰǶƕ]',
1713		'i': '[iÍíÌìĬĭÎîǏǐÏïḮÃ
1713¡Â¸Â¯Ã„¨Ä©Ä®Ä¯ÄªÄ«á»ˆá»‰ÈˆÈ‰ÈŠÈ‹á»Šá»‹á¸¬á¸­Æ—ɨɨ̆ᵻᶖİiIıɪIi]',
1714		'j': '[jȷĴĵɈɉʝɟʲ]',
1715		'k': '[kƘƙꝀꝁḰḱǨǩḲḳḴḵκϰ₭]',
1716		'l': '[lŁłĽľĻļĹĺḶḷḸḹḼḽḺḻĿŀȽƚⱠⱡⱢɫɬᶅɭȴʟLl]',
1717		'n': '[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲȠƞᵰᶇɳȵɴNnŊŋ]',
1718		'o': '[oØøÖöÓóÒòÔôǑǒŐőŎŏȮȯỌọƟɵƠơỎỏŌōÕõǪǫȌȍՕօ]',
1719		'p': '[pṔṕṖṗⱣᵽƤƥᵱ]',
1720		'q': '[qꝖꝗʠɊɋꝘꝙq̃]',
1721		'r': '[rŔŕɌɍŘřŖŗṘṙȐȑȒȓṚṛⱤɽ]',
1722		's': '[sŚśṠṡṢṣꞨꞩŜŝŠšŞşȘșS̈s̈]',
1723		't': '[tŤťṪṫŢţṬṭƮʈȚțṰṱṮṯƬƭ]',
1724		'u': '[uŬŭɄʉỤụÜüÚúÙùÛûǓǔŰűŬŭƯưỦủŪūŨũŲųȔȕ∪]',
1725		'v': '[vṼṽṾṿƲʋꝞꝟⱱʋ]',
1726		'w': '[wẂẃẀẁŴŵẄẅẆẇẈẉ]',
1727		'x': '[xẌẍẊẋχ]',
1728		'y': '[yÝýỲỳŶŷŸÿỸỹẎẏỴỵɎɏƳƴ]',
1729		'z': '[zŹźẐẑŽžŻżẒẓẔẕƵƶ]'
1730	};
1731
1732	var asciifold = (function() {
1733		var i, n, k, chunk;
1734		var foreignletters = '';
1735		var lookup = {};
1736		for (k in DIACRITICS) {
1737			if (DIACRITICS.hasOwnProperty(k)) {
1738				chunk = DIACRITICS[k].substring(2, DIACRITICS[k].length - 1);
1739				foreignletters += chunk;
1740				for (i = 0, n = chunk.length; i < n; i++) {
1741					lookup[chunk.charAt(i)] = k;
1742				}
1743			}
1744		}
1745		var regexp = new RegExp('[' +  foreignletters + ']', 'g');
1746		return function(str) {
1747			return str.replace(regexp, function(foreignletter) {
1748				return lookup[foreignletter];
1749			}).toLowerCase();
1750		};
1751	})();
1752
1753
1754	// export
1755	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1756
1757	return Sifter;
1758}));
1759
1760/**
1761 * General utilities module
1762 *
1763 * @copyright Copyright (c) WHMCS Limited 2005-2017
1764 * @license http://www.whmcs.com/license/ WHMCS Eula
1765 */
1766(function(module) {
1767    if (!WHMCS.hasModule('utils')) {
1768        WHMCS.loadModule('utils', module);
1769    }
1770})(
1771function () {
1772    /**
1773     * Not crypto strong; server-side must discard for
1774     * something with more entropy; the value is sufficient
1775     * for strong client-side validation check
1776     */
1777    this.simpleRNG = function () {
1778        var chars = './$_-#!,^*()|';
1779        var r = 0;
1780        for (var i = 0; r < 3; i++) {
1781            r += Math.floor((Math.random() * 10) / 2);
1782        }
1783        r = Math.floor(r);
1784        var s = '';
1785        for (var x = 0; x < r; x++) {
1786            v = (Math.random() + 1).toString(24).split('.')[1];
1787            if ((Math.random()) > 0.5) {
1788                s += btoa(v).substr(0,4)
1789            } else {
1790                s += v
1791            }
1792
1793            if ((Math.random()) > 0.5) {
1794                s += chars.substr(
1795                    Math.floor(Math.random() * 13),
1796                    1
1797                );
1798            }
1799        }
1800
1801        return s;
1802    };
1803
1804    this.getRouteUrl = function (path) {
1805        return whmcsBaseUrl + "/index.php?rp=" + path;
1806    };
1807
1808    this.validateBaseUrl = function() {
1809        if (typeof window.whmcsBaseUrl === 'undefined') {
1810            console.log('Warning: The WHMCS Base URL definition is missing '
1811                + 'from your active template. Please refer to '
1812                + 'https://docs.whmcs.com/WHMCS_Base_URL_Template_Variable '
1813                + 'for more information and details of how to resolve this '
1814                + 'warning.');
1815            window.whmcsBaseUrl = this.autoDetermineBaseUrl();
1816            window.whmcsBaseUrlAutoSet = true;
1817        } else if (window.whmcsBaseUrl === ''
1818            && typeof window.whmcsBaseUrlAutoSet !== 'undefined'
1819            && window.whmcsBaseUrlAutoSet === true
1820        ) {
1821            window.whmcsBaseUrl = this.autoDetermineBaseUrl();
1822        }
1823    };
1824
1825    this.autoDetermineBaseUrl = function() {
1826        var windowLocation = window.location.href;
1827        var phpExtensionLocation = -1;
1828
1829        if (typeof windowLocation !== 'undefined') {
1830            phpExtensionLocation = windowLocation.indexOf('.php');
1831        }
1832
1833        if (phpExtensionLocation === -1) {
1834            windowLocation = jQuery('#Primary_Navbar-Home a').attr('href');
1835            if (typeof windowLocation !== 'undefined') {
1836                phpExtensionLocation = windowLocation.indexOf('.php');
1837            }
1838        }
1839
1840        if (phpExtensionLocation !== -1) {
1841            windowLocation = windowLocation.substring(0, phpExtensionLocation);
1842            var lastTrailingSlash = windowLocation.lastIndexOf('/');
1843            if (lastTrailingSlash !== false) {
1844                return windowLocation.substring(0, lastTrailingSlash);
1845            }
1846        }
1847
1848        return '';
1849    };
1850
1851    this.normaliseStringValue = function(status) {
1852        return status ? status.toLowerCase().replace(/\s/g, '-') : '';
1853    };
1854
1855    this.generatePassword = function(len) {
1856        var charset = this.getPasswordCharacterSet();
1857        var result = "";
1858        for (var i = 0; len > i; i++)
1859            result += charset[this.randomInt(charset.length)];
1860        return result;
1861    };
1862    this.getPasswordCharacterSet = function() {
1863        var rawCharset = '0123456789'
1864            + 'abcdefghijklmnopqrstuvwxyz'
1865            + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
1866            + '!#$%()*+,-.:;=@_|{ldelim}{rdelim}~';
1867
1868        // Parse UTF-16, remove duplicates, convert to array of strings
1869        var charset = [];
1870        for (var i = 0; rawCharset.length > i; i++) {
1871            var c = rawCharset.charCodeAt(i);
1872            if (0xD800 > c || c >= 0xE000) {  // Regular UTF-16 character
1873                var s = rawCharset.charAt(i);
1874                if (charset.indexOf(s) == -1)
1875                    charset.push(s);
1876                continue;
1877            }
1878            if (0xDC00 > c ? rawCharset.length > i + 1 : false) {  // High surrogate
1879                var d = rawCharset.charCodeAt(i + 1);
1880                if (d >= 0xDC00 ? 0xE000 > d : false) {  // Low surrogate
1881                    var s = rawCharset.substring(i, i + 2);
1882                    i++;
1883                    if (charset.indexOf(s) == -1)
1884                        charset.push(s);
1885                    continue;
1886                }
1887            }
1888            throw "Invalid UTF-16";
1889        }
1890        return charset;
1891    };
1892    this.randomInt = function(n) {
1893        var x = this.randomIntMathRandom(n);
1894        x = (x + this.randomIntBrowserCrypto(n)) % n;
1895        return x;
1896    };
1897    this.randomIntMathRandom = function(n) {
1898        var x = Math.floor(Math.random() * n);
1899        if (0 > x || x >= n)
1900            throw "Arithmetic exception";
1901        return x;
1902    };
1903    this.randomIntBrowserCrypto = function(n) {
1904        var cryptoObject = null;
1905
1906        if ("crypto" in window)
1907            cryptoObject = crypto;
1908        else if ("msCrypto" in window)
1909            cryptoObject = msCrypto;
1910        else
1911            return 0;
1912
1913        if (!("getRandomValues" in cryptoObject) || !("Uint32Array" in window) || typeof Uint32Array != "function")
1914            cryptoObject = null;
1915
1916        if (cryptoObject == null)
1917            return 0;
1918
1919        // Generate an unbiased sample
1920        var x = new Uint32Array(1);
1921        do cryptoObject.getRandomValues(x);
1922        while (x[0] - x[0] % n > 4294967296 - n);
1923        return x[0] % n;
1924    };
1925
1926    return this;
1927});
1928
1929WHMCS.utils.validateBaseUrl();
1930
1931// Default catch for all other popovers
1932jQuery('[data-toggle="popover"]').popover({
1933    html: true
1934});
1935
1936    // Enable tooltips
1937    // Attach function to body so tooltips inserted by ajax will load
1938    jQuery(function(jQuery){
1939        jQuery('body').tooltip({
1940            selector: '[data-toggle="tooltip"]'
1941        });
1942    });
1943
1944// Logic to dismiss popovers on click outside
1945jQuery('body').on('click', function (e) {
1946    jQuery('[data-toggle="popover"]').each(function () {
1947        if (!jQuery(this).is(e.target) && jQuery(this).has(e.target).length === 0 && jQuery('.popover').has(e.target).length === 0) {
1948            jQuery(this).popover('hide');
1949        }
1950    });
1951});
1952
1953(function(j) {
1954  j.fn.extend({
1955    accordion: function() {
1956      return this.each(function() {
1957        function b(c, b) {
1958          jQuery(c).parent(d).siblings().removeClass(e).children(f).slideUp(g);
1959          jQuery(c).siblings(f)[b || h](b == "show" ? g : !1, function() {
1960            jQuery(c).siblings(f).is(":visible") ? jQuery(c).parents(d).not(a.parents()).addClass(e) : jQuery(c).parent(d).removeClass(e);
1961            b == "show" && jQuery(c).parents(d).not(a.parents()).addClass(e);
1962            jQuery(c).parents().show()
1963          })
1964        }
1965        var a = jQuery(this), e = "active", h = "slideToggle", f = "ul, div", g = "fast", d = "li";
1966        if (a.data("accordiated"))
1967          return !1;
1968        jQuery.each(a.find("ul, li>div"),
1969          function() {
1970            jQuery(this).data("accordiated", !0);
1971            jQuery(this).hide()
1972          });
1973        jQuery.each(a.find("a"), function() {
1974          jQuery(this).click(function() {
1975            b(this, h)
1976          });
1977          jQuery(this).bind("activate-node", function() {
1978            a.find(f).not(jQuery(this).parents()).not(jQuery(this).siblings()).slideUp(g);
1979            b(this, "slideDown")
1980          })
1981        });
1982        var i = location.hash ? a.find("a[href='" + location.hash + "']")[0]: a.find("li.current a")[0];
1983        i && b(i, !1)
1984      })
1985}
1986})
1987})(jQuery);
1988
1989jQuery(function(){
1990    var url = document.location.toString();
1991    if (url.match('#')) {
1992        jQuery('.nav a[href="#' + url.split('#')[1] + '"]').tab('show');
1993    }
1994});
1995
1996// Checkboxes Toggle
1997// ===============================
1998function toggleCheckboxes(classname) {
1999  jQuery("."+classname).attr('checked',!jQuery("."+classname+":first").is(':checked'));
2000}
2001
2002// Disable Field Class
2003// ===============================
2004function disableFields(radiobutton, classname,disable) {
2005
2006    $('.domain-ns-options').each(function() {
2007        $( this ).removeClass( "active" );
2008    });
2009    $(radiobutton).closest('.domain-ns-options').addClass("active")
2010
2011    // $(radiobutton).closest('.domain-ns-options').addClass("active");
2012    //
2013
2014
2015  if (disable){
2016      jQuery("."+classname).attr("disabled","disabled");
2017  }else{
2018      jQuery("."+classname).removeAttr("disabled");
2019  }
2020}
2021
2022function navigateToTab(link, tabname){
2023
2024    $('.domain-tab').each(function() {
2025        $( this ).removeClass( "active" );
2026    });
2027
2028    $('.domain-link').each(function() {
2029        $( this ).removeClass( "active" );
2030    });
2031
2032    $(link).closest('.domain-link').addClass("active")
2033    $("#"+tabname).addClass("active");
2034}
2035
2036function navigateToContact(elem = null, contact){
2037    $('.contact-container').each(function() {
2038        $( this ).removeClass( "active" );
2039    });
2040    $('.domain-contacts').each(function() {
2041        $( this ).removeClass( "active" );
2042    });
2043    $("#"+contact).addClass("active");
2044    $(elem).closest('.domain-contacts').addClass("active");
2045}
2046
2047function showNewBillingAddressFields() {
2048    jQuery('#newBillingAddress').slideDown();
2049}
2050
2051function hideNewBillingAddressFields() {
2052    jQuery('#newBillingAddress').slideUp();
2053}
2054
2055/**
2056 * Show new credit card input fields.
2057 */
2058function showNewCardInputFields() {
2059    if (jQuery(".cc-details").hasClass("hidden")) {
2060        jQuery(".cc-details").hide().removeClass("hidden");
2061    }
2062    jQuery(".cc-details").slideDown();
2063
2064    jQuery("#billingAddressChoice")
2065        .slideDown()
2066        .find('input[name="billingcontact"]')
2067        .first()
2068        .iCheck('check');
2069}
2070/**
2071 * Hide new credit card input fields.
2072 */
2073function hideNewCardInputFields() {
2074    hideNewBillingAddressFields();
2075
2076    jQuery(".cc-details").slideUp();
2077    jQuery("#billingAddressChoice").slideUp();
2078
2079    var selectedCcInfo = jQuery('input[name="ccinfo"]:checked');
2080
2081    var selectedCcBillingContactId = jQuery(selectedCcInfo).data('billing-contact-id');
2082
2083    var selectedBillingContactData = jQuery('.billing-contact-info[data-billing-contact-id="' + selectedCcBillingContactId + '"]');
2084
2085    if (selectedBillingContactData.length) {
2086        jQuery('.billing-contact-info').hide();
2087        jQuery(selectedBillingContactData).show();
2088    }
2089}
2090
2091function showRemoteInputForm() {
2092    var remoteInputForm = jQuery('#remoteInput'),
2093        paymentGateway = remoteInputForm.data('payment-module'),
2094        errorDiv = jQuery('#remoteInputError'),
2095        btnSubmitContainer = jQuery('#btnSubmitContainer');
2096    if (btnSubmitContainer.is(':visible')) {
2097        btnSubmitContainer.slideUp('fast');
2098    }
2099    if (remoteInputForm.not(':visible')) {
2100        remoteInputForm.html(
2101            jQuery('#remoteInputLoading').html()
2102        )
2103            .hide()
2104            .removeClass('hidden')
2105            .slideDown('fast', function() {
2106                var invoiceId = jQuery('input[name="invoiceid"]').val();
2107                WHMCS.http.jqClient.jsonPost({
2108                    url: WHMCS.utils.getRouteUrl('/account/paymentmethods/remote-input'),
2109                    data: 'gateway=' + paymentGateway + '&invoice_id=' + invoiceId + '&token=' + csrfToken,
2110                    success: function(response) {
2111                        remoteInputForm.slideUp('fast').html(response.output).slideDown('fast');
2112                    },
2113                    warning: function(error) {
2114                        errorDiv.find('div').html(error);
2115                        remoteInputForm.slideUp('fast').html(errorDiv.html()).slideDown('fast');
2116                    },
2117                    fail: function(error) {
2118                        errorDiv.find('div').html(error);
2119                        remoteInputForm.slideUp('fast').html(errorDiv.html()).slideDown('fast');
2120                    }
2121                });
2122            });
2123    }
2124}
2125
2126function hideRemoteInputForm() {
2127    var remoteInputForm = jQuery('#remoteInput'),
2128        btnSubmitContainer = jQuery('#btnSubmitContainer');
2129    if (remoteInputForm.is(':visible')) {
2130        remoteInputForm.slideUp('fast').html('').addClass('hidden');
2131    }
2132    if (btnSubmitContainer.not(':visible')) {
2133        btnSubmitContainer.slideDown('fast');
2134    }
2135}
2136/**
2137 * Redirect on click if an element is not a button or link.
2138 *
2139 * Where table rows are clickable, we only want to redirect if the row
2140 * itself is clicked. If a button or link within the row is clicked,
2141 * the event tied to that object should be executed. This function
2142 * stops the standard JS event bubbling required to make that happen.
2143 *
2144 * @param {object} clickEvent jQuery click event
2145 * @param {string} target     Redirect location
2146 * @param {bool} newWindow    Open link in new window
2147 */
2148function clickableSafeRedirect(clickEvent, target, newWindow) {
2149    var eventSource = clickEvent.target.tagName.toLowerCase();
2150    var eventParent = clickEvent.target.parentNode.tagName.toLowerCase();
2151    var eventTable = clickEvent.target.parentNode.parentNode.parentNode;
2152    if (jQuery(eventTable).hasClass('collapsed')) {
2153        // This is a mobile device sized display, and datatables has triggered folding
2154        return false;
2155    }
2156    if (eventSource === 'i' && jQuery(clickEvent.target).hasClass('ssl-required')) {
2157        return false;
2158    }
2159    if(eventSource != 'button' && eventSource != 'a') {
2160        if(eventParent != 'button' && eventParent != 'a') {
2161            if (newWindow) {
2162                window.open(target);
2163            } else {
2164                window.location.href = target;
2165            }
2166        }
2167    }
2168}
2169
2170// Open Centered Popup
2171// ===============================
2172function popupWindow(addr,popname,w,h,features) {
2173  var winl = (screen.width-w)/2;
2174  var wint = (screen.height-h)/2;
2175  if (winl < 0) winl = 0;
2176  if (wint < 0) wint = 0;
2177  var settings = 'height=' + h + ',';
2178  settings += 'width=' + w + ',';
2179  settings += 'top=' + wint + ',';
2180  settings += 'left=' + winl + ',';
2181  settings += features;
2182  win = window.open(addr,popname,settings);
2183  win.window.focus();
2184}
2185// Support Tickets
2186// ===============================
2187function extraTicketAttachment() {
2188  jQuery("#fileuploads").append('<p><div class="input-group"><span class="input-group-btn"><span class="btn btn-default btn-sm btn-file"><span class="glyphicon glyphicon-folder-open"></span> <input type="file" name="attachments[]" multiple=""></span></span><input type="text" class="form-control input-sm" readonly=""></div></p>');
2189  jQuery(document).ready( function() {
2190    jQuery('.btn-file :file').on('fileselect', function(event, numFiles, label) {
2191
2192      var input = jQuery(this).parents('.input-group').find(':text'),
2193      log = numFiles > 1 ? numFiles + ' files selected' : label;
2194
2195      if( input.length ) {
2196        input.val(log);
2197      } else {
2198        if( log ) alert(log);
2199      }
2200
2201    });
2202  });
2203}
2204function rating_hover(id) {
2205    var selrating=id.split('_');
2206    for(var i=1; i<=5; i++){
2207        if(i<=selrating[1]) document.getElementById(selrating[0]+'_'+i).childNodes[0].className="glyphicon glyphicon-star";
2208        if(i>selrating[1]) document.getElementById(selrating[0]+'_'+i).childNodes[0].className="glyphicon glyphicon-star-empty";
2209    }
2210}
2211function rating_leave(id){
2212    for(var i=1; i<=5; i++){
2213        document.getElementById(id+'_'+i).childNodes[0].className="glyphicon glyphicon-star-empty";
2214    }
2215}
2216function rating_select(tid,c,id){
2217  window.location='viewticket.php?tid='+tid+'&c='+c+'&rating='+id;
2218}
2219// Sidebar
2220// ===============================
2221jQuery('ul.nav-list').accordion();
2222jQuery('.site-holder.container .nav > li > ul > li.active').parent().css('display','block');
2223jQuery('.site-holder.container.mini-sidebar .nav > li > ul > li.active').parent().css('display','none');
2224jQuery('.btn-nav-toggle-responsive').click(function(){
2225  jQuery('.left-sidebar').toggleClass('show-fullsidebar');
2226});
2227jQuery('li.nav-toggle > button').click(function(e){
2228        //Set cookie
2229        if(jQuery.cookie('minibar')==null||jQuery.cookie('minibar')==0) {
2230          jQuery.cookie('minibar',1);
2231          jQuery('li.submenu').removeClass('active');
2232          jQuery('li.submenu > ul').hide();
2233          jQuery('.fa.arrow.rotate').removeClass('down');
2234        }
2235        else {
2236          jQuery.cookie('minibar',0);
2237        }
2238        e.preventDefault();
2239        changeSidebarState();
2240      });
2241    //Load sidebar state
2242    jQuery(function(){
2243      if(jQuery.cookie('minibar')==1) {
2244        changeSidebarStateNoAnimate();
2245      }
2246    });
2247    function changeSidebarState(){
2248      jQuery('.hidden-minibar').toggleClass("hide");
2249      jQuery('.site-holder').toggleClass("mini-sidebar");
2250
2251      if(jQuery('.toggle-right').hasClass('fa-angle-double-right')){ jQuery('.toggle-right').removeClass('fa-angle-double-right').addClass('fa-angle-double-left'); }
2252            else { jQuery('.toggle-right').removeClass('fa-angle-double-left').addClass('fa-angle-double-right'); }
2253
2254      if(jQuery('.toggle-left').hasClass('fa-angle-double-left')){ jQuery('.toggle-left').removeClass('fa-angle-double-left').addClass('fa-angle-double-right'); }
2255            else { jQuery('.toggle-left').removeClass('fa-angle-double-right').addClass('fa-angle-double-left'); }
2256
2257      if(jQuery('.site-holder').hasClass('mini-sidebar'))
2258      {
2259        jQuery('.sidebar-holder').tooltip({
2260          selector: "a",
2261          container: "body",
2262          placement: "right"
2263        });
2264        jQuery('li.submenu ul').tooltip('destroy');
2265      }
2266      else
2267      {
2268        jQuery('.sidebar-holder').tooltip('destroy');
2269      }
2270    }
2271    function changeSidebarStateNoAnimate(){
2272      jQuery('.toggle-left').removeClass('fa-angle-double-left').addClass('fa-angle-double-right');
2273      jQuery('.toggle-right').removeClass('fa-angle-double-right').addClass('fa-angle-double-left');
2274      if(jQuery('.site-holder').hasClass('mini-sidebar'))
2275      {
2276        jQuery('.sidebar-holder').tooltip({
2277          selector: "a",
2278          container: "body",
2279          placement: "right"
2280        });
2281        jQuery('li.submenu ul').tooltip('destroy');
2282      }
2283      else
2284      {
2285        jQuery('.sidebar-holder').tooltip('destroy');
2286      }
2287    }
2288      //
2289      if(jQuery('.site-holder').hasClass('mini-sidebar'))
2290      {
2291        jQuery('.sidebar-holder').tooltip({
2292          selector: "a",
2293          container: "body",
2294          placement: "right"
2295        });
2296        jQuery('li.submenu').tooltip('destroy');
2297      }
2298      else
2299      {
2300        jQuery('.sidebar-holder').tooltip('destroy');
2301      }
2302      jQuery('.show-info').click(function(){
2303       jQuery(this).toggleClass("down");
2304        jQuery('.page-information').toggleClass('hidden');
2305      });
2306
2307      jQuery('.show-task-info').click(function(){
2308       jQuery(this).toggleClass("down");
2309        jQuery(this).parents('.todo-task').find('.task-info').toggleClass('hidden');
2310      });
2311
2312      jQuery('.site-holder.mini-sidebar .content').click(function () {
2313       jQuery('.site-holder.mini-sidebar li.submenu ul').hide();
2314       jQuery('.site-holder.mini-sidebar li.submenu').removeClass('active');
2315
2316     });
2317
2318// Rotate icon
2319// ===============================
2320jQuery(".sidebar-holder .nav a.dropdown").click(function(){
2321jQuery('.rotate', this).toggleClass("down")  ;
2322})
2323
2324
2325// Language Dropdown
2326// ===============================
2327
2328
2329jQuery('.settings-toggle').click(function(e){
2330  e.preventDefault();
2331  jQuery('.right-sidebar').toggleClass('right-sidebar-hidden');
2332});
2333jQuery('.theme-panel-close').click(function(){
2334  jQuery('.right-sidebar').toggleClass('right-sidebar-hidden');
2335});
2336
2337jQuery(function(){
2338  jQuery('.right-sidebar-holder').perfectScrollbar();
2339});
2340
2341// Scrollbar
2342// ===============================
2343
2344
2345jQuery(function(){
2346  jQuery('.ps-messages').perfectScrollbar();
2347});
2348
2349jQuery(function(){
2350  jQuery('.ps-languages').perfectScrollbar();
2351});
2352
2353jQuery(function(){
2354  jQuery('.server-list').perfectScrollbar();
2355});
2356
2357// Server List
2358// ===============================
2359
2360
2361jQuery(function(){
2362        jQuery('.server-list').tooltip({
2363          selector: "a",
2364          container: "body",
2365          placement: "top"
2366        });
2367});
2368
2369 // Enable Switches for Checkboxes
2370    if (jQuery.prototype.bootstrapSwitch) {
2371        jQuery(".toggle-switch-success").bootstrapSwitch({
2372            onColor: 'success'
2373        });
2374    }
2375
2376// Handle Single Sign-On Toggle Setting
2377    jQuery("#inputAllowSso").on('switchChange.bootstrapSwitch', function(event, isChecked) {
2378        if (isChecked) {
2379            jQuery("#ssoStatusTextEnabled").removeClass('hidden').show();
2380            jQuery("#ssoStatusTextDisabled").hide();
2381        } else {
2382            jQuery("#ssoStatusTextDisabled").removeClass('hidden').show();
2383            jQuery("#ssoStatusTextEnabled").hide();
2384        }
2385        WHMCS.http.jqClient.post("clientarea.php", jQuery("#frmSingleSignOn").serialize());
2386    });
2387
2388
2389    // Single Sign-On call for Product/Service
2390    jQuery('.btn-service-sso').on('click', function(e) {
2391        e.preventDefault();
2392        var button = jQuery(this);
2393
2394        var form = button.parents('form');
2395
2396        if (form.length == 0) {
2397            form = button.find('form');
2398        }
2399        if (form.hasClass('disabled')) {
2400            return;
2401        }
2402
2403        button.find('.loading').removeClass('hidden').show().end()
2404            .attr('disabled', 'disabled');
2405        WHMCS.http.jqClient.post(
2406            window.location.href,
2407            form.serialize(),
2408            function (data) {
2409                button.find('.loading').hide().end().removeAttr('disabled');
2410                form.find('.login-feedback').html('');
2411                if (data.error) {
2412                    form.find('.login-feedback').html(data.error);
2413                }
2414                if (data.redirect !== undefined && data.redirect.substr(0, 7) === 'window|') {
2415                    window.open(data.redirect.substr(7), '_blank');
2416                }
2417            },
2418            'json'
2419        );
2420    });
2421    jQuery('.btn-sidebar-form-submit').on('click', function(e) {
2422        e.preventDefault();
2423        jQuery(this).find('.loading').removeClass('hidden').show().end()
2424            .attr('disabled', 'disabled');
2425
2426        var form = jQuery(this).parents('form');
2427
2428        if (form.length == 0) {
2429            form = jQuery(this).find('form');
2430        }
2431
2432        if (form.length !== 0 && form.hasClass('disabled') === false) {
2433            form.submit();
2434        } else {
2435            jQuery(this).find('.loading').hide().end().removeAttr('disabled');
2436        }
2437    });
2438
2439    // Email verification close
2440    jQuery('.email-verification .btn.close').click(function(e) {
2441        e.preventDefault();
2442        WHMCS.http.jqClient.post('clientarea.php', 'action=dismiss-email-banner&token=' + csrfToken);
2443        jQuery('.email-verification').hide();
2444    });
2445
2446    // Back to top animated scroll
2447    jQuery('.back-to-top').click(function(e) {
2448        e.preventDefault();
2449        jQuery('body,html').animate({scrollTop: 0}, 500);
2450    });
2451
2452    // Prevent page scroll on language choose click
2453    jQuery('.choose-language').click(function(e) {
2454        e.preventDefault();
2455    });
2456
2457    // Activate copy to clipboard functionality
2458    jQuery('.copy-to-clipboard').click(WHMCS.ui.clipboard.copy);
2459
2460    // Password Generator
2461    jQuery('.generate-password').click(function(e) {
2462        jQuery('#frmGeneratePassword').submit();
2463        jQuery('#modalGeneratePassword')
2464            .data('targetfields', jQuery(this).data('targetfields'))
2465            .modal('show');
2466    });
2467    jQuery('#frmGeneratePassword').submit(function(e) {
2468        e.preventDefault();
2469        var length = parseInt(jQuery('#inputGeneratePasswordLength').val(), 10);
2470
2471        // Check length
2472        if (length < 8 || length > 64) {
2473            jQuery('#generatePasswordLengthError').removeClass('hidden').show();
2474            return;
2475        }
2476
2477        jQuery('#inputGeneratePasswordOutput').val(WHMCS.utils.generatePassword(length));
2478    });
2479    jQuery('#btnGeneratePasswordInsert')
2480        .click(WHMCS.ui.clipboard.copy)
2481        .click(function(e) {
2482            jQuery(this).closest('.modal').modal('hide');
2483            var targetFields = jQuery(this).closest('.modal').data('targetfields');
2484            targetFields = targetFields.split(',');
2485            for(var i = 0; i < targetFields.length; i++) {
2486                jQuery('#' + targetFields[i]).val(jQuery('#inputGeneratePasswordOutput').val())
2487                    .trigger('keyup');
2488            }
2489        });
2490
2491// Sub-Account Activation Toggle
2492    jQuery("#inputSubaccountActivate").click(function () {
2493        if (jQuery("#inputSubaccountActivate:checked").val() != null) {
2494            jQuery("#subacct-container").removeClass('hidden');
2495        } else {
2496            jQuery("#subacct-container").addClass('hidden');
2497        }
2498    });
2499
2500/**
2501 * Add domain renewal to shopping cart.
2502 *
2503 * @param {number} renewalID    The domain ID to be added
2504 * @param {domElement} selfThis The object triggering the add
2505 */
2506function addRenewalToCart(renewalID, selfThis) {
2507    jQuery("#domainRow" + renewalID).attr('disabled', 'disabled');
2508    jQuery("#domainRow" + renewalID).find("select,button").attr("disabled", "disabled");
2509    jQuery(selfThis).html('<span class="glyphicon glyphicon-shopping-cart"></span> Adding...');
2510    var renewalPeriod = jQuery("#renewalPeriod" + renewalID).val();
2511    WHMCS.http.jqClient.post(
2512        "clientarea.php",
2513        "addRenewalToCart=1&token=" + csrfToken + "&renewID="+ renewalID + "&period=" + renewalPeriod,
2514        function( data ) {
2515            jQuery("#cartItemCount").html(((jQuery("#cartItemCount").html() * 1) + 1));
2516            jQuery(selfThis).html('<span class="glyphicon glyphicon-shopping-cart"></span> Added');
2517            jQuery("#btnCheckout").fadeIn();
2518        }
2519    );
2520}
2521
2522    // Mass Domain Management Bulk Action Handling
2523    jQuery(".setBulkAction").click(function(event) {
2524        event.preventDefault();
2525        var id = jQuery(this).attr('id').replace('Link', ''),
2526            domainForm = jQuery('#domainForm');
2527
2528        if (id === 'renewDomains') {
2529            domainForm.attr('action', WHMCS.utils.getRouteUrl('/cart/domain/renew'));
2530        } else {
2531            if (jQuery('#' + id).length !== 0) {
2532                var action = domainForm.attr('action');
2533                domainForm.attr('action', action + '#' + id);
2534            }
2535            jQuery('#bulkaction').val(id);
2536        }
2537        domainForm.submit();
2538    });
2539
2540/**
2541 * Submit default whois info and disable custom fields.
2542 *
2543 * @param {string} regType The contact registration type
2544 */
2545function useDefaultWhois(regType) {
2546    jQuery("." + regType.substr(0, regType.length - 1) + "customwhois").attr("disabled", true);
2547    jQuery("." + regType.substr(0, regType.length - 1) + "defaultwhois").attr("disabled", false);
2548    jQuery('#' + regType.substr(0, regType.length - 1) + '1').attr("checked", "checked");
2549}
2550
2551/**
2552 * Submit the first form that exists within a given container.
2553 *
2554 * @param {string} containerId The ID name of the container
2555 */
2556 function autoSubmitFormByContainer(containerId) {
2557     if (typeof noAutoSubmit === "undefined" || noAutoSubmit === false) {
2558         jQuery("#" + containerId).find("form:first").submit();
2559     }
2560 }
2561
2562/**
2563 * Submit custom fields and disable default whois info.
2564 *
2565 * @param {string} regType The contact registration type
2566 */
2567function useCustomWhois(regType) {
2568    jQuery("." + regType.substr(0, regType.length - 1) + "customwhois").attr("disabled", false);
2569    jQuery("." + regType.substr(0, regType.length - 1) + "defaultwhois").attr("disabled", true);
2570    jQuery('#' + regType.substr(0, regType.length - 1) + '2').attr("checked", "checked");
2571}
2572
2573
2574if (typeof Waves !== 'undefined') {
2575  Waves.init();
2576  Waves.attach(".sidebar-holder .nav a", ["waves-light", "waves-round"]);
2577}
2578
2579/**
2580 * Smooth scroll to named element.
2581 */
2582function smoothScroll(element) {
2583    $('html, body').animate({
2584        scrollTop: $(element).offset().top
2585    }, 500);
2586}
2587
2588function irtpSubmit()
2589{
2590    allowSubmit = true;
2591    var optOut = 0,
2592        optOutCheckbox = jQuery('#modalIrtpOptOut'),
2593        optOutReason = jQuery('#modalReason'),
2594        formOptOut = jQuery('#irtpOptOut'),
2595        formOptOutReason = jQuery('#irtpOptOutReason');
2596
2597    if (optOutCheckbox.is(':checked')) {
2598        optOut = 1;
2599    }
2600    formOptOut.val(optOut);
2601    formOptOutReason.val(optOutReason.val());
2602    jQuery('#frmDomainContactModification').submit();
2603}
2604
2605/*!
2606 * WHMCS Ajax Driven Modal Framework
2607 *
2608 * @copyright Copyright (c) WHMCS Limited 2005-2019
2609 * @license http://www.whmcs.com/license/ WHMCS Eula
2610 */
2611var ajaxModalSubmitEvents = [];
2612jQuery(document).ready(function(){
2613    jQuery(document).on('click', '.open-modal', function(e) {
2614        e.preventDefault();
2615        var url = jQuery(this).attr('href'),
2616            modalSize = jQuery(this).data('modal-size'),
2617            modalClass = jQuery(this).data('modal-class'),
2618            modalTitle = jQuery(this).data('modal-title'),
2619            submitId = jQuery(this).data('btn-submit-id'),
2620            submitLabel = jQuery(this).data('btn-submit-label'),
2621            hideClose = jQuery(this).data('btn-close-hide'),
2622            disabled = jQuery(this).attr('disabled'),
2623            successDataTable = jQuery(this).data('datatable-reload-success');
2624
2625        if (!disabled) {
2626            openModal(url, '', modalTitle, modalSize, modalClass, submitLabel, submitId, hideClose, successDataTable);
2627        }
2628    });
2629
2630    // define modal close reset action
2631    jQuery('#modalAjax').on('hidden.bs.modal', function (e) {
2632        if (jQuery(this).hasClass('modal-feature-highlights')) {
2633            var dismissForVersion = jQuery('#cbFeatureHighlightsDismissForVersion').is(':checked');
2634            WHMCS.http.jqClient.post(
2635                'whatsnew.php',
2636                {
2637                    dismiss: "1",
2638                    until_next_update: dismissForVersion ? '1' : '0',
2639                    token: csrfToken
2640                }
2641            );
2642        }
2643
2644        jQuery('#modalAjax').find('.modal-body').empty();
2645        jQuery('#modalAjax').children('div.modal-dialog').removeClass('modal-lg');
2646        jQuery('#modalAjax').removeClass().addClass('modal whmcs-modal fade');
2647        jQuery('#modalAjax .modal-title').html('Title');
2648        jQuery('#modalAjax .modal-submit').html('Submit')
2649            .removeClass()
2650            .addClass('btn btn-primary modal-submit')
2651            .removeAttr('id')
2652            .removeAttr('disabled');
2653        jQuery('#modalAjax .loader').show();
2654    });
2655});
2656
2657function openModal(url, postData, modalTitle, modalSize, modalClass, submitLabel, submitId, hideClose, successDataTable) {
2658    //set the text of the modal title
2659    jQuery('#modalAjax .modal-title').html(modalTitle);
2660
2661    // set the modal size via a class attribute
2662    if (modalSize) {
2663        jQuery('#modalAjax').children('div[class="modal-dialog"]').addClass(modalSize);
2664    }
2665    // set the modal class
2666    if (modalClass) {
2667        jQuery('#modalAjax').addClass(modalClass);
2668    }
2669
2670    // set the modal class
2671    if (modalClass) {
2672        jQuery('#modalAjax').addClass(modalClass);
2673    }
2674
2675    // set the text of the submit button
2676    if(!submitLabel){
2677       jQuery('#modalAjax .modal-submit').hide();
2678    } else {
2679        jQuery('#modalAjax .modal-submit').show().html(submitLabel);
2680        // set the button id so we can target the click function of it.
2681        if (submitId) {
2682            jQuery('#modalAjax .modal-submit').attr('id', submitId);
2683        }
2684    }
2685
2686    if (hideClose) {
2687        jQuery('#modalAjaxClose').hide();
2688    }
2689
2690    jQuery('#modalAjax .modal-body').html('');
2691
2692    jQuery('#modalSkip').hide();
2693    jQuery('#modalAjax .modal-submit').prop('disabled', true);
2694
2695    // show modal
2696    jQuery('#modalAjax').modal('show');
2697
2698    // fetch modal content
2699    WHMCS.http.jqClient.post(url, postData, function(data) {
2700        updateAjaxModal(data);
2701    }, 'json').fail(function() {
2702        jQuery('#modalAjax .modal-body').html('An error occurred while communicating with the server. Please try again.');
2703        jQuery('#modalAjax .loader').fadeOut();
2704    }).always(function () {
2705        if (successDataTable) {
2706            var modalForm = jQuery('#modalAjax').find('form');
2707            modalForm.data('successDataTable', successDataTable);
2708        }
2709    });
2710
2711    //define modal submit button click
2712    if (submitId) {
2713        /**
2714         * Reloading ajax modal multiple times on the same page can add
2715         * multiple "on" click events which submits the same form over
2716         * and over.
2717         * Remove the on click event with "off" to avoid multiple growl
2718         * and save events being run.
2719         *
2720         * @see http://api.jquery.com/off/
2721         */
2722        var submitButton = jQuery('#' + submitId);
2723        submitButton.off('click');
2724        submitButton.on('click', submitIdAjaxModalClickEvent);
2725    }
2726}
2727
2728function submitIdAjaxModalClickEvent ()
2729{
2730    if (jQuery(this).hasClass('disabled')) {
2731        return;
2732    }
2733    var canContinue = true;
2734    if (ajaxModalSubmitEvents.length) {
2735        jQuery.each(ajaxModalSubmitEvents, function (index, value) {
2736            var fn = window[value];
2737            if (canContinue && typeof fn === 'function') {
2738                canContinue = fn();
2739            }
2740        });
2741    }
2742    if (!canContinue) {
2743        return;
2744    }
2745    var modalForm = jQuery('#modalAjax').find('form');
2746    var modalBody = jQuery('#modalAjax .modal-body');
2747    var modalErrorContainer = jQuery(modalBody).find('.admin-modal-error');
2748
2749    jQuery('#modalAjax .loader').show();
2750    jQuery(modalErrorContainer).slideUp();
2751
2752    var modalPost = WHMCS.http.jqClient.post(
2753        modalForm.attr('action'),
2754        modalForm.serialize(),
2755        function(data) {
2756            if (modalForm.data('successDataTable')) {
2757                data.successDataTable = modalForm.data('successDataTable');
2758            }
2759            updateAjaxModal(data);
2760        },
2761        'json'
2762    ).fail(function(xhr) {
2763        var data = xhr.responseJSON;
2764        var genericErrorMsg = 'An error occurred while communicating with the server. Please try again.';
2765        if (data && data.data) {
2766            data = data.data;
2767            if (data.errorMsg) {
2768                if (modalErrorContainer.length > 0) {
2769                    jQuery(modalErrorContainer)
2770                        .html(data.errorMsg)
2771                        .slideDown();
2772                } else {
2773                    jQuery.growl.warning({title: data.errorMsgTitle, message: data.errorMsg});
2774                }
2775            } else if (data.data.body) {
2776                jQuery(modalBody).html(data.body);
2777            } else {
2778                jQuery(modalBody).html(genericErrorMsg);
2779            }
2780        } else {
2781            jQuery(modalBody).html(genericErrorMsg);
2782        }
2783        jQuery('#modalAjax .loader').fadeOut();
2784    });
2785}
2786
2787function updateAjaxModal(data) {
2788    if (data.reloadPage) {
2789        if (typeof data.reloadPage === 'string') {
2790            window.location = data.reloadPage;
2791        } else {
2792            window.location.reload();
2793        }
2794        return;
2795    }
2796    if (data.successDataTable) {
2797        WHMCS.ui.dataTable.getTableById(data.successDataTable, undefined).ajax.reload();
2798    }
2799    if (data.redirect) {
2800        window.location = data.redirect;
2801        dialogClose();
2802    }
2803    if (data.successWindow && typeof window[data.successWindow] === "function") {
2804        window[data.successWindow]();
2805    }
2806    if (data.dismiss) {
2807        dialogClose();
2808    }
2809    if (data.successMsg) {
2810        jQuery.growl.notice({ title: data.successMsgTitle, message: data.successMsg });
2811    }
2812    if (data.errorMsg) {
2813        var inModalErrorContainer = jQuery('#modalAjax .modal-body .admin-modal-error');
2814
2815        if (inModalErrorContainer.length > 0 && !data.dismiss) {
2816            jQuery(inModalErrorContainer)
2817                .html(data.errorMsg)
2818                .slideDown();
2819        } else {
2820            jQuery.growl.warning({title: data.errorMsgTitle, message: data.errorMsg});
2821        }
2822    }
2823    if (data.title) {
2824        jQuery('#modalAjax .modal-title').html(data.title);
2825    }
2826    if (data.body) {
2827        jQuery('#modalAjax .modal-body').html(data.body);
2828    } else {
2829        if (data.url) {
2830            WHMCS.http.jqClient.post(data.url, '', function(data2) {
2831                jQuery('#modalAjax').find('.modal-body').html(data2.body);
2832            }, 'json').fail(function() {
2833                jQuery('#modalAjax').find('.modal-body').html('An error occurred while communicating with the server. Please try again.');
2834                jQuery('#modalAjax').find('.loader').fadeOut();
2835            });
2836        }
2837    }
2838    if (data.submitlabel) {
2839        jQuery('#modalAjax .modal-submit').html(data.submitlabel).show();
2840        if (data.submitId) {
2841            jQuery('#modalAjax').find('.modal-submit').attr('id', data.submitId);
2842        }
2843    }
2844
2845    if (data.submitId) {
2846        /**
2847         * Reloading ajax modal multiple times on the same page can add
2848         * multiple "on" click events which submits the same form over
2849         * and over.
2850         * Remove the on click event with "off" to avoid multiple growl
2851         * and save events being run.
2852         *
2853         * @see http://api.jquery.com/off/
2854         */
2855        var submitButton = jQuery('#' + data.submitId);
2856        submitButton.off('click');
2857        submitButton.on('click', function() {
2858            var modalForm = jQuery('#modalAjax').find('form');
2859            jQuery('#modalAjax .loader').show();
2860            var modalPost = WHMCS.http.jqClient.post(modalForm.attr('action'), modalForm.serialize(),
2861                function(data) {
2862                    updateAjaxModal(data);
2863                }, 'json').fail(function() {
2864                    jQuery('#modalAjax .modal-body').html('An error occurred while communicating with the server. Please try again.');
2865                    jQuery('#modalAjax .loader').fadeOut();
2866                }
2867            );
2868        })
2869    }
2870
2871    jQuery('#modalAjax .loader').fadeOut();
2872    jQuery('#modalAjax .modal-submit').removeProp('disabled');
2873}
2874
2875// backwards compat for older dialog implementations
2876
2877function dialogSubmit() {
2878    jQuery('#modalAjax .modal-submit').prop("disabled", true);
2879    jQuery('#modalAjax .loader').show();
2880    var postUrl = jQuery('#modalAjax').find('form').attr('action');
2881    WHMCS.http.jqClient.post(postUrl, jQuery('#modalAjax').find('form').serialize(),
2882        function(data) {
2883            updateAjaxModal(data);
2884        }, 'json').fail(function() {
2885            jQuery('#modalAjax .modal-body').html('An error occurred while communicating with the server. Please try again.');
2886            jQuery('#modalAjax .loader').fadeOut();
2887        });
2888}
2889
2890function dialogClose() {
2891    jQuery('#modalAjax').modal('hide');
2892}
2893
2894function addAjaxModalSubmitEvents(functionName) {
2895    if (functionName) {
2896        ajaxModalSubmitEvents.push(functionName);
2897    }
2898}
2899
2900function removeAjaxModalSubmitEvents(functionName) {
2901    if (functionName) {
2902        var index = ajaxModalSubmitEvents.indexOf(functionName);
2903        if (index >= 0) {
2904            ajaxModalSubmitEvents.splice(index, 1);
2905        }
2906    }
2907}
2908
2909
2910jQuery(document).ready(function() {
2911
2912    /**
2913     * Code will loop through each element that has the class markdown-editor and
2914     * enable the Markdown editor.
2915     */
2916    var count = 0,
2917        editorName = 'clientMDE',
2918        counter = 0;
2919    jQuery(".markdown-editor").each(function( index ) {
2920        count++;
2921        var autoSaveName = jQuery(this).data('auto-save-name'),
2922            footerId = jQuery(this).attr('id') + '-footer';
2923        if (typeof autoSaveName == "undefined") {
2924            autoSaveName = 'client_area';
2925        }
2926        window[editorName + count.toString()] = jQuery(this).markdown(
2927        {
2928            footer: '<div id="' + footerId + '" class="markdown-editor-status"></div>',
2929            autofocus: false,
2930            savable: false,
2931            resize: 'vertical',
2932            iconlibrary: 'glyph',
2933            language: locale,
2934            onShow: function(e){
2935                var content = '',
2936                    save_enabled = false;
2937                if(typeof(Storage) !== "undefined") {
2938                    // Code for localStorage/sessionStorage.
2939                    content = localStorage.getItem(autoSaveName);
2940                    save_enabled = true;
2941                    if (content && typeof(content) !== "undefined") {
2942                        e.setContent(content);
2943                    }
2944                }
2945                jQuery("#" + footerId).html(parseMdeFooter(content, save_enabled, saved));
2946            },
2947            onChange: function(e){
2948                var content = e.getContent(),
2949                    save_enabled = false;
2950                if(typeof(Storage) !== "undefined") {
2951                    counter = 3;
2952                    save_enabled = true;
2953                    localStorage.setItem(autoSaveName, content);
2954                    doCountdown();
2955                }
2956                jQuery("#" + footerId).html(parseMdeFooter(content, save_enabled));
2957            },
2958            onPreview: function(e){
2959                var originalContent = e.getContent(),
2960                    parsedContent;
2961
2962                jQuery.ajax({
2963                    url: 'clientarea.php',
2964                    async: false,
2965                    data: {token: csrfToken, action: 'parseMarkdown', content: originalContent},
2966                    dataType: 'json',
2967                    success: function (data) {
2968                        parsedContent = data;
2969                    }
2970                });
2971
2972                return parsedContent.body ? parsedContent.body : '';
2973            },
2974            additionalButtons: [
2975                [{
2976                    name: "groupCustom",
2977                    data: [{
2978                        name: "cmdHelp",
2979                        title: "Help",
2980                        hotkey: "Ctrl+F1",
2981                        btnClass: "btn open-modal",
2982                        icon: {
2983                            glyph: 'fas fa-question-circle',
2984                            fa: 'fa fa-question-circle',
2985                            'fa-3': 'icon-question-sign'
2986                        },
2987                        callback: function(e) {
2988                            e.$editor.removeClass("md-fullscreen-mode");
2989                        }
2990                    }]
2991                }]
2992            ],
2993            hiddenButtons: [
2994                'cmdImage'
2995            ]
2996        });
2997        jQuery('button[data-handler="bootstrap-markdown-cmdHelp"]')
2998            .attr('data-modal-title', markdownGuide)
2999            .attr('href', 'submitticket.php?action=markdown');
3000
3001        jQuery(this).closest("form").bind({
3002            submit: function() {
3003                if(typeof(Storage) !== "undefined") {
3004                    localStorage.removeItem(autoSaveName);
3005                }
3006            }
3007        });
3008    });
3009
3010    // Email verification
3011    jQuery('#btnResendVerificationEmail').click(function() {
3012        WHMCS.http.jqClient.post('clientarea.php',
3013            {
3014                'token': csrfToken,
3015                'action': 'resendVerificationEmail'
3016            }).done(function(data) {
3017                jQuery('#btnResendVerificationEmail').html('Email Sent').prop('disabled', true);
3018            });
3019    });
3020
3021    /**
3022     * Parse the content to populate the markdown editor footer.
3023     *
3024     * @param {string} content
3025     * @param {bool} auto_save
3026     * @param {string} [saveText]
3027     * @returns {string}
3028     */
3029    function parseMdeFooter(content, auto_save, saveText)
3030    {
3031        saveText = saveText || saving;
3032        var pattern = /[a-zA-Z0-9_\u0392-\u03c9]+|[\u4E00-\u9FFF\u3400-\u4dbf\uf900-\ufaff\u3040-\u309f\uac00-\ud7af]+/g,
3033            m = [],
3034            word_count = 0,
3035            line_count = 0;
3036        if (content) {
3037            m = content.match(pattern);
3038            line_count = content.split(/\\r\\n|\\r|\\n/).length;
3039        }
3040        for(var i = 0; i < m.length; i++) {
3041            if(m[i].charCodeAt(0) >= 0x4E00) {
3042                word_count += m[i].length;
3043            } else {
3044                word_count += 1;
3045            }
3046        }
3047        return '<div class="small-font">lines: ' + line_count
3048            + '&nbsp;&nbsp;&nbsp;words: ' + word_count + ''
3049            + (auto_save ? '&nbsp;&nbsp;&nbsp;<span class="markdown-save">' + saveText + '</span>' : '')
3050            + '</div>';
3051    }
3052
3053    /**
3054     * Countdown the save timeout. When zero, the span will update to show saved.
3055     */
3056    function doCountdown()
3057    {
3058        if (counter >= 0) {
3059            if (counter == 0) {
3060                jQuery("span.markdown-save").html(saved);
3061            }
3062            counter--;
3063            setTimeout(doCountdown, 1000);
3064        }
3065    }
3066
3067    // Two-Factor Activation Process Modal Handler.
3068    var frmTwoFactorActivation = jQuery('input[name=2fasetup]').parent('form');
3069    frmTwoFactorActivation.submit(function(e) {
3070        e.preventDefault();
3071        openModal(frmTwoFactorActivation.attr('action'), frmTwoFactorActivation.seri
3071alize(), 'Loading...');
3072    });
3073
3074    jQuery.fn.showInputError = function () {
3075        this.parents('.form-group').addClass('has-error').find('.field-error-msg').show();
3076        return this;
3077    };
3078
3079    jQuery('#frmPayment').on('submit', function() {
3080        var btn = jQuery('#btnSubmit');
3081            btn.find('span').toggleClass('hidden');
3082            btn.prop('disabled', true).addClass('disabled');
3083    });
3084
3085    // SSL Manage Action Button.
3086jQuery('.btn-resend-approver-email').click(function () {
3087        WHMCS.http.jqClient.post(
3088        jQuery(this).data('url'),
3089        {
3090            addonId: jQuery(this).data('addonid'),
3091            serviceId: jQuery(this).data('serviceid'),
3092        },
3093        function(data) {
3094            if (data.success == true) {
3095                jQuery('.alert-table-ssl-manage').addClass('alert-success').text('Approver Email Resent').removeClass('hidden');
3096            } else {
3097                jQuery('.alert-table-ssl-manage').addClass('alert-danger').text('Error: ' + data.message).removeClass('hidden');
3098            }
3099        }
3100    );
3101});
3102
3103    // Domain Pricing Table Filters
3104    jQuery(".tld-filters a").click(function(e) {
3105        e.preventDefault();
3106
3107        if (jQuery(this).hasClass('label-success')) {
3108            jQuery(this).removeClass('label-success');
3109        } else {
3110            jQuery(this).addClass('label-success');
3111        }
3112
3113        jQuery('.tld-row').removeClass('filtered-row');
3114        jQuery('.tld-filters a.label-success').each(function(index) {
3115            var filterValue = jQuery(this).data('category');
3116            jQuery('.tld-row[data-category*="' + filterValue + '"]').addClass('filtered-row');
3117        });
3118        jQuery(".filtered-row:even").removeClass('highlighted');
3119        jQuery(".filtered-row:odd").addClass('highlighted');
3120        jQuery('.tld-row:not(".filtered-row")').fadeOut('', function() {
3121            if (jQuery('.filtered-row').size() === 0) {
3122                jQuery('.tld-row.no-tlds').show();
3123            } else {
3124                jQuery('.tld-row.no-tlds').hide();
3125            }
3126        });
3127        jQuery('.tld-row.filtered-row').fadeIn();
3128    });
3129    jQuery(".filtered-row:even").removeClass('highlighted');
3130    jQuery(".filtered-row:odd").addClass('highlighted');
3131
3132// DataTable data-driven auto object registration
3133WHMCS.ui.dataTable.register();
3134
3135    // Bootstrap Confirmation popup auto object registration
3136    WHMCS.ui.confirmation.register();
3137
3138    WHMCS.ui.jsonForm.initAll();
3139
3140jQuery('#frmReply').submit(function(e) {
3141    jQuery('#frmReply').find('input[type="submit"]').addClass('disabled').prop('disabled', true);
3142  });
3143
3144jQuery('#frmDomainContactModification').on('submit', function(){
3145  if (!allowSubmit) {
3146      var changed = false;
3147      jQuery('.irtp-field').each(function() {
3148          var value = jQuery(this).val(),
3149              originalValue = jQuery(this).data('original-value');
3150          if (value !== originalValue) {
3151              changed = true;
3152          }
3153      });
3154      if (changed) {
3155          jQuery('#modalIRTPConfirmation').modal('show');
3156          return false;
3157      }
3158  }
3159  return true;
3160});
3161
3162jQuery('.ssl-state.ssl-sync').each(function () {
3163    var self = jQuery(this);
3164    WHMCS.http.jqClient.post(
3165        WHMCS.utils.getRouteUrl('/domain/ssl-check'),
3166        {
3167            'type': self.parent('td').data('type'),
3168            'domain': self.parent('td').data('domain'),
3169            'token': csrfToken
3170        },
3171        function (data) {
3172            if (data.invalid) {
3173                self.hide();
3174            } else {
3175                self.replaceWith(
3176                    '<img src="' + data.image + '" data-toggle="tooltip" title="' + data.tooltip + '" class="' + data.class + '">'
3177                );
3178            }
3179        }
3180    );
3181});
3182jQuery(document).on('click', '.ssl-state.ssl-inactive', function(e) {
3183    e.preventDefault();
3184    window.location.href = WHMCS.utils.getRouteUrl('/ssl-purchase');
3185});
3186
3187WHMCS.recaptcha.register();
3188
3189var dynamicRecaptchaContainer = jQuery('#divDynamicRecaptcha');
3190var homepageHasRecaptcha = jQuery(dynamicRecaptchaContainer).length > 0;
3191var homepageHasInvisibleRecaptcha = homepageHasRecaptcha && jQuery(dynamicRecaptchaContainer).data('size') === 'invisible';
3192
3193if (homepageHasRecaptcha && !homepageHasInvisibleRecaptcha) {
3194    jQuery('section#home-banner').addClass('with-recaptcha');
3195}
3196
3197if (jQuery('.domainchecker-homepage-captcha').length && !homepageHasInvisibleRecaptcha) {
3198    // invisible reCaptcha doesn't play well with onsubmit() handlers on all submissions following a prevented one
3199
3200    jQuery('#frmDomainHomepage').submit(function (e) {
3201        var frmDomain = jQuery('#frmDomainHomepage'),
3202            inputDomain = jQuery(frmDomain).find('input[name="domain"]'),
3203            reCaptchaContainer = jQuery('#divDynamicRecaptcha'),
3204            reCaptcha = jQuery('#g-recaptcha-response'),
3205            captcha = jQuery('#inputCaptcha');
3206
3207        if (reCaptcha.length && !reCaptcha.val()) {
3208            reCaptchaContainer.tooltip('show');
3209
3210            e.preventDefault();
3211            return;
3212        }
3213
3214        if (captcha.length && !captcha.val()) {
3215            captcha.tooltip('show');
3216
3217            e.preventDefault();
3218            return;
3219        }
3220    });
3221}
3222    $('.icheck-button').iCheck({
3223        inheritID: true,
3224        checkboxClass: 'icheckbox_square-blue',
3225        radioClass: 'iradio_square-blue',
3226        increaseArea: '20%'
3227    });
3228
3229    jQuery('#inputNoStore').on('switchChange.bootstrapSwitch', function(event, state) {
3230        var descContainer = jQuery('#inputDescription');
3231        if (!state) {
3232            descContainer.prop('disabled', true).addClass('disabled');
3233        }
3234        if (state) {
3235            descContainer.removeClass('disabled').prop('disabled', false);
3236        }
3237    });
3238});
3239
3240/**
3241 * WHMCS Telephone Country Code Dropdown
3242 *
3243 * Using https://github.com/jackocnr/intl-tel-input
3244 *
3245 * @copyright Copyright (c) WHMCS Limited 2005-2017
3246 * @license http://www.whmcs.com/license/ WHMCS Eula
3247 */
3248//
3249// jQuery(document).ready(function() {
3250//     if (jQuery('body').data('phone-cc-input')) {
3251//         var phoneInput = jQuery('input[name^="phone"], input[name$="phone"]').not('input[type="hidden"]');
3252//         if (phoneInput.length) {
3253//             var countryInput = jQuery('[name^="country"], [name$="country"]'),
3254//                 initialCountry = 'us';
3255//             if (countryInput.length) {
3256//                 initialCountry = countryInput.val().toLowerCase();
3257//                 if (initialCountry === 'um') {
3258//                     initialCountry = 'us';
3259//                 }
3260//             }
3261//
3262//             phoneInput.each(function(){
3263//                 var thisInput = jQuery(this),
3264//                     inputName = thisInput.attr('name');
3265//                 jQuery(this).before(
3266//                     '<input id="populatedCountryCode' + inputName + '" type="hidden" name="country-calling-code-' + inputName + '" value="" />'
3267//                 );
3268//                 thisInput.intlTelInput({
3269//                     preferredCountries: [initialCountry, "us", "gb"].filter(function(value, index, self) {
3270//                         return self.indexOf(value) === index;
3271//                     }),
3272//                     initialCountry: initialCountry,
3273//                     autoPlaceholder: 'polite', //always show the helper placeholder
3274//                     separateDialCode: true
3275//                 });
3276//
3277//                 thisInput.on('countrychange', function (e, countryData) {
3278//                     jQuery('#populatedCountryCode' + inputName).val(countryData.dialCode);
3279//                     if (jQuery(this).val() === '+' + countryData.dialCode) {
3280//                         jQuery(this).val('');
3281//                     }
3282//                 });
3283//                 thisInput.on('blur keydown', function (e) {
3284//                     if (e.type === 'blur' || (e.type === 'keydown' && e.keyCode === 13)) {
3285//                         var number = jQuery(this).intlTelInput("getNumber"),
3286//                             countryData = jQuery(this).intlTelInput("getSelectedCountryData"),
3287//                             countryPrefix = '+' + countryData.dialCode;
3288//
3289//                         if (number.indexOf(countryPrefix) === 0 && (number.match(/\+/g) || []).length > 1) {
3290//                             number = number.substr(countryPrefix.length);
3291//                         }
3292//                         jQuery(this).intlTelInput("setNumber", number);
3293//                     }
3294//                 });
3295//                 jQuery('#populatedCountryCode' + inputName).val(thisInput.intlTelInput('getSelectedCountryData').dialCode);
3296//
3297//                 countryInput.on('change', function() {
3298//                     if (thisInput.val() === '') {
3299//                         var country = jQuery(this).val().toLowerCase();
3300//                         if (country === 'um') {
3301//                             country = 'us';
3302//                         }
3303//                         phoneInput.intlTelInput('setCountry', country);
3304//                     }
3305//                 });
3306//                                 // this must be .attr (not .data) in order for it to be found by [data-initial-value] selector
3307//                                 thisInput.attr('data-initial-value', $(thisInput).val());
3308//
3309//                                 thisInput.parents('form').find('input[type=reset]').each(function() {
3310//                                     var resetButton = this;
3311//                                     var form = $(resetButton).parents('form');
3312//
3313//                                     if (!$(resetButton).data('phone-handler')) {
3314//                                         $(resetButton).data('phone-handler', true);
3315//
3316//                                         $(resetButton).click(function(e) {
3317//                                             e.stopPropagation();
3318//
3319//                                             $(form).trigger('reset');
3320//
3321//                                             $(form).find('input[data-initial-value]').each(function() {
3322//                                                 var inputToReset = this;
3323//
3324//                                                 $(inputToReset).val(
3325//                                                     $(inputToReset).attr('data-initial-value')
3326//                                                 );
3327//                                             });
3328//
3329//                                             return false;
3330//                                         });
3331//                                     }
3332//                                 });
3333//             });
3334//
3335//             /**
3336//              * In places where a form icon is present, hide it.
3337//              * Where the input has a class of field, remove that and add form-control in place.
3338//              */
3339//             phoneInput.parents('div.form-group').find('.field-icon').addClass('hidden').end();
3340//             phoneInput.removeClass('field').addClass('form-control');
3341//         }
3342//
3343//         var registrarPhoneInput = jQuery('input[name$="][Phone Number]"], input[name$="][Phone]"]').not('input[type="hidden"]');
3344//         if (registrarPhoneInput.length) {
3345//             jQuery.each(registrarPhoneInput, function(index, input) {
3346//                 var thisInput = jQuery(this),
3347//                     inputName = thisInput.attr('name');
3348//                 inputName = inputName.replace('contactdetails[', '').replace('][Phone Number]', '').replace('][Phone]', '');
3349//
3350//                 var countryInput = jQuery('[name$="' + inputName + '][Country]"]'),
3351//                     initialCountry = countryInput.val().toLowerCase();
3352//                 if (initialCountry === 'um') {
3353//                     initialCountry = 'us';
3354//                 }
3355//
3356//                 thisInput.before('<input id="populated' + inputName + 'CountryCode" type="hidden" name="contactdetails[' + inputName + '][Phone Country Code]" value="" />');
3357//                 thisInput.intlTelInput({
3358//                     preferredCountries: [initialCountry, "us", "gb"].filter(function(value, index, self) {
3359//                         return self.indexOf(value) === index;
3360//                     }),
3361//                     initialCountry: initialCountry,
3362//                     autoPlaceholder: 'polite', //always show the helper placeholder
3363//                     separateDialCode: true
3364//                 });
3365//
3366//                 thisInput.on('countrychange', function (e, countryData) {
3367//                     jQuery('#populated' + inputName + 'CountryCode').val(countryData.dialCode);
3368//                     if (jQuery(this).val() === '+' + countryData.dialCode) {
3369//                         jQuery(this).val('');
3370//                     }
3371//                 });
3372//                 thisInput.on('blur keydown', function (e) {
3373//                     if (e.type === 'blur' || (e.type === 'keydown' && e.keyCode === 13)) {
3374//                         var number = jQuery(this).intlTelInput("getNumber"),
3375//                             countryData = jQuery(this).intlTelInput("getSelectedCountryData"),
3376//                             countryPrefix = '+' + countryData.dialCode;
3377//
3378//                         if (number.indexOf(countryPrefix) === 0 && (number.match(/\+/g) || []).length > 1) {
3379//                             number = number.substr(countryPrefix.length);
3380//                         }
3381//                         jQuery(this).intlTelInput("setNumber", number);
3382//                     }
3383//                 });
3384//                 jQuery('#populated' + inputName + 'CountryCode').val(thisInput.intlTelInput('getSelectedCountryData').dialCode);
3385//
3386//                 countryInput.on('blur', function() {
3387//                     if (thisInput.val() === '') {
3388//                         var country = jQuery(this).val().toLowerCase();
3389//                         if (country === 'um') {
3390//                             country = 'us';
3391//                         }
3392//                         thisInput.intlTelInput('setCountry', country);
3393//                     }
3394//                 });
3395//
3396//             });
3397//         }
3398//     }
3399// });

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.