1/**
2 * Javascript functions utilised by the client area templates.
3 *
4 * @file WHMCS Six Theme Javascript Library
5 * @copyright Copyright 2015 WHMCS Limited
6 */
7
8jQuery(document).ready(function() {
9
10 if (jQuery('#lightbox').length === 0) {
11 lightbox.init();
12 }
13
14 // Language chooser popover
15 jQuery('#languageChooser').popover({
16 container: 'body',
17 placement: 'bottom',
18 template: '<div class="popover language-popover" role="tooltip"><div class="arrow"></div><div class="popover-content"></div></div>',
19 html: true,
20 content: function() {
21 return jQuery("#languageChooserContent").html();
22 },
23 });
24
25 // Login or register popover
26 jQuery('#loginOrRegister').popover({
27 container: 'body',
28 placement: 'bottom',
29 template: '<div class="popover login-popover" role="tooltip"><div class="arrow"></div><div class="popover-content"></div></div>',
30 html: true,
31 content: function() {
32 return jQuery("#loginOrRegisterContent").html();
33 },
34 });
35
36 // Account notifications popover
37 jQuery("#accountNotifications").popover({
38 container: 'body',
39 placement: 'bottom',
40 template: '<div class="popover popover-user-notifications" role="tooltip"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>',
41 html: true,
42 content: function() {
43 return jQuery("#accountNotificationsContent").html();
44 },
45 });
46
47 jQuery('.panel-sidebar .truncate').each(function () {
48 jQuery(this).attr('title', jQuery(this).text())
49 .attr('data-toggle', 'tooltip')
50 .attr('data-placement', 'bottom');
51 });
52
53 // Default catch for all other popovers
54 jQuery('[data-toggle="popover"]').popover({
55 html: true
56 });
57
58 // Enable tooltips
59 // Attach function to body so tooltips inserted by ajax will load
60 jQuery(function(jQuery){
61 jQuery('body').tooltip({
62 selector: '[data-toggle="tooltip"]'
63 });
64 });
65
66 // Logic to dismiss popovers on click outside
67 jQuery('body').on('click', function (e) {
68 jQuery('[data-toggle="popover"]').each(function () {
69 if (!jQuery(this).is(e.target) && jQuery(this).has(e.target).length === 0 && jQuery('.popover').has(e.target).length === 0) {
70 jQuery(this).popover('hide');
71 }
72 });
73 });
74
75 // Sidebar active class toggle
76 jQuery(".list-group-tab-nav a").click(function() {
77 if (jQuery(this).hasClass('disabled')) {
78 return false;
79 }
80 jQuery(".list-group-tab-nav a").removeClass('active');
81 jQuery(this).addClass('active');
82 var urlFragment = this.href.split('#')[1];
83 if (urlFragment) {
84 // set the fragment in the URL bar for bookmarking and such.
85 window.location.hash = '#' + urlFragment;
86 }
87 });
88
89 // Sidebar minimise/maximise
90 jQuery('.panel-minimise').click(function(e) {
91 e.preventDefault();
92 if (jQuery(this).hasClass('minimised')) {
93 jQuery(this).parents('.panel').find('.panel-body, .list-group').slideDown();
94 jQuery(this).removeClass('minimised');
95 } else {
96 jQuery(this).parents('.panel').find('.panel-body, .list-group').slideUp();
97 jQuery(this).addClass('minimised');
98 }
99 });
100
101 // Minimise sidebar panels by default on small devices
102 if (jQuery('.container').width() <= 720) {
103 jQuery('.panel-sidebar').find('.panel-body, .list-group').hide();
104 jQuery('.panel-sidebar').find('.panel-minimise').addClass('minimised');
105 }
106
107 // Internal page tab selection handling via location hash
108 if (jQuery(location).attr('hash').substr(1) != "") {
109 var activeTab = jQuery(location).attr('hash');
110 jQuery(".tab-pane").removeClass('active');
111 jQuery(activeTab).removeClass('fade').addClass('active');
112 jQuery(".list-group-tab-nav a").removeClass('active');
113 jQuery('a[href="' + activeTab + '"]').addClass('active');
114 setTimeout(function() {
115 // Browsers automatically scroll on page load with a fragment.
116 // This scrolls back to the top right after page complete, but
117 // just before render (no perceptible scroll).
118 window.scrollTo(0, 0);
119 }, 1);
120 }
121
122 // Enable Switches for Checkboxes
123 if (jQuery.prototype.bootstrapSwitch) {
124 jQuery(".toggle-switch-success").bootstrapSwitch({
125 onColor: 'success'
126 });
127 }
128
129 // Collapsable Panels
130 jQuery(".panel-collapsable .panel-heading").click(function(e) {
131 var $this = jQuery(this);
132 if (!$this.parents('.panel').hasClass('panel-collapsed')) {
133 $this.parents('.panel').addClass('panel-collapsed').find('.panel-body').slideUp();
134 $this.find('.collapse-icon i').removeClass('fa-minus').addClass('fa-plus');
135 } else {
136 $this.parents('.panel').removeClass('panel-collapsed').find('.panel-body').slideDown();
137 $this.find('.collapse-icon i').removeClass('fa-plus').addClass('fa-minus');
138 }
139 });
140
141 // Two-Factor Authentication Auto Focus Rules
142 if (("#frmLogin").length > 0) {
143 jQuery("#frmLogin input:text:visible:first").focus();
144 }
145 if (("#twofaactivation").length > 0) {
146 jQuery("#twofaactivation input:text:visible:first,#twofaactivation input:password:visible:first").focus();
147 }
148
149 // Sub-Account Activation Toggle
150 jQuery("#inputSubaccountActivate").click(function () {
151 if (jQuery("#inputSubaccountActivate:checked").val() != null) {
152 jQuery("#subacct-container").removeClass('hidden');
153 } else {
154 jQuery("#subacct-container").addClass('hidden');
155 }
156 });
157
158 // Mass Domain Management Bulk Action Handling
159 jQuery(".setBulkAction").click(function(event) {
160 event.preventDefault();
161 var id = jQuery(this).attr('id').replace('Link', ''),
162 domainForm = jQuery('#domainForm');
163
164 if (id === 'renewDomains') {
165 domainForm.attr('action', WHMCS.utils.getRouteUrl('/cart/domain/renew'));
166 } else {
167 if (jQuery('#' + id).length !== 0) {
168 var action = domainForm.attr('action');
169 domainForm.attr('action', action + '#' + id);
170 }
171 jQuery('#bulkaction').val(id);
172 }
173 domainForm.submit();
174 });
175
176 // Stop events on objects with this class from bubbling up the dom
177 jQuery('.stopEventBubble').click( function(event) {
178 event.stopPropagation();
179 });
180
181 // Tab Control Link handling for tab switching via regular links
182 jQuery('.tabControlLink').on(
183 'click',
184 function(event) {
185 event.preventDefault();
186 var id = jQuery(this).attr('href');
187 jQuery("a[href='/"+id+"']").click();
188 }
189 );
190
191 jQuery(document).on('click', '.delete-cc-email', function() {
192 var self = jQuery(this),
193 email = self.data('email'),
194 feedback = jQuery('#divCcEmailFeedback');
195
196 if (feedback.is(':visible')) {
197 feedback.slideUp('fast');
198 }
199
200 WHMCS.http.jqClient.jsonPost({
201 url: window.location.href,
202 data: {
203 action: 'delete',
204 email: email,
205 token: csrfToken
206 },
207 success: function (data) {
208 if (data.success) {
209 self.closest('.ticket-cc-email').parent('div').slideUp('fast').remove();
210 feedback.slideUp('fast')
211 .removeClass('alert-danger hidden')
212 .addClass('alert-success')
213 .html(data.message)
214 .slideDown('fast');
215 }
216 },
217 error: function (error) {
218 if (error) {
219 feedback.slideUp('fast')
220 .removeClass('alert-success hidden')
221 .addClass('alert-danger')
222 .html(error)
223 .slideDown('fast');
224 }
225 }
226 });
227 }).on('submit', '#frmAddCcEmail', function(e) {
228 e.preventDefault();
229 var frm = jQuery(this),
230 cloneRow = jQuery('#ccCloneRow').clone().removeAttr('id'),
231 email = jQuery('#inputAddCcEmail'),
232 feedback = jQuery('#divCcEmailFeedback');
233
234 if (feedback.is(':visible')) {
235 feedback.slideUp('fast');
236 }
237 WHMCS.http.jqClient.jsonPost({
238 url: frm.attr('action'),
239 data: frm.serialize(),
240 success: function (data) {
241 if (data.success) {
242 cloneRow.find('span.email')
243 .html(email.val())
244 .find('button')
245 .data('email', email.val())
246 .end();
247
248 cloneRow.removeClass('hidden')
249 .appendTo(jQuery('#sidebarTicketCc').find('.list-group'));
250 email.val('');
251 feedback.slideUp('fast')
252 .removeClass('alert-danger hidden')
253 .addClass('alert-success')
254 .html(data.message)
255 .slideDown('fast');
256 }
257 },
258 error: function (error) {
259 if (error) {
260 feedback.slideUp('fast')
261 .removeClass('alert-success hidden')
262 .addClass('alert-danger')
263 .html(error)
264 .slideDown('fast');
265 }
266 }
267 });
268 });
269
270 // Ticket Rating Click Handler
271 jQuery('.ticket-reply .rating span.star').click( function(event) {
272 window.location = 'viewticket.php?tid='
273 + jQuery(this).parent('.rating').attr("ticketid")
274 + '&c=' + jQuery(this).parent('.rating').attr("ticketkey")
275 + '&rating=rate' + jQuery(this).parent('.rating').attr("ticketreplyid")
276 + '_' + jQuery(this).attr("rate");
277 });
278
279 // Prevent malicious window.opener activity from auto-linked URLs
280 jQuery('a.autoLinked').click(function (e) {
281 e.preventDefault();
282 if (jQuery(this).hasClass('disabled')) {
283 return false;
284 }
285
286 var child = window.open();
287 child.opener = null;
288 child.location = e.target.href;
289 });
290
291 // Handle Single Sign-On Toggle Setting
292 jQuery("#inputAllowSso").on('switchChange.bootstrapSwitch', function(event, isChecked) {
293 if (isChecked) {
294 jQuery("#ssoStatusTextEnabled").removeClass('hidden').show();
295 jQuery("#ssoStatusTextDisabled").hide();
296 } else {
297 jQuery("#ssoStatusTextDisabled").removeClass('hidden').show();
298 jQuery("#ssoStatusTextEnabled").hide();
299 }
300 WHMCS.http.jqClient.post("clientarea.php", jQuery("#frmSingleSignOn").serialize());
301 });
302
303 // Single Sign-On call for Product/Service
304 jQuery('.btn-service-sso').on('click', function(e) {
305 e.preventDefault();
306 var button = jQuery(this);
307
308 var form = button.parents('form');
309
310 if (form.length === 0) {
311 form = button.find('form');
312 }
313 if (form.hasClass('disabled') || button.hasClass('disabled')) {
314 return;
315 }
316 var url = form.data('href');
317 if (!url) {
318 url = window.location.href;
319 }
320
321 button.attr('disabled', 'disabled').addClass('disabled');
322 jQuery('.loading', button).removeClass('hidden').show().end();
323 jQuery('.login-feedback', form).slideUp();
324 WHMCS.http.jqClient.post(
325 url,
326 form.serialize(),
327 function (data) {
328 jQuery('.loading', button).hide().end().removeAttr('disabled');
329 jQuery('.login-feedback', form).html('');
330 if (data.error) {
331 jQuery('.login-feedback', form).hide().html(data.error).slideDown();
332 }
333 if (data.redirect !== undefined && data.redirect.substr(0, 7) === 'window|') {
334 window.open(data.redirect.substr(7), '_blank');
335 }
336 },
337 'json'
338 ).always(function() {
339 button.removeAttr('disabled').removeClass('disabled');
340 jQuery('.loading', button).hide().end();
341 });
342 });
343 jQuery('.btn-sidebar-form-submit').on('click', function(e) {
344 e.preventDefault();
345 jQuery(this).find('.loading').removeClass('hidden').show().end()
346 .attr('disabled', 'disabled');
347
348 var form = jQuery(this).parents('form');
349
350 if (form.length == 0) {
351 form = jQuery(this).find('form');
352 }
353
354 if (form.length !== 0 && form.hasClass('disabled') === false) {
355 form.submit();
356 } else {
357 jQuery(this).find('.loading').hide().end().removeAttr('disabled');
358 }
359 });
360
361 // Back to top animated scroll
362 jQuery('.back-to-top').click(function(e) {
363 e.preventDefault();
364 jQuery('body,html').animate({scrollTop: 0}, 500);
365 });
366
367 // Prevent page scroll on language choose click
368 jQuery('.choose-language').click(function(e) {
369 e.preventDefault();
370 });
371
372 // Activate copy to clipboard functionality
373 jQuery('.copy-to-clipboard').click(WHMCS.ui.clipboard.copy);
374
375 // Password Generator
376 jQuery('.generate-password').click(function(e) {
377 jQuery('#frmGeneratePassword').submit();
378 jQuery('#modalGeneratePassword')
379 .data('targetfields', jQuery(this).data('targetfields'))
380 .modal('show');
381 });
382 jQuery('#frmGeneratePassword').submit(function(e) {
383 e.preventDefault();
384 var length = parseInt(jQuery('#inputGeneratePasswordLength').val(), 10);
385
386 // Check length
387 if (length < 8 || length > 64) {
388 jQuery('#generatePwLengthError').removeClass('hidden').show();
389 return;
390 }
391
392 jQuery('#inputGeneratePasswordOutput').val(WHMCS.utils.generatePassword(length));
393 });
394 jQuery('#btnGeneratePasswordInsert')
395 .click(WHMCS.ui.clipboard.copy)
396 .click(function(e) {
397 jQuery(this).closest('.modal').modal('hide');
398 var targetFields = jQuery(this).closest('.modal').data('targetfields');
399 targetFields = targetFields.split(',');
400 for(var i = 0; i < targetFields.length; i++) {
401 jQuery('#' + targetFields[i]).val(jQuery('#inputGeneratePasswordOutput').val())
402 .trigger('keyup');
403 }
404 // Remove the generated password.
405 jQuery('#inputGeneratePasswordOutput').val('');
406 });
407
408 /**
409 * Code will loop through each element that has the class markdown-editor and
410 * enable the Markdown editor.
411 */
412 var count = 0,
413 editorName = 'clientMDE',
414 counter = 0;
415 jQuery(".markdown-editor").each(function( index ) {
416 count++;
417 var autoSaveName = jQuery(this).data('auto-save-name'),
418 footerId = jQuery(this).attr('id') + '-footer';
419 if (typeof autoSaveName == "undefined") {
420 autoSaveName = 'client_area';
421 }
422 window[editorName + count.toString()] = jQuery(this).markdown(
423 {
424 footer: '<div id="' + footerId + '" class="markdown-editor-status"></div>',
425 autofocus: false,
426 savable: false,
427 resize: 'vertical',
428 iconlibrary: 'glyph',
429 language: locale,
430 onShow: function(e){
431 var content = '',
432 save_enabled = false;
433 if(typeof(Storage) !== "undefined") {
434 // Code for localStorage/sessionStorage.
435 content = localStorage.getItem(autoSaveName);
436 save_enabled = true;
437 if (content && typeof(content) !== "undefined") {
438 e.setContent(content);
439 }
440 }
441 jQuery("#" + footerId).html(parseMdeFooter(content, save_enabled, saved));
442 },
443 onChange: function(e){
444 var content = e.getContent(),
445 save_enabled = false;
446 if(typeof(Storage) !== "undefined") {
447 counter = 3;
448 save_enabled = true;
449 localStorage.setItem(autoSaveName, content);
450 doCountdown();
451 }
452 jQuery("#" + footerId).html(parseMdeFooter(content, save_enabled));
453 },
454 onPreview: function(e){
455 var originalContent = e.getContent(),
456 parsedContent;
457
458 jQuery.ajax({
459 url: WHMCS.utils.getRouteUrl('/clientarea/message/preview'),
460 async: false,
461 data: {token: csrfToken, content: originalContent},
462 dataType: 'json',
463 success: function (data) {
464 parsedContent = data;
465 }
466 });
467
468 return parsedContent.body ? parsedContent.body : '';
469 },
470 additionalButtons: [
471 [{
472 name: "groupCustom",
473 data: [{
474 name: "cmdHelp",
475 title: "Help",
476 hotkey: "Ctrl+F1",
477 btnClass: "btn open-modal",
478 icon: {
479 glyph: 'fas fa-question-circle',
480 fa: 'fas fa-question-circle',
481 'fa-3': 'icon-question-sign'
482 },
483 callback: function(e) {
484 e.$editor.removeClass("md-fullscreen-mode");
485 }
486 }]
487 }]
488 ],
489 hiddenButtons: [
490 'cmdImage'
491 ]
492 });
493
494 jQuery('button[data-handler="bootstrap-markdown-cmdHelp"]')
495 .attr('data-modal-title', markdownGuide)
496 .attr('href', 'submitticket.php?action=markdown');
497
498 jQuery(this).closest("form").bind({
499 submit: function() {
500 if(typeof(Storage) !== "undefined") {
501 localStorage.removeItem(autoSaveName);
502 }
503 }
504 });
505 });
506
507 // Email verification
508 var btnResendEmail = jQuery('.btn-resend-verify-email');
509 jQuery(btnResendEmail).click(function() {
510 $(this).prop('disabled', true).find('.loader').removeClass('hidden').show();
511 WHMCS.http.jqClient.post(
512 jQuery(this).data('uri'),
513 {
514 'token': csrfToken,
515 }).done(function(data) {
516 btnResendEmail.find('.loader').hide();
517 if (data.success) {
518 btnResendEmail.text(btnResendEmail.data('email-sent'));
519 } else {
520 btnResendEmail.text(btnResendEmail.data('error-msg'));
521 }
522 });
523 });
524 jQuery('#btnEmailVerificationClose').click(function(e) {
525 e.preventDefault();
526 WHMCS.http.jqClient.post(jQuery(this).data('uri'),
527 {
528 'token': csrfToken
529 });
530 jQuery('.verification-banner.email-verification').hide();
531 });
532
533 jQuery('#btnUserValidationClose').click(function(e) {
534 e.preventDefault();
535 WHMCS.http.jqClient.post(jQuery(this).data('uri'),
536 {
537 'token': csrfToken
538 });
539 jQuery('.verification-banner.user-validation').hide();
540 });
541
542 var ssoDropdown = jQuery('#servicesPanel').find('.list-group');
543 if (parseInt(ssoDropdown.css('height'), 10) < parseInt(ssoDropdown.css('max-height'), 10)) {
544 ssoDropdown.css('overflow', 'unset');
545 }
546
547
548 /**
549 * Parse the content to populate the markdown editor footer.
550 *
551 * @param {string} content
552 * @param {bool} auto_save
553 * @param {string} [saveText]
554 * @returns {string}
555 */
556 function parseMdeFooter(content, auto_save, saveText)
557 {
558 saveText = saveText || saving;
559 var pattern = /[^\s]+/g,
560 m = [],
561 word_count = 0,
562 line_count = 0;
563 if (content) {
564 m = content.match(pattern);
565 line_count = content.split(/\\r\\n|\\r|\\n/).length;
566 }
567 if (m) {
568 for (var i = 0; i < m.length; i++) {
569 if (m[i].charCodeAt(0) >= 0x4E00) {
570 word_count += m[i].length;
571 } else {
572 word_count += 1;
573 }
574 }
575 }
576 return '<div class="small-font">lines: ' + line_count
577 + ' words: ' + word_count + ''
578 + (auto_save ? ' <span class="markdown-save">' + saveText + '</span>' : '')
579 + '</div>';
580 }
581
582 /**
583 * Countdown the save timeout. When zero, the span will update to show saved.
584 */
585 function doCountdown()
586 {
587 if (counter >= 0) {
588 if (counter == 0) {
589 jQuery("span.markdown-save").html(saved);
590 }
591 counter--;
592 setTimeout(doCountdown, 1000);
593 }
594 }
595
596 // Two-Factor Activation Process Modal Handler.
597 var frmTwoFactorActivation = jQuery('input[name=2fasetup]').parent('form');
598 frmTwoFactorActivation.submit(function(e) {
599 e.preventDefault();
600 openModal(frmTwoFactorActivation.attr('action'), frmTwoFactorActivation.serialize(), 'Loading...');
601 });
602
603 $.fn.setInputError = function(error) {
604 this.parents('.form-group').addClass('has-error').find('.field-error-msg').text(error);
605 return this;
606 };
607
608 jQuery.fn.showInputError = function () {
609 this.parents('.form-group').addClass('has-error').find('.field-error-msg').show();
610 return this;
611 };
612
613 jQuery('#frmPayment').on('submit', function() {
614 var btn = jQuery('#btnSubmit');
615 btn.find('span').toggle();
616 btn.prop('disabled', true).addClass('disabled');
617 });
618
619 // SSL Manage Action Button.
620 jQuery('.btn-resend-approver-email').click(function () {
621 WHMCS.http.jqClient.post(
622 jQuery(this).data('url'),
623 {
624 addonId: jQuery(this).data('addonid'),
625 serviceId: jQuery(this).data('serviceid'),
626 },
627 function(data) {
628 if (data.success == true) {
629 jQuery('.alert-table-ssl-manage').addClass('alert-success').text('Approver Email Resent').removeClass('hidden');
630 } else {
631 jQuery('.alert-table-ssl-manage').addClass('alert-danger').text('Error: ' + data.message).removeClass('hidden');
632 }
633 }
634 );
635 });
636
637 // Domain Pricing Table Filters
638 jQuery(".tld-filters a").click(function(e) {
639 e.preventDefault();
640
641 var noTlds = jQuery('.tld-row.no-tlds');
642
643 if (jQuery(this).hasClass('badge-success')) {
644 jQuery(this).removeClass('badge-success');
645 } else {
646 jQuery(this).addClass('badge-success');
647 }
648 if (noTlds.is(':visible')) {
649 noTlds.hide();
650 }
651
652 jQuery('.tld-row').removeClass('filtered-row');
653 jQuery('.tld-filters a.badge-success').each(function(index) {
654 var filterValue = jQuery(this).data('category');
655 jQuery('.tld-row[data-category*="' + filterValue + '"]').addClass('filtered-row');
656 });
657 jQuery(".filtered-row:even").removeClass('highlighted');
658 jQuery(".filtered-row:odd").addClass('highlighted');
659
660 var rowsToHide = jQuery('.tld-row:not(".filtered-row")');
661 rowsToHide.fadeOut('fast');
662 rowsToHide.promise().done(function () {
663 if (jQuery('.filtered-row').length === 0) {
664 noTlds.show();
665 } else {
666 jQuery('.tld-row.filtered-row').show();
667 }
668 });
669 });
670 jQuery(".filtered-row:even").removeClass('highlighted');
671 jQuery(".filtered-row:odd").addClass('highlighted');
672
673 // DataTable data-driven auto object registration
674 WHMCS.ui.dataTable.register();
675
676 WHMCS.ui.jsonForm.initAll();
677
678 jQuery('#frmReply').submit(function(e) {
679 jQuery('#frmReply').find('input[type="submit"]').addClass('disabled').prop('disabled', true);
680 });
681
682 jQuery('#frmDomainContactModification').on('submit', function(){
683 if (!allowSubmit) {
684 var changed = false;
685 jQuery('.irtp-field').each(function() {
686 var value = jQuery(this).val(),
687 originalValue = jQuery(this).data('original-value');
688 if (value !== originalValue) {
689 changed = true;
690 }
691 });
692 if (changed) {
693 jQuery('#modalIRTPConfirmation').modal('show');
694 return false;
695 }
696 }
697 return true;
698 });
699
700 jQuery('.ssl-state.ssl-sync').each(function () {
701 var self = jQuery(this),
702 type = getSslAttribute(self, 'type'),
703 domain = getSslAttribute(self, 'domain');
704 WHMCS.http.jqClient.post(
705 WHMCS.utils.getRouteUrl('/domain/ssl-check'),
706 {
707 'type': type,
708 'domain': domain,
709 'token': csrfToken
710 },
711 function (data) {
712 if (data.invalid) {
713 self.hide();
714 } else {
715 var width = '',
716 statusDisplayLabel = '';
717 if (self.attr('width')) {
718 width = ' width="' + self.attr('width') + '"';
719 }
720 if (self.data('showlabel')) {
721 statusDisplayLabel = ' ' + data.statusDisplayLabel;
722 }
723 self.replaceWith(
724 '<img src="' + data.image + '" data-toggle="tooltip" title="' + data.tooltip + '" class="' + data.class + '"' + width + '>'
725 );
726 if (data.ssl.status === 'active') {
727 jQuery('#ssl-startdate').text(data.ssl.startDate);
728 jQuery('#ssl-expirydate').text(data.ssl.expiryDate);
729 jQuery('#ssl-issuer').text(data.ssl.issuer);
730 } else {
731 jQuery('#ssl-startdate').parent('div').hide();
732 jQuery('#ssl-expirydate').parent('div').hide();
733 jQuery('#ssl-issuer').parent('div').hide();
734 }
735
736 jQuery('#statusDisplayLabel').text(statusDisplayLabel);
737 }
738 }
739 );
740 });
741
742 jQuery(document).on('click', '.ssl-state.ssl-inactive', function(e) {
743 e.preventDefault();
744 window.location.href = WHMCS.utils.getRouteUrl('/ssl-purchase');
745 });
746
747 WHMCS.recaptcha.register();
748
749 var dynamicRecaptchaContainer = jQuery('#divDynamicRecaptcha');
750 var homepageHasRecaptcha = jQuery(dynamicRecaptchaContainer).length > 0;
751 var homepageHasInvisibleRecaptcha = homepageHasRecaptcha && jQuery(dynamicRecaptchaContainer).data('size') === 'invisible';
752
753 var frmDomainHomepage = jQuery('#frmDomainHomepage');
754
755 jQuery(frmDomainHomepage).find('#btnTransfer').click(function () {
756 jQuery(frmDomainHomepage).find('input[name="transfer"]').val('1');
757 });
758
759 if (homepageHasRecaptcha && !homepageHasInvisibleRecaptcha) {
760 jQuery('section#home-banner').addClass('with-recaptcha');
761 }
762
763 if (jQuery('.domainchecker-homepage-captcha').length && !homepageHasInvisibleRecaptcha) {
764 // invisible reCaptcha doesn't play well with onsubmit() handlers on all submissions following a prevented one
765
766 jQuery(frmDomainHomepage).submit(function (e) {
767 var inputDomain = jQuery(frmDomainHomepage).find('input[name="domain"]'),
768 reCaptchaContainer = jQuery('#divDynamicRecaptcha'),
769 reCaptcha = jQuery('#g-recaptcha-response'),
770 captcha = jQuery('#inputCaptcha');
771
772 if (reCaptcha.length && !reCaptcha.val()) {
773 reCaptchaContainer.tooltip('show');
774
775 e.preventDefault();
776 return;
777 }
778
779 if (captcha.length && !captcha.val()) {
780 captcha.tooltip('show');
781
782 e.preventDefault();
783 return;
784 }
785 });
786 }
787
788 $('.icheck-button').iCheck({
789 inheritID: true,
790 checkboxClass: 'icheckbox_square-blue',
791 radioClass: 'iradio_square-blue',
792 increaseArea: '20%'
793 });
794
795 jQuery('#inputNoStore').on('switchChange.bootstrapSwitch', function(event, state) {
796 var descContainer = jQuery('#inputDescription');
797 if (!state) {
798 descContainer.prop('disabled', true).addClass('disabled');
799 }
800 if (state) {
801 descContainer.removeClass('disabled').prop('disabled', false);
802 }
803 });
804
805 jQuery(document).on('click', '#btnConfirmModalConfirmBtn', function () {
806 var confirmButton = jQuery(this),
807 confirmationModal = confirmButton.closest('div.modal'),
808 targetUrl = confirmButton.data('target-url'),
809 dataTable = confirmButton.closest('table.dataTable[data-on-draw-rebind-confirmation-modal="true"]');
810 WHMCS.http.jqClient.jsonPost(
811 {
812 url: targetUrl,
813 data: {
814 token: csrfToken
815 },
816 success: function(data) {
817 if (data.status === 'success' || data.status === 'okay') {
818 if (dataTable.length > 0) {
819 dataTable.DataTable().ajax.reload();
820 }
821 }
822 }
823 }
824 );
825 confirmationModal.modal('toggle');
826 });
827 jQuery('input[name="approval_method"]').on('ifChecked', function(event) {
828 var fileMethod = $('#containerApprovalMethodFile'),
829 emailMethod = $('#containerApprovalMethodEmail'),
830 dnsMethod = $('#containerApprovalMethodDns');
831 if (jQuery(this).attr('value') == 'file') {
832 fileMethod.show();
833 dnsMethod.hide();
834 emailMethod.hide();
835 } else if (jQuery(this).attr('value') == 'dns-txt-token') {
836 dnsMethod.show();
837 fileMethod.hide();
838 emailMethod.hide();
839 } else {
840 fileMethod.hide();
841 dnsMethod.hide();
842 emailMethod.show();
843 }
844 });
845
846 (function () {
847 jQuery('.div-service-status').css(
848 'width',
849 (jQuery('.div-service-status .label-placeholder').outerWidth() + 5)
850 );
851 jQuery('div[menuitemname="Active Products/Services"] .list-group-item:visible')
852 .last()
853 .css('border-bottom', '1px solid #ddd');
854 }());
855 jQuery('div[menuitemname="Active Products/Services"] .btn-view-more').on('click', function(event) {
856 var hiddenItems = jQuery('div[menuitemname="Active Products/Services"] .list-group-item:hidden');
857 var itemAmount = 8;
858 event.preventDefault();
859 hiddenItems.slice(0,itemAmount).css('display', 'block');
860 if ((hiddenItems.length - itemAmount) <= 0) {
861 jQuery(event.target).addClass('disabled').attr("aria-disabled", true);
862 }
863 jQuery('div[menuitemname="Active Products/Services"] .list-group-item:visible')
864 .css('border-bottom', '')
865 .last()
866 .css('border-bottom', '1px solid #ddd');
867 })
868 jQuery('div[menuitemname="Service Details Actions"] a[data-identifier][data-serviceid][data-active="1"]').on('click', function(event) {
869 return customActionAjaxCall(event, jQuery(event.target))
870 });
871 jQuery('.div-service-item').on('click', function (event) {
872 var element = jQuery(event.target);
873 if (element.is('.dropdown-toggle, .dropdown-menu, .caret')) {
874 return true;
875 }
876 if (element.hasClass('btn-custom-action')) {
877 return customActionAjaxCall(event, element);
878 }
879 window.location.href = element.closest('.div-service-item').data('href');
880 return false;
881 });
882});
883
884/**
885 * Control disabled/enabled state of elements by class name.
886 *
887 * @param {string} className Common element class name.
888 * @param {bool} disabledState Whether the elements should be disabled or not.
889 */
890function disableFields(className, disabledState) {
891 if (className[0] != '.') {
892 className = '.' + className;
893 }
894 var elements = jQuery(className);
895 elements.prop('disabled', disabledState);
896 if (disabledState) {
897 elements.addClass('disabled');
898 } else {
899 elements.removeClass('disabled');
900 }
901}
902
903/**
904 * Check all checkboxes with a given class.
905 *
906 * @param {string} className Common class name.
907 * @param {domElement} masterControl Parent checkbox to which the other checkboxes should mirror.
908 */
909function checkAll(className, masterControl) {
910 if (className[0] != '.') {
911 className = '.' + className;
912 }
913 // In jQuery, if you set the checked attribute directly, the dom
914 // element is changed, but browsers don't show the check box as
915 // checked. Using the click event will properly display.
916 jQuery(className).removeAttr('checked');
917 if(jQuery(masterControl).is(":checked")) {
918 jQuery(className).click();
919 }
920}
921
922/**
923 * Redirect on click if an element is not a button or link.
924 *
925 * Where table rows are clickable, we only want to redirect if the row
926 * itself is clicked. If a button or link within the row is clicked,
927 * the event tied to that object should be executed. This function
928 * stops the standard JS event bubbling required to make that happen.
929 *
930 * @param {object} clickEvent jQuery click event
931 * @param {string} target Redirect location
932 * @param {bool} newWindow Open link in new window
933 */
934function clickableSafeRedirect(clickEvent, target, newWindow) {
935 var eventSource = clickEvent.target.tagName.toLowerCase();
936 var eventParent = clickEvent.target.parentNode.tagName.toLowerCase();
937 var eventTable = clickEvent.target.parentNode.parentNode.parentNode;
938 if (jQuery(eventTable).hasClass('collapsed')) {
939 // This is a mobile device sized display, and datatables has triggered folding
940 return false;
941 }
942 if (eventSource === 'i' && jQuery(clickEvent.target).hasClass('ssl-required')) {
943 return false;
944 }
945 if(eventSource != 'button' && eventSource != 'a') {
946 if(eventParent != 'button' && eventParent != 'a') {
947 if (newWindow) {
948 window.open(target);
949 } else {
950 window.location.href = target;
951 }
952 }
953 }
954}
955
956/**
957 * Open a centered popup window.
958 *
959 * @param {string} addr The URL to navigate to
960 * @param {string} popname The name to assign the window
961 * @param {number} w The width
962 * @param {number} h The height
963 * @param {string} features Any additional settings to apply
964 */
965function popupWindow(addr, popname, w, h, features) {
966 var winl = (screen.width-w) / 2;
967 var wint = (screen.height-h) / 2;
968 if (winl < 0) winl = 0;
969 if (wint < 0) wint = 0;
970 var settings = 'height=' + h + ',';
971 settings += 'width=' + w + ',';
972 settings += 'top=' + wint + ',';
973 settings += 'left=' + winl + ',';
974 settings += features;
975 win = window.open(addr, popname, settings);
976 win.window.focus();
977}
978
979/**
980 * Add domain renewal to shopping cart.
981 *
982 * @param {number} renewalID The domain ID to be added
983 * @param {domElement} selfThis The object triggering the add
984 */
985function addRenewalToCart(renewalID, selfThis) {
986 jQuery("#domainRow" + renewalID).attr('disabled', 'disabled');
987 jQuery("#domainRow" + renewalID).find("select,button").attr("disabled", "disabled");
988 jQuery(selfThis).html('<span class="glyphicon glyphicon-shopping-cart"></span> Adding...');
989 var renewalPeriod = jQuery("#renewalPeriod" + renewalID).val();
990 WHMCS.http.jqClient.post(
991 "clientarea.php",
992 "addRenewalToCart=1&token=" + csrfToken + "&renewID="+ renewalID + "&period=" + renewalPeriod,
993 function( data ) {
994 jQuery("#cartItemCount").html(((jQuery("#cartItemCount").html() * 1) + 1));
995 jQuery(selfThis).html('<span class="glyphicon glyphicon-shopping-cart"></span> Added');
996 jQuery("#btnCheckout").fadeIn();
997 }
998 );
999}
1000
1001/**
1002 * Navigate to a page on dropdown change.
1003 *
1004 * This is implemented onblur() for a dropdown. When the dropdown
1005 * changes state, the value is pulled and the browser navigated to
1006 * the selected page.
1007 *
1008 * @param {domElement} select The dropdown triggering the event
1009 */
1010function selectChangeNavigate(select) {
1011 window.location.href = $(select).val();
1012}
1013
1014/**
1015 * Append additional file upload input field.
1016 */
1017function extraTicketAttachment() {
1018 jQuery("#fileUploadsContainer").append('<input type="file" name="attachments[]" class="form-control" />');
1019}
1020
1021/**
1022 * Fetch load and uptime for a given server.
1023 *
1024 * @param {number} num Server Id
1025 */
1026function getStats(num) {
1027 WHMCS.http.jqClient.post('serverstatus.php', 'getstats=1&num=' + num, function(data) {
1028 jQuery("#load"+num).html(data.load);
1029 jQuery("#uptime"+num).html(data.uptime);
1030 },'json');
1031}
1032
1033/**
1034 * Determine status of a given port for a given server.
1035 *
1036 * @param {number} num Server Id
1037 * @param {number} port Port Number
1038 */
1039function checkPort(num, port) {
1040 WHMCS.http.jqClient.post('serverstatus.php', 'ping=1&num=' + num + '&port=' + port, function(data) {
1041 jQuery("#port" + port + "_" + num).html(data);
1042 });
1043}
1044
1045/**
1046 * Fetch automated knowledgebase suggestions for ticket content.
1047 */
1048function getticketsuggestions() {
1049 currentcheckcontent = jQuery("#message").val();
1050 if (currentcheckcontent != lastcheckcontent && currentcheckcontent != "") {
1051 WHMCS.http.jqClient.post("submitticket.php", { action: "getkbarticles", text: currentcheckcontent },
1052 function(data){
1053 if (data) {
1054 jQuery("#searchresults").html(data);
1055 jQuery("#searchresults").hide().removeClass('hidden').slideDown();
1056 }
1057 });
1058 lastcheckcontent = currentcheckcontent;
1059 }
1060 setTimeout('getticketsuggestions();', 3000);
1061}
1062
1063/**
1064 * Update custom fields upon department change.
1065 *
1066 * @param {domElement} input The department selector dropdown object
1067 */
1068function refreshCustomFields(input) {
1069 jQuery("#customFieldsContainer").load(
1070 "submitticket.php",
1071 { action: "getcustomfields", deptid: $(input).val() }
1072 );
1073}
1074
1075/**
1076 * Submit the first form that exists within a given container.
1077 *
1078 * @param {string} containerId The ID name of the container
1079 */
1080function autoSubmitFormByContainer(containerId) {
1081 if (typeof noAutoSubmit === "undefined" || noAutoSubmit === false) {
1082 jQuery("#" + containerId).find("form:first").submit();
1083 }
1084}
1085
1086/**
1087 * Submit default whois info and disable custom fields.
1088 *
1089 * @param {string} regType The contact registration type
1090 */
1091function useDefaultWhois(regType) {
1092 jQuery("." + regType.substr(0, regType.length - 1) + "customwhois").attr("disabled", true);
1093 jQuery("." + regType.substr(0, regType.length - 1) + "defaultwhois").attr("disabled", false);
1094 jQuery('#' + regType.substr(0, regType.length - 1) + '1').attr("checked", "checked");
1095}
1096
1097/**
1098 * Submit custom fields and disable default whois info.
1099 *
1100 * @param {string} regType The contact registration type
1101 */
1102function useCustomWhois(regType) {
1103 jQuery("." + regType.substr(0, regType.length - 1) + "customwhois").attr("disabled", false);
1104 jQuery("." + regType.substr(0, regType.length - 1) + "defaultwhois").attr("disabled", true);
1105 jQuery('#' + regType.substr(0, regType.length - 1) + '2').attr("checked", "checked");
1106}
1107
1108function showNewBillingAddressFields() {
1109 jQuery('#newBillingAddress').slideDown();
1110}
1111
1112function hideNewBillingAddressFields() {
1113 jQuery('#newBillingAddress').slideUp();
1114}
1115
1116/**
1117 * Show new credit card input fields.
1118 */
1119function showNewCardInputFields() {
1120 var ccDetails = jQuery('.cc-details'),
1121 ccNumber = jQuery('#inputCardNumber'),
1122 billAddress = jQuery('#billingAddressChoice');
1123
1124 if (ccDetails.hasClass("hidden")) {
1125 ccDetails.hide().removeClass("hidden");
1126 }
1127 ccDetails.slideDown();
1128 ccNumber.focus();
1129
1130 billAddress.slideDown()
1131 .find('input[name="billingcontact"]')
1132 .first()
1133 .iCheck('check');
1134}
1135
1136/**
1137 * Show new bank account input fields.
1138 */
1139function showNewAccountInputFields() {
1140 if (jQuery(".bank-details").hasClass("hidden")) {
1141 jQuery(".bank-details").hide().removeClass("hidden");
1142 }
1143 jQuery(".bank-details").slideDown();
1144
1145 jQuery("#billingAddressChoice")
1146 .slideDown()
1147 .find('input[name="billingcontact"]')
1148 .first()
1149 .iCheck('check');
1150}
1151
1152/**
1153 * Hide new credit card input fields.
1154 */
1155function hideNewCardInputFields() {
1156 hideNewBillingAddressFields();
1157 jQuery(".cc-details").slideUp();
1158 jQuery("#billingAddressChoice").slideUp();
1159 var contactId = jQuery('input[name="ccinfo"]:checked').data('billing-contact-id');
1160 if (contactId != undefined) {
1161 jQuery('#billingAddressChoice label.billing-contact-' + contactId)
1162 .iCheck('check');
1163 }
1164 jQuery('#inputCardCvv').focus();
1165}
1166
1167/**
1168 * Hide new bank account input fields.
1169 */
1170function hideNewAccountInputFields() {
1171 hideNewBillingAddressFields();
1172
1173 jQuery(".bank-details").slideUp();
1174 jQuery("#billingAddressChoice").slideUp();
1175
1176 var selectedAccount = jQuery('input[name="paymethod"]:checked'),
1177 selectedContactId = jQuery(selectedAccount).data('billing-contact-id'),
1178 selectedContactData = jQuery('.billing-contact-info[data-billing-contact-id="' + selectedContactId + '"]');
1179
1180 if (selectedContactData.length) {
1181 jQuery('.billing-contact-info').hide();
1182 jQuery(selectedContactData).show();
1183 }
1184}
1185
1186/**
1187 * Get automatic knowledgebase suggestions for support ticket message.
1188 */
1189var lastTicketMsg;
1190function getTicketSuggestions() {
1191 var userMsg = jQuery("#inputMessage").val();
1192 if (userMsg != lastTicketMsg && userMsg != '') {
1193 WHMCS.http.jqClient.post("submitticket.php", { action: "getkbarticles", text: userMsg },
1194 function (data) {
1195 if (data) {
1196 jQuery("#autoAnswerSuggestions").html(data);
1197 if (!jQuery("#autoAnswerSuggestions").is(":visible")) {
1198 jQuery("#autoAnswerSuggestions").hide().removeClass('hidden').slideDown();
1199 }
1200 }
1201 });
1202 lastTicketMsg = userMsg;
1203 }
1204 setTimeout('getTicketSuggestions()', 3000);
1205}
1206
1207/**
1208 * Smooth scroll to named element.
1209 */
1210function smoothScroll(element) {
1211 $('html, body').animate({
1212 scrollTop: $(element).offset().top
1213 }, 500);
1214}
1215
1216function irtpSubmit() {
1217 allowSubmit = true;
1218 var optOut = 0,
1219 optOutCheckbox = jQuery('#modalIrtpOptOut'),
1220 optOutReason = jQuery('#modalReason'),
1221 formOptOut = jQuery('#irtpOptOut'),
1222 formOptOutReason = jQuery('#irtpOptOutReason');
1223
1224 if (optOutCheckbox.is(':checked')) {
1225 optOut = 1;
1226 }
1227 formOptOut.val(optOut);
1228 formOptOutReason.val(optOutReason.val());
1229 jQuery('#frmDomainContactModification').submit();
1230}
1231
1232function showOverlay(msg) {
1233 jQuery('#fullpage-overlay .msg').html(msg);
1234 jQuery('#fullpage-overlay').removeClass('hidden').show();
1235}
1236
1237function hideOverlay() {
1238 jQuery('#fullpage-overlay').hide();
1239}
1240
1241function getSslAttribute(element, attribute) {
1242 if (element.data(attribute)) {
1243 return element.data(attribute);
1244 }
1245 return element.parent('td').data(attribute);
1246}
1247
1248function openValidationSubmitModal(caller)
1249{
1250 var validationSubmitModal = jQuery('#validationSubmitModal');
1251 validationSubmitModal.find('.modal-body iframe').attr('src', caller.dataset.url);
1252 validationSubmitModal.modal('show');
1253}
1254
1255function completeValidationComClientWorkflow()
1256{
1257 var submitDocsRequestBanner = jQuery('.user-validation'),
1258 secondarySidebarStatus = jQuery('.validation-status-label'),
1259 submitDiv = jQuery('.validation-submit-div'),
1260 redirectUser = true;
1261
1262 $('#validationSubmitModal').modal('hide');
1263 if (submitDocsRequestBanner.length !== 0) {
1264 submitDocsRequestBanner.slideUp();
1265 redirectUser = false;
1266 }
1267 if (secondarySidebarStatus.length !== 0) {
1268 var submitString = submitDiv.find('a').data('submitted-string');
1269 secondarySidebarStatus.text(submitString).removeClass('label-default').addClass('label-warning');
1270 submitDiv.hide();
1271 redirectUser = false;
1272 }
1273
1274 if (redirectUser) {
1275 window.location.href = WHMCS.utils.autoDetermineBaseUrl();
1276 }
1277 return false;
1278}
1279
1280/**
1281 * Perform the AjaxCall for a CustomAction.
1282 *
1283 * @param event
1284 * @param element
1285 * @returns {boolean}
1286 */
1287function customActionAjaxCall(event, element) {
1288 event.stopPropagation();
1289 if (!element.data('active')) {
1290 return false;
1291 }
1292 element.attr('disabled', 'disabled').addClass('disabled');
1293 jQuery('.loading', element).show();
1294 WHMCS.http.jqClient.jsonPost({
1295 url: WHMCS.utils.getRouteUrl(
1296 '/clientarea/service/' + element.data('serviceid') + '/custom-action/' + element.data('identifier')
1297 ),
1298 data: {
1299 'token': csrfToken
1300 },
1301 success: function(data) {
1302 if (data.success) {
1303 window.open(data.redirectTo);
1304 } else {
1305 window.open('clientarea.php?action=productdetails&id=' + element.data('serviceid') + '&customaction_error=1');
1306 }
1307 },
1308 fail: function () {
1309 window.open('clientarea.php?action=productdetails&id=' + element.data('serviceid') + '&customaction_ajax_error=1');
1310 },
1311 always: function() {
1312 jQuery('.loading', element).hide();
1313 element.removeAttr('disabled').removeClass('disabled');
1314 if (element.hasClass('dropdown-item')) {
1315 element.closest('.dropdown-menu').removeClass('show');
1316 }
1317 },
1318 });
1319 return true;
1320}
1321