PageSourceSearch

https://dcs.net/wp-content/plugins/wpforms-lite/assets/js/wpforms.js?ver=1.5.8.2

js dcs.net collected 2026-10-06 21:02:03 UTC 53,257 bytes, 1,765 lines download raw bytes

1/* global wpforms_settings, grecaptcha, wpformsRecaptchaCallback, wpforms_validate, wpforms_datepicker, wpforms_timepicker, Mailcheck */
2
3'use strict';
4
5var wpforms = window.wpforms || ( function( document, window, $ ) {
6
7	var app = {
8
9		/**
10		 * Start the engine.
11		 *
12		 * @since 1.2.3
13		 */
14		init: function() {
15
16			// Document ready.
17			$( document ).ready( app.ready );
18
19			// Page load.
20			$( window ).on( 'load', app.load );
21
22			app.bindUIActions();
23			app.bindOptinMonster();
24		},
25
26		/**
27		 * Document ready.
28		 *
29		 * @since 1.2.3
30		 */
31		ready: function() {
32
33			// Clear URL - remove wpforms_form_id.
34			app.clearUrlQuery();
35
36			// Set user identifier.
37			app.setUserIndentifier();
38
39			app.loadValidation();
40			app.loadDatePicker();
41			app.loadTimePicker();
42			app.loadInputMask();
43			app.loadSmartPhoneField();
44			app.loadPayments();
45			app.loadMailcheck();
46
47			// Randomize elements.
48			$( '.wpforms-randomize' ).each( function() {
49				var $list      = $( this ),
50					$listItems = $list.children();
51				while ( $listItems.length ) {
52					$list.append( $listItems.splice( Math.floor( Math.random() * $listItems.length ), 1 )[0] );
53				}
54			} );
55
56			$( document ).trigger( 'wpformsReady' );
57		},
58
59		/**
60		 * Page load.
61		 *
62		 * @since 1.2.3
63		 */
64		load: function() {
65
66		},
67
68		//--------------------------------------------------------------------//
69		// Initializing
70		//--------------------------------------------------------------------//
71
72		/**
73		 * Remove wpforms_form_id from URL.
74		 *
75		 * @since 1.5.2
76		 */
77		clearUrlQuery: function() {
78			var loc   = window.location,
79				query = loc.search;
80
81			if ( query.indexOf( 'wpforms_form_id=' ) !== -1 ) {
82				query = query.replace( /([&?]wpforms_form_id=[0-9]*$|wpforms_form_id=[0-9]*&|[?&]wpforms_form_id=[0-9]*(?=#))/, '' );
83				history.replaceState( {}, null, loc.origin + loc.pathname + query );
84			}
85		},
86
87
88		/**
89		 * Load jQuery Validation.
90		 *
91		 * @since 1.2.3
92		 */
93		loadValidation: function() {
94
95			// Only load if jQuery validation library exists.
96			if ( typeof $.fn.validate !== 'undefined' ) {
97
98				// jQuery Validation library will not correctly validate
99				// fields that do not have a name attribute, so we use the
100				// `wpforms-input-temp-name` class to add a temporary name
101				// attribute before validation is initialized, then remove it
102				// before the form submits.
103				$( '.wpforms-input-temp-name' ).each( function( index, el ) {
104					var random = Math.floor( Math.random() * 9999 ) + 1;
105					$( this ).attr( 'name', 'wpf-temp-' + random );
106				} );
107
108				// Prepend URL field contents with http:// if user input doesn't contain a schema.
109				$( '.wpforms-validate input[type=url]' ).change( function() {
110					var url = $( this ).val();
111					if ( ! url ) {
112						return false;
113					}
114					if ( url.substr( 0, 7 ) !== 'http://' && url.substr( 0, 8 ) !== 'https://' ) {
115						$( this ).val( 'http://' + url );
116					}
117				} );
118
119				$.validator.messages.required = wpforms_settings.val_required;
120				$.validator.messages.url      = wpforms_settings.val_url;
121				$.validator.messages.email    = wpforms_settings.val_email;
122				$.validator.messages.number   = wpforms_settings.val_number;
123
124				// Payments: Validate method for Credit Card Number.
125				if ( typeof $.fn.payment !== 'undefined' ) {
126					$.validator.addMethod( 'creditcard', function( value, element ) {
127
128						//var type  = $.payment.cardType(value);
129						var valid = $.payment.validateCardNumber( value );
130						return this.optional( element ) || valid;
131					}, wpforms_settings.val_creditcard );
132
133					// @todo validate CVC and expiration
134				}
135
136				// Validate method for file extensions.
137				$.validator.addMethod( 'extension', function( value, element, param ) {
138					param = 'string' === typeof param ? param.replace( /,/g, '|' ) : 'png|jpe?g|gif';
139					return this.optional( element ) || value.match( new RegExp( '\\.(' + param + ')$', 'i' ) );
140				}, wpforms_settings.val_fileextension );
141
142				// Validate method for file size.
143				$.validator.addMethod( 'maxsize', function( value, element, param ) {
144					var maxSize = param,
145						optionalValue = this.optional( element ),
146						i, len, file;
147					if ( optionalValue ) {
148						return optionalValue;
149					}
150					if ( element.files && element.files.length ) {
151						i = 0;
152						len = element.files.length;
153						for ( ; i < len; i++ ) {
154							file = element.files[i];
155							if ( file.size > maxSize ) {
156								return false;
157							}
158						}
159					}
160					return true;
161				}, wpforms_settings.val_filesize );
162
163				// Validate email addresses.
164				$.validator.methods.email = function( value, element ) {
165					return this.optional( element ) || /^[a-z0-9.!#$%&'*+\/=?^_`{|}~-]+@((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/i.test( value );
166				};
167
168				// Validate confirmations.
169				$.validator.addMethod( 'confirm', function( value, element, param ) {
170					return $.validator.methods.equalTo.call( this, value, element, param );
171				}, wpforms_settings.val_confirm );
172
173				// Validate required payments.
174				$.validator.addMethod( 'required-payment', function( value, element ) {
175					return app.amountSanitize( value ) > 0;
176				}, wpforms_settings.val_requiredpayment );
177
178				// Validate 12-hour time.
179				$.validator.addMethod( 'time12h', function( value, element ) {
180					return this.optional( element ) || /^((0?[1-9]|1[012])(:[0-5]\d){1,2}(\ ?[AP]M))$/i.test( value );
181				}, wpforms_settings.val_time12h );
182
183				// Validate 24-hour time.
184				$.validator.addMethod( 'time24h', function( value, element ) {
185					return this.optional( element ) || /^(([0-1]?[0-9])|([2][0-3])):([0-5]?[0-9])(\ ?[AP]M)?$/i.test( value );
186				}, wpforms_settings.val_time24h );
187
188				// Validate checkbox choice limit.
189				$.validator.addMethod( 'check-limit', function( value, element ) {
190					var $ul = $( element ).closest( 'ul' ),
191						$checked = $ul.find( 'input[type="checkbox"]:checked' ),
192						choiceLimit = parseInt( $ul.attr( 'data-choice-limit' ) || 0, 10 );
193
194					if ( 0 === choiceLimit ) {
195						return true;
196					}
197					return $checked.length <= choiceLimit;
198				}, function( params, element ) {
199					var	choiceLimit = parseInt( $( element ).closest( 'ul' ).attr( 'data-choice-limit' ) || 0, 10 );
200					return wpforms_settings.val_checklimit.replace( '{#}', choiceLimit );
201				} );
202
203				// Validate Smart Phone Field.
204				if ( typeof $.fn.intlTelInput !== 'undefined' ) {
205					$.validator.addMethod( 'smart-phone-field', function( value, element ) {
206						return this.optional( element ) || $( element ).intlTelInput( 'isValidNumber' );
207					}, wpforms_settings.val_phone );
208				}
209
210				// Validate US Phone Field.
211				$.validator.addMethod( 'us-phone-field', function( value, element ) {
212					return this.optional( element ) || value.replace( /[^\d]/g, '' ).length === 10;
213				}, wpforms_settings.val_phone );
214
215				// Validate International Phone Field.
216				$.validator.addMethod( 'int-phone-field', function( value, element ) {
217					return this.optional( element ) || value.replace( /[^\d]/g, '' ).length > 0;
218				}, wpforms_settings.val_phone );
219
220				// Finally load jQuery Validation library for our forms.
221				$( '.wpforms-validate' ).each( function() {
222					var form   = $( this ),
223						formID = form.data( 'formid' ),
224						properties;
225
226					// TODO: cleanup this BC with wpforms_validate.
227					if ( typeof window['wpforms_' + formID] !== 'undefined' && window['wpforms_' + formID].hasOwnProperty( 'validate' ) ) {
228						properties = window['wpforms_' + formID].validate;
229					} else if ( typeof wpforms_validate !== 'undefined' ) {
230						properties = wpforms_validate;
231					} else {
232						properties = {
233							errorClass: 'wpforms-error',
234							validClass: 'wpforms-valid',
235							errorPlacement: function( error, element ) {
236								if ( 'radio' === element.attr( 'type' ) || 'checkbox' === element.attr( 'type' ) ) {
237									if ( element.hasClass( 'wpforms-likert-scale-option' ) ) {
238										if ( element.closest( 'table' ).hasClass( 'single-row' ) ) {
239											element.closest( 'table' ).after( error );
240										} else {
241											element.closest( 'tr' ).find( 'th' ).append( error );
242										}
243									} else if ( element.hasClass( 'wpforms-net-promoter-score-option' ) ) {
244										element.closest( 'table' ).after( error );
245									} else {
246										element.closest( '.wpforms-field-checkbox' ).find( 'label.wpforms-error' ).remove();
247										element.parent().parent().parent().append( error );
248									}
249								} else if ( element.is( 'select' ) && element.attr( 'class' ).match( /date-month|date-day|date-year/ ) ) {
250									if ( 0 === element.parent().find( 'label.wpforms-error:visible' ).length ) {
251										element.parent().find( 'select:last' ).after( error );
252									}
253								} else if ( element.hasClass( 'wpforms-smart-phone-field' ) ) {
254									element.parent().after( error );
255								} else {
256									error.insertAfter( element );
257								}
258							},
259							highlight: function( element, errorClass, validClass ) {
260								var $element  = $( element ),
261									$field    = $element.closest( '.wpforms-field' ),
262									inputName = $element.attr( 'name' );
263								if ( 'radio' === $element.attr( 'type' ) || 'checkbox' === $element.attr( 'type' ) ) {
264									$field.find( 'input[name=\'' + inputName + '\']' ).addClass( errorClass ).removeClass( validClass );
265								} else {
266									$element.addClass( errorClass ).removeClass( validClass );
267								}
268								$field.addClass( 'wpforms-has-error' );
269							},
270							unhighlight: function( element, errorClass, validClass ) {
271								var $element  = $( element ),
272									$field    = $element.closest( '.wpforms-field' ),
273									inputName = $element.attr( 'name' );
274								if ( 'radio' === $element.attr( 'type' ) || 'checkbox' === $element.attr( 'type' ) ) {
275									$field.find( 'input[name=\'' + inputName + '\']' ).addClass( validClass ).removeClass( errorClass );
276								} else {
277									$element.addClass( validClass ).removeClass( errorClass );
278								}
279								$field.removeClass( 'wpforms-has-error' );
280							},
281							submitHandler: function( form ) {
282
283								var $form       = $( form ),
284									$submit     = $form.find( '.wpforms-submit' ),
285									altText     = $submit.data( 'alt-text' ),
286									recaptchaID = $submit.get( 0 ).recaptchaID;
287
288								$submit.prop( 'disabled', true );
289								$form.find( '#wpforms-field_recaptcha-error' ).remove();
290
291								if ( ! app.empty( recaptchaID ) || recaptchaID === 0 ) {
292
293									// Form contains invisible reCAPTCHA.
294									grecaptcha.execute( recaptchaID ).then( null, function( reason ) {
295
296										reason = ( null === reason ) ? '' : '<br>' + reason;
297										$form.find( '.wpforms-recaptcha-container' ).append( '<label id="wpforms-field_recaptcha-error" class="wpforms-error"> ' + wpforms_settings.val_recaptcha_fail_msg + reason + '</label>' );
298										$submit.prop( 'disabled', false );
299									} );
300									return false;
301								}
302
303								// Normal form.
304								if ( altText ) {
305									$submit.text( altText );
306								}
307
308								// Remove name attributes if needed.
309								$( '.wpforms-input-temp-name' ).removeAttr( 'name' );
310
311								app.formSubmit( $form );
312							},
313							invalidHandler: function( event, validator ) {
314
315								if ( typeof validator.errorList[0] !== 'undefined' ) {
316									app.scrollToError( $( validator.errorList[0].element ) );
317								}
318							},
319							onkeyup: function( element, event ) {
320
321								// This code is copied from JQuery Validate 'onkeyup' method with only one change: 'wpforms-novalidate-onkeyup' class check.
322								var excludedKeys = [ 16, 17, 18, 20, 35, 36, 37, 38, 39, 40, 45, 144, 225 ];
323
324								if ( $( element ).hasClass( 'wpforms-novalidate-onkeyup' ) ) {
325									return; // Disable onkeyup validation for some elements (e.g. remote calls).
326								}
327
328								if ( 9 === event.which && '' === this.elementValue( element ) || $.inArray( event.keyCode, excludedKeys ) !== -1 ) {
329									return;
330								} else if ( element.name in this.submitted || element.name in this.invalid ) {
331									this.element( element );
332								}
333							},
334							onfocusout: function( element ) {
335
336								// This code is copied from JQuery Validate 'onfocusout' method with only one change: 'wpforms-novalidate-onkeyup' class check.
337								var validate = false;
338
339								if ( $( element ).hasClass( 'wpforms-novalidate-onkeyup' ) && ! element.value ) {
340									validate = true; // Empty value error handling for elements with onkeyup validation disabled.
341								}
342
343								if ( ! this.checkable( element ) && ( element.name in this.submitted || ! this.optional( element ) ) ) {
344									validate = true;
345								}
346
347								if ( validate ) {
348									this.element( element );
349								}
350							},
351							onclick: function( element ) {
352								var validate = false,
353									type = ( element || {} ).type,
354									$el = $( element );
355
356								if ( [ 'checkbox', 'radio' ].indexOf( type ) > -1 ) {
357									if ( $el.hasClass( 'wpforms-likert-scale-option' ) ) {
358										$el = $el.closest( 'tr' );
359									} else {
360										$el = $el.closest( '.wpforms-field' );
361									}
362									$el.find( 'label.wpforms-error' ).remove();
363									validate = true;
364								}
365
366								if ( validate ) {
367									this.element( element );
368								}
369							},
370						};
371					}
372					form.validate( properties );
373				} );
374			}
375		},
376
377		/**
378		 * Load jQuery Date Picker.
379		 *
380		 * @since 1.2.3
381		 */
382		loadDatePicker: function() {
383
384			// Only load if jQuery datepicker library exists.
385			if ( typeof $.fn.flatpickr !== 'undefined' ) {
386				$( '.wpforms-datepicker' ).each( function() {
387					var element = $( this ),
388						form    = element.closest( '.wpforms-form' ),
389						formID  = form.data( 'formid' ),
390						fieldID = element.closest( '.wpforms-field' ).data( 'field-id' ),
391						properties;
392
393					if ( typeof window['wpforms_' + formID + '_' + fieldID] !== 'undefined' && window['wpforms_' + formID + '_' + fieldID].hasOwnProperty( 'datepicker' ) ) {
394						properties = window['wpforms_' + formID + '_' + fieldID].datepicker;
395					} else if ( typeof window['wpforms_' + formID] !== 'undefined' && window['wpforms_' + formID].hasOwnProperty( 'datepicker' ) ) {
396						properties = window['wpforms_' + formID].datepicker;
397					} else if ( typeof wpforms_datepicker !== 'undefined' ) {
398						properties = wpforms_datepicker;
399					} else {
400						properties = {
401							disableMobile: true,
402						};
403					}
404
405					// Redefine locale only if user doesn't do that manually and we have the locale.
406					if (
407						! properties.hasOwnProperty( 'locale' ) &&
408						typeof wpforms_settings !== 'undefined' &&
409						wpforms_settings.hasOwnProperty( 'locale' )
410					) {
411						properties.locale = wpforms_settings.locale;
412					}
413
414					element.flatpickr( properties );
415				} );
416			}
417		},
418
419		/**
420		 * Load jQuery Time Picker.
421		 *
422		 * @since 1.2.3
423		 */
424		loadTimePicker: function() {
425
426			// Only load if jQuery timepicker library exists.
427			if ( typeof $.fn.timepicker !== 'undefined' ) {
428				$( '.wpforms-timepicker' ).each( function() {
429					var element = $( this ),
430						form    = element.closest( '.wpforms-form' ),
431						formID  = form.data( 'formid' ),
432						fieldID = element.closest( '.wpforms-field' ).data( 'field-id' ),
433						properties;
434
435					if (
436						typeof window['wpforms_' + formID + '_' + fieldID] !== 'undefined' &&
437						window['wpforms_' + formID + '_' + fieldID].hasOwnProperty( 't
437imepicker' )
438					) {
439						properties = window['wpforms_' + formID + '_' + fieldID].timepicker;
440					} else if (
441						typeof window['wpforms_' + formID] !== 'undefined' &&
442						window['wpforms_' + formID].hasOwnProperty( 'timepicker' )
443					) {
444						properties = window['wpforms_' + formID].timepicker;
445					} else if ( typeof wpforms_timepicker !== 'undefined' ) {
446						properties = wpforms_timepicker;
447					} else {
448						properties = {
449							scrollDefault: 'now',
450							forceRoundTime: true,
451						};
452					}
453
454					element.timepicker( properties );
455				} );
456			}
457		},
458
459		/**
460		 * Load jQuery input masks.
461		 *
462		 * @since 1.2.3
463		 */
464		loadInputMask: function() {
465
466			// Only load if jQuery input mask library exists.
467			if ( typeof $.fn.inputmask === 'undefined' ) {
468				return;
469			}
470
471			$( '.wpforms-masked-input' ).inputmask();
472		},
473
474		/**
475		 * Load smart phone field.
476		 *
477		 * @since 1.5.2
478		 */
479		loadSmartPhoneField: function() {
480
481			// Only load if library exists.
482			if ( typeof $.fn.intlTelInput === 'undefined' ) {
483				return;
484			}
485
486			var inputOptions = {};
487
488			// Determine the country by IP if no GDPR restrictions enabled.
489			if ( ! wpforms_settings.gdpr ) {
490				inputOptions.geoIpLookup = app.currentIpToCountry;
491			}
492
493			// Try to kick in an alternative solution if GDPR restrictions are enabled.
494			if ( wpforms_settings.gdpr ) {
495				var lang = this.getFirstBrowserLanguage(),
496					countryCode = lang.indexOf( '-' ) > -1 ? lang.split( '-' ).pop() : '';
497			}
498
499			// Make sure the library recognizes browser country code to avoid console error.
500			if ( countryCode ) {
501				var countryData = window.intlTelInputGlobals.getCountryData();
502
503				countryData = countryData.filter( function( country ) {
504					return country.iso2 === countryCode.toLowerCase();
505				} );
506				countryCode = countryData.length ? countryCode : '';
507			}
508
509			// Set default country.
510			inputOptions.initialCountry = wpforms_settings.gdpr && countryCode ? countryCode : 'auto';
511
512			$( '.wpforms-smart-phone-field' ).each( function( i, el ) {
513
514				var $el = $( el );
515
516				// Hidden input allows to include country code into submitted data.
517				inputOptions.hiddenInput = $el.closest( '.wpforms-field-phone' ).data( 'field-id' );
518				inputOptions.utilsScript = wpforms_settings.wpforms_plugin_url + 'pro/assets/js/vendor/jquery.intl-tel-input-utils.js';
519
520				$el.intlTelInput( inputOptions );
521
522				// Remove original input name not to interfere with a hidden input.
523				$el.removeAttr( 'name' );
524
525				// Instantly update a hidden form input with a correct data.
526				// Previously "blur" only was used, which is broken in case Enter was used to submit the form.
527				$el.on( 'blur keydown', function() {
528					if ( $el.intlTelInput( 'isValidNumber' ) ) {
529						$el.siblings( 'input[type="hidden"]' ).val( $el.intlTelInput( 'getNumber' ) );
530					}
531				} );
532			} );
533		},
534
535		/**
536		 * Payments: Do various payment-related tasks on load.
537		 *
538		 * @since 1.2.6
539		 */
540		loadPayments: function() {
541
542			// Update Total field(s) with latest calculation.
543			$( '.wpforms-payment-total' ).each( function( index, el ) {
544				app.amountTotal( this );
545			} );
546
547			// Credit card validation.
548			if ( typeof $.fn.payment !== 'undefined' ) {
549				$( '.wpforms-field-credit-card-cardnumber' ).payment( 'formatCardNumber' );
550				$( '.wpforms-field-credit-card-cardcvc' ).payment( 'formatCardCVC' );
551			}
552		},
553
554		/**
555		 * Load mailcheck.
556		 *
557		 * @since 1.5.3
558		 */
559		loadMailcheck: function() {
560
561			// Skip loading if `wpforms_mailcheck_enabled` filter return false.
562			if ( ! wpforms_settings.mailcheck_enabled ) {
563				return;
564			}
565
566			// Only load if library exists.
567			if ( typeof $.fn.mailcheck === 'undefined' ) {
568				return;
569			}
570
571			if ( wpforms_settings.mailcheck_domains.length > 0 ) {
572				Mailcheck.defaultDomains = Mailcheck.defaultDomains.concat( wpforms_settings.mailcheck_domains );
573			}
574			if ( wpforms_settings.mailcheck_toplevel_domains.length > 0 ) {
575				Mailcheck.defaultTopLevelDomains = Mailcheck.defaultTopLevelDomains.concat( wpforms_settings.mailcheck_toplevel_domains );
576			}
577
578			// Mailcheck suggestion.
579			$( document ).on( 'blur', '.wpforms-field-email input', function() {
580				var $t = $( this ),
581					id = $t.attr( 'id' );
582
583				$t.mailcheck( {
584					suggested: function( el, suggestion ) {
585						$( '#' + id + '_suggestion' ).remove();
586						var sugg = '<a href="#" class="mailcheck-suggestion" data-id="' + id + '" title="' + wpforms_settings.val_email_suggestion_title + '">' + suggestion.full + '</a>';
587						sugg = wpforms_settings.val_email_suggestion.replace( '{suggestion}', sugg );
588						$( el ).after( '<label class="wpforms-error mailcheck-error" id="' + id + '_suggestion">' + sugg + '</label>' );
589					},
590					empty: function() {
591						$( '#' + id + '_suggestion' ).remove();
592					},
593				} );
594			} );
595
596			// Apply Mailcheck suggestion.
597			$( document ).on( 'click', '.wpforms-field-email .mailcheck-suggestion', function( e ) {
598				var $t = $( this ),
599					id = $t.attr( 'data-id' );
600				e.preventDefault();
601				$( '#' + id ).val( $t.text() );
602				$t.parent().remove();
603			} );
604
605		},
606
607		//--------------------------------------------------------------------//
608		// Binds.
609		//--------------------------------------------------------------------//
610
611		/**
612		 * Element bindings.
613		 *
614		 * @since 1.2.3
615		 */
616		bindUIActions: function() {
617
618			// Pagebreak navigation.
619			$( document ).on( 'click', '.wpforms-page-button', function( event ) {
620				event.preventDefault();
621				app.pagebreakNav( this );
622			} );
623
624			// Payments: Update Total field(s) when latest calculation.
625			$( document ).on( 'change input', '.wpforms-payment-price', function() {
626				app.amountTotal( this, true );
627			} );
628
629			// Payments: Restrict user input payment fields.
630			$( document ).on( 'input', '.wpforms-payment-user-input', function() {
631				var $this = $( this ),
632					amount = $this.val();
633				$this.val( amount.replace( /[^0-9.,]/g, '' ) );
634			} );
635
636			// Payments: Sanitize/format user input amounts.
637			$( document ).on( 'focusout', '.wpforms-payment-user-input', function() {
638				var $this     = $( this ),
639					amount    = $this.val(),
640					sanitized = app.amountSanitize( amount ),
641					formatted = app.amountFormat( sanitized );
642				$this.val( formatted );
643			} );
644
645			// Payments: Update Total field(s) when conditials are processed.
646			$( document ).on( 'wpformsProcessConditionals', function( e, el ) {
647				app.amountTotal( el, true );
648			} );
649
650			// Payment radio/checkbox fields: preselect the selected payment (from dynamic/fallback population).
651			$( document ).ready( function() {
652
653				// Radios.
654				$( '.wpforms-field-radio .wpforms-image-choices-item input:checked' ).change();
655				$( '.wpforms-field-payment-multiple .wpforms-image-choices-item input:checked' ).change();
656
657				// Checkboxes.
658				$( '.wpforms-field-checkbox .wpforms-image-choices-item input' ).change();
659				$( '.wpforms-field-payment-checkbox .wpforms-image-choices-item input' ).change();
660			} );
661
662			// Rating field: hover effect.
663			$( '.wpforms-field-rating-item' ).hover(
664				function() {
665					$( this ).parent().find( '.wpforms-field-rating-item' ).removeClass( 'selected hover' );
666					$( this ).prevAll().andSelf().addClass( 'hover' );
667				},
668				function() {
669					$( this ).parent().find( '.wpforms-field-rating-item' ).removeClass( 'selected hover' );
670					$( this ).parent().find( 'input:checked' ).parent().prevAll().andSelf().addClass( 'selected' );
671				}
672			);
673
674			// Rating field: toggle selected state.
675			$( document ).on( 'change', '.wpforms-field-rating-item input', function() {
676
677				var $this  = $( this ),
678					$wrap  = $this.closest( '.wpforms-field-rating-items' ),
679					$items = $wrap.find( '.wpforms-field-rating-item' );
680
681				$items.removeClass( 'hover selected' );
682				$this.parent().prevAll().andSelf().addClass( 'selected' );
683			} );
684
685			// Rating field: preselect the selected rating (from dynamic/fallback population).
686			$( document ).ready( function() {
687				$( '.wpforms-field-rating-item input:checked' ).change();
688			} );
689
690			// Checkbox/Radio/Payment checkbox: make labels keyboard-accessible.
691			$( document ).on( 'keypress', '.wpforms-image-choices-item label', function( event ) {
692				var $this  = $( this ),
693					$field = $this.closest( '.wpforms-field' );
694
695				if ( $field.hasClass( 'wpforms-conditional-hide' ) ) {
696					event.preventDefault();
697					return false;
698				}
699
700				// Cause the input to be clicked when clicking the label.
701				if ( 13 === event.which ) {
702					$( '#' + $this.attr( 'for' ) ).click();
703				}
704			} );
705
706			// IE: Click on the `image choice` image should trigger the click event on the input (checkbox or radio) field.
707			$( document ).on( 'click', '.wpforms-image-choices-item img', function( e ) {
708
709				e.preventDefault();
710				$( this ).closest( 'label' ).find( 'input' ).click();
711			} );
712
713			$( document ).on( 'change', '.wpforms-field-checkbox input, .wpforms-field-radio input, .wpforms-field-payment-multiple input, .wpforms-field-payment-checkbox input, .wpforms-field-gdpr-checkbox input', function( event ) {
714
715				var $this  = $( this ),
716					$field = $this.closest( '.wpforms-field' );
717
718				if ( $field.hasClass( 'wpforms-conditional-hide' ) ) {
719					event.preventDefault();
720					return false;
721				}
722
723				switch ( $this.attr( 'type' ) ) {
724					case 'radio':
725						$this.closest( 'ul' ).find( 'li' ).removeClass( 'wpforms-selected' ).find( 'input[type=radio]' ).removeProp( 'checked' );
726						$this
727							.prop( 'checked', true )
728							.closest( 'li' ).addClass( 'wpforms-selected' );
729						break;
730
731					case 'checkbox':
732						if ( $this.is( ':checked' ) ) {
733							$this.closest( 'li' ).addClass( 'wpforms-selected' );
734							$this.prop( 'checked', true );
735						} else {
736							$this.closest( 'li' ).removeClass( 'wpforms-selected' );
737							$this.prop( 'checked', false );
738						}
739						break;
740				}
741			} );
742
743			// Upload fields: Check combined file size.
744			$( document ).on( 'change', '.wpforms-field-file-upload input[type=file]:not(".dropzone-input")', function() {
745				var $this       = $( this ),
746					$uploads    = $this.closest( 'form.wpforms-form' ).find( '.wpforms-field-file-upload input:not(".dropzone-input")' ),
747					totalSize   = 0,
748					postMaxSize = Number( wpforms_settings.post_max_size ),
749					errorMsg    = '<div class="wpforms-error-container-post_max_size">' + wpforms_settings.val_post_max_size + '</div>',
750					errorCntTpl = '<div class="wpforms-error-container">{errorMsg}</span></div>',
751					$submitCnt  = $this.closest( 'form.wpforms-form' ).find( '.wpforms-submit-container' ),
752					$submitBtn  = $submitCnt.find( 'button.wpforms-submit' ),
753					$errorCnt   = $submitCnt.prev();
754
755				// Calculating totalSize.
756				$uploads.each( function() {
757					var $upload = $( this ),
758						i = 0,
759						len = $upload[0].files.length;
760					for ( ; i < len; i++ ) {
761						totalSize += $upload[0].files[i].size;
762					}
763				} );
764
765				// Checking totalSize.
766				if ( totalSize > postMaxSize ) {
767
768					// Convert sizes to Mb.
769					totalSize = Number( ( totalSize / 1048576 ).toFixed( 3 ) );
770					postMaxSize = Number( ( postMaxSize / 1048576 ).toFixed( 3 ) );
771
772					// Preparing error message.
773					errorMsg = errorMsg.replace( /{totalSize}/, totalSize ).replace( /{maxSize}/, postMaxSize );
774
775					// Output error message.
776					if ( $errorCnt.hasClass( 'wpforms-error-container' ) ) {
777						$errorCnt.find( '.wpforms-error-container-post_max_size' ).remove();
778						$errorCnt.append( errorMsg );
779					} else {
780						$submitCnt.before( errorCntTpl.replace( /{errorMsg}/, errorMsg ) );
781					}
782
783					// Disable submit button.
784					$submitBtn.prop( 'disabled', true );
785				} else {
786
787					// Remove error and release submit button.
788					$errorCnt.find( '.wpforms-error-container-post_max_size' ).remove();
789					$submitBtn.prop( 'disabled', false );
790				}
791
792			} );
793
794			// Number Slider field: update hints.
795			$( document ).on( 'change input', '.wpforms-field-number-slider input[type=range]', function( event ) {
796				var hintEl = $( event.target ).siblings( '.wpforms-field-number-slider-hint' );
797
798				hintEl.html( hintEl.data( 'hint' ).replace( '{value}', '<b>' + event.target.value + '</b>' ) );
799			} );
800
801			// Enter key event.
802			$( document ).on( 'keydown', '.wpforms-form input', function( e ) {
803
804				if ( e.keyCode !== 13 ) {
805					return;
806				}
807
808				var $t = $( this ),
809					$page = $t.closest( '.wpforms-page' );
810
811				if ( $page.length === 0 ) {
812					return;
813				}
814
815				if ( [ 'text', 'tel', 'number', 'email', 'url', 'radio', 'checkbox' ].indexOf( $t.attr( 'type' ) ) < 0 ) {
816					return;
817				}
818
819				if ( $t.hasClass( 'wpforms-datepicker' ) ) {
820					$t.flatpickr( 'close' );
821				}
822
823				e.preventDefault();
824
825				if ( $page.hasClass( 'last' ) ) {
826					$page.closest( '.wpforms-form' ).find( '.wpforms-submit' ).click();
827					return;
828				}
829
830				$page.find( '.wpforms-page-next' ).click();
831			} );
832
833			// Allow only numbers, minus and decimal point to be entered into the Numbers field.
834			$( document ).on( 'input', '.wpforms-field-number input', function( e ) {
835				this.value = this.value.replace( /[^-0-9.]/g, '' );
836			} );
837		},
838
839		/**
840		 * Scroll to and focus on the field with error.
841		 *
842		 * @since 1.5.8
843		 *
844		 * @param {jQuery} $el Form, container or input element jQuery object.
845		 */
846		scrollToError: function( $el ) {
847
848			if ( $el.length === 0 ) {
849				return;
850			}
851
852			// Look for a field with an error inside an $el.
853			var $field = $el.find( '.wpforms-field.wpforms-has-error' );
854
855			// Look outside in not found inside.
856			if ( $field.length === 0 ) {
857				$field = $el.closest( '.wpforms-field' );
858			}
859
860			if ( $field.length === 0 ) {
861				return;
862			}
863
864			var offset = $field.offset();
865
866			if ( typeof offset === 'undefined' ) {
867				return;
868			}
869
870			app.animateScrollTop( offset.top - 75, 750 ).done( function() {
871				var $error = $field.find( '.wpforms-error' ).first();
872				if ( app.isFunction( $error.focus ) ) {
873					$error.focus();
874				}
875			} );
876		},
877
878		/**
879		 * Update Pagebreak navigation.
880		 *
881		 * @since 1.2.2
882		 *
883		 * @param {jQuery} el jQuery element object.
884		 */
885		pagebreakNav: function( el ) {
886
887			var $this      = $( el ),
888				valid      = true,
889				action     = $this.data( 'action' ),
890				page       = $this.data( 'page' ),
891				page2      = page,
892				next       = page + 1,
893				prev       = page - 1,
894				formID     = $this.data( 'formid' ),
895				$form      = $this.closest( '.wpforms-form' ),
896				$page      = $form.find( '.wpforms-page-' + page ),
897				$submit    = $form.find( '.wpforms-submit-container' ),
898				$indicator = $form.find( '.wpforms-page-indicator' ),
899				$reCAPTCHA = $form.find( '.wpforms-recaptcha-container' ),
900				pageScroll = false;
901
902			// Page scroll.
903			// TODO: cleanup this BC with wpform_pageScroll.
904			if ( false === window.wpforms_pageScroll ) {
905				pageScroll = false;
906			} else if ( ! app.empty( window.wpform_pageScroll ) ) {
907				pageScroll = window.wpform_pageScroll;
908			} else {
909				pageScroll = $indicator.attr( 'scroll' ) !== '0' ? 75 : false;
910			}
911
912			// Toggling between the pages.
913			if ( 'next' === action ) {
914
915				// Validate.
916				if ( typeof $.fn.validate !== 'undefined' ) {
917					$page.find( ':input' ).each( function( index, el ) {
918						if ( ! $( el ).valid() ) {
919							valid = false;
920						}
921					} );
922
923					// Scroll to first/top error on page.
924					app.scrollToError( $page );
925				}
926
927				// Move to the next page.
928				if ( valid ) {
929					page2 = next;
930					$page.hide();
931					var $nextPage = $form.find( '.wpforms-page-' + next );
932					$nextPage.show();
933					if ( $nextPage.hasClass( 'last' ) ) {
934						$reCAPTCHA.show();
935						$submit.show();
936					}
937					if ( pageScroll ) {
938
939						// Scroll to top of the form.
940						app.animateScrollTop( $form.offset().top - pageScroll, 750 );
941					}
942					$this.trigger( 'wpformsPageChange', [ page2, $form ] );
943				}
944			} else if ( 'prev' === action ) {
945
946				// Move to the prev page.
947				page2 = prev;
948				$page.hide();
949				$form.find( '.wpforms-page-' + prev ).show();
950				$reCAPTCHA.hide();
951				$submit.hide();
952				if ( pageScroll ) {
953
954					// Scroll to the top of the form.
955					app.animateScrollTop( $form.offset().top - pageScroll );
956				}
957				$this.trigger( 'wpformsPageChange', [ page2, $form ] );
958			}
959
960			if ( $indicator ) {
961				var theme = $indicator.data( 'indicator' ),
962					color = $indicator.data( 'indicator-color' );
963				if ( 'connector' === theme || 'circles' === theme ) {
964					$indicator.find( '.wpforms-page-indicator-page' ).removeClass( 'active' );
965					$indicator.find( '.wpforms-page-indicator-page-' + page2 ).addClass( 'active' );
966					$indicator.find( '.wpforms-page-indicator-page-number' ).removeAttr( 'style' );
967					$indicator.find( '.active .wpforms-page-indicator-page-number' ).css( 'background-color', color );
968					if ( 'connector' === theme ) {
969						$indicator.find( '.wpforms-page-indicator-page-triangle' ).removeAttr( 'style' );
970						$indicator.find( '.active .wpforms-page-indicator-page-triangle' ).css( 'border-top-color', color );
971					}
972				} else if ( 'progress' === theme ) {
973					var $pageTitle = $indicator.find( '.wpforms-page-indicator-page-title' ),
974						$pageSep   = $indicator.find( '.wpforms-page-indicator-page-title-sep' ),
975						totalPages = $form.find( '.wpforms-page' ).length,
976						width = ( page2 / totalPages ) * 100;
977					$indicator.find( '.wpforms-page-indicator-page-progress' ).css( 'width', width + '%' );
978					$indicator.find( '.wpforms-page-indicator-steps-current' ).text( page2 );
979					if ( $pageTitle.data( 'page-' + page2 + '-title' ) ) {
980						$pageTitle.css( 'display', 'inline' ).text( $pageTitle.data( 'page-' + page2 + '-title' ) );
981						$pageSep.css( 'display', 'inline' );
982					} else {
983						$pageTitle.css( 'display', 'none' );
984						$pageSep.css( 'display', 'none' );
985					}
986				}
987			}
988		},
989
990		/**
991		 * OptinMonster compatibility.
992		 *
993		 * Re-initialize after OptinMonster loads to accommodate changes that
994		 * have occurred to the DOM.
995		 *
996		 * @since 1.5.0
997		 */
998		bindOptinMonster: function() {
999
1000			// OM v5.
1001			document.addEventListener( 'om.Campaign.load', function( event ) {
1002				app.ready();
1003				app.optinMonsterRecaptchaReset( event.detail.Campaign.data.id );
1004			} );
1005
1006			// OM Legacy.
1007			$( document ).on( 'OptinMonsterOnShow', function( event, data, object ) {
1008				app.ready();
1009				app.optinMonsterRecaptchaReset( data.optin );
1010			} );
1011		},
1012
1013		/**
1014		 * Reset/recreate reCAPTCHA v2 inside OptinMonster.
1015		 *
1016		 * @since 1.5.0
1017		 */
1018		optinMonsterRecaptchaReset: function( optinId ) {
1019
1020			var $form               = $( '#om-' + optinId ).find( '.wpforms-form' ),
1021				$recaptchaContainer = $form.find( '.wpforms-recaptcha-container' ),
1022				$recaptcha          = $form.find( '.g-recaptcha' ),
1023				recaptchaSiteKey    = $recaptcha.attr( 'data-sitekey' ),
1024				recaptchaID         = 'recaptcha-' + Date.now();
1025
1026			if ( $form.length && $recaptcha.length ) {
1027
1028				$recaptcha.remove();
1029				$recaptchaContainer.prepend( '<div class="g-recaptcha" id="' + recaptchaID + '" data-sitekey="' + recaptchaSiteKey + '"></div>' );
1030
1031				grecaptcha.render(
1032					recaptchaID,
1033					{
1034						sitekey: recaptchaSiteKey,
1035						callback: function() {
1036							wpformsRecaptchaCallback( $( '#' + recaptchaID ) );
1037						},
1038					}
1039				);
1040			}
1041		},
1042
1043		//--------------------------------------------------------------------//
1044		// Other functions.
1045		//--------------------------------------------------------------------//
1046
1047		/**
1048		 * Payments: Calculate total.
1049		 *
1050		 * @since 1.2.3
1051		 * @since 1.5.1 Added support for payment-checkbox field.
1052		 */
1053		amountTotal: function( el, validate ) {
1054
1055			validate = validate || false;
1056
1057			var $form = $( el ).closest( '.wpforms-form' ),
1058				total = 0,
1059				totalFormatted,
1060				totalFormattedSymbol,
1061				currency = app.getCurrency();
1062
1063			$( '.wpforms-payment-price', $form ).each( function( index, el ) {
1064
1065				var amount = 0,
1066					$this = $( this );
1067
1068				if ( $this.closest( '.wpforms-field-payment-single' ).hasClass( 'wpforms-conditional-hide' ) ) {
1069					return;
1070				}
1071				if ( 'text' === $this.attr( 'type' ) || 'hidden' === $this.attr( 'type' ) ) {
1072					amount = $this.val();
1073				} else if ( ( 'radio' === $this.attr( 'type' ) || 'checkbox' === $this.attr( 'type' ) ) && $this.is( ':checked' ) ) {
1074					amount = $this.data( 'amount' );
1075				} else if ( $this.is( 'select' ) && $this.find( 'option:selected' ).length > 0 ) {
1076					amount = $this.find( 'option:selected' ).data( 'amount' );
1077				}
1078				if ( ! app.empty( amount ) ) {
1079					amount = app.amountSanitize( amount );
1080					total = Number( total ) + Number( amount );
1081				}
1082			} );
1083
1084			totalFormatted = app.amountFormat( total );
1085
1086			if ( 'left' === currency.symbol_pos ) {
1087				totalFormattedSymbol = currency.symbol + ' ' + totalFormatted;
1088			} else {
1089				totalFormattedSymbol = totalFormatted + ' ' + currency.symbol;
1090			}
1091
1092			$form.find( '.wpforms-payment-total' ).each( function( index, el ) {
1093				if ( 'hidden' === $( this ).attr( 'type' ) || 'text' === $( this ).attr( 'type' ) ) {
1094					$( this ).val( totalFormattedSymbol );
1095					if ( 'text' === $( this ).attr( 'type' ) && validate && $form.data( 'validator' ) ) {
1096						$( this ).valid();
1097					}
1098				} else {
1099					$( this ).text( totalFormattedSymbol );
1100				}
1101			} );
1102		},
1103
1104		/**
1105		 * Sanitize amount and convert to standard format for calculations.
1106		 *
1107		 * @since 1.2.6
1108		 */
1109		amountSanitize: function( amount ) {
1110
1111			var currency = app.getCurrency();
1112
1113			amount = amount.toString().replace( /[^0-9.,]/g, '' );
1114
1115			if ( ',' === currency.decimal_sep && ( amount.indexOf( currency.decimal_sep ) !== -1 ) ) {
1116				if ( '.' === currency.thousands_sep && amount.indexOf( currency.thousands_sep ) !== -1 ) {
1117					amount = amount.replace( currency.thousands_sep, '' );
1118				} else if ( '' === currency.thousands_sep && amount.indexOf( '.' ) !== -1 ) {
1119					amount = amount.replace( '.', '' );
1120				}
1121				amount = amount.replace( currency.decimal_sep, '.' );
1122			} else if ( ',' === currency.thousands_sep && ( amount.indexOf( currency.thousands_sep ) !== -1 ) ) {
1123				amount = amount.replace( currency.thousands_sep, '' );
1124			}
1125
1126			return app.numberFormat( amount, 2, '.', '' );
1127		},
1128
1129		/**
1130		 * Format amount.
1131		 *
1132		 * @since 1.2.6
1133		 */
1134		amountFormat: function( amount ) {
1135
1136			var currency = app.getCurrency();
1137
1138			amount = String( amount );
1139
1140			// Format the amount
1141			if ( ',' === currency.decimal_sep && ( amount.indexOf( currency.decimal_sep ) !== -1 ) ) {
1142				var sepFound = amount.indexOf( currency.decimal_sep ),
1143					whole    = amount.substr( 0, sepFound ),
1144					part     = amount.substr( sepFound + 1, amount.strlen - 1 );
1145				amount = whole + '.' + part;
1146			}
1147
1148			// Strip , from the amount (if set as the thousands separator)
1149			if ( ',' === currency.thousands_sep && ( amount.indexOf( currency.thousands_sep ) !== -1 ) ) {
1150				amount = amount.replace( ',', '' );
1151			}
1152
1153			if ( app.empty( amount ) ) {
1154				amount = 0;
1155			}
1156
1157			return app.numberFormat( amount, 2, currency.decimal_sep, currency.thousands_sep );
1158		},
1159
1160		/**
1161		 * Get site currency settings.
1162		 *
1163		 * @since 1.2.6
1164		 */
1165		getCurrency: function() {
1166
1167			var currency = {
1168				code: 'USD',
1169				thousands_sep: ',',
1170				decimal_sep: '.',
1171				symbol: '$',
1172				symbol_pos: 'left',
1173			};
1174
1175			// Backwards compatibility.
1176			if ( typeof wpforms_settings.currency_code !== 'undefined' ) {
1177				currency.code = wpforms_settings.currency_code;
1178			}
1179			if ( typeof wpforms_settings.currency_thousands !== 'undefined' ) {
1180				currency.thousands_sep = wpforms_settings.currency_thousands;
1181			}
1182			if ( typeof wpforms_settings.currency_decimal !== 'undefined' ) {
1183				currency.decimal_sep = wpforms_settings.currency_decimal;
1184			}
1185			if ( typeof wpforms_settings.currency_symbol !== 'undefined' ) {
1186				currency.symbol = wpforms_settings.currency_symbol;
1187			}
1188			if ( typeof wpforms_settings.currency_symbol_pos !== 'undefined' ) {
1189				currency.symbol_pos = wpforms_settings.currency_symbol_pos;
1190			}
1191
1192			return currency;
1193		},
1194
1195		/**
1196		 * Format number.
1197		 *
1198		 * @link http://locutus.io/php/number_format/
1199		 * @since 1.2.6
1200		 */
1201		numberFormat: function( number, decimals, decimalSep, thousandsSep ) {
1202
1203			number = ( number + '' ).replace( /[^0-9+\-Ee.]/g, '' );
1204			var n = ! isFinite( +number ) ? 0 : +number;
1205			var prec = ! isFinite( +decimals ) ? 0 : Math.abs( decimals );
1206			var sep = ( 'undefined' === typeof thousandsSep ) ? ',' : thousandsSep;
1207			var dec = ( 'undefined' === typeof decimalSep ) ? '.' : decimalSep;
1208			var s;
1209
1210			var toFixedFix = function( n, prec ) {
1211				var k = Math.pow( 10, prec );
1212				return '' + ( Math.round( n * k ) / k ).toFixed( prec );
1213			};
1214
1215			// @todo: for IE parseFloat(0.55).toFixed(0) = 0;
1216			s = ( prec ? toFixedFix( n, prec ) : '' + Math.round( n ) ).split( '.' );
1217			if ( s[0].length > 3 ) {
1218				s[0] = s[0].replace( /\B(?=(?:\d{3})+(?!\d))/g, sep );
1219			}
1220			if ( ( s[1] || '' ).length < prec ) {
1221				s[1] = s[1] || '';
1222				s[1] += new Array( prec - s[1].length + 1 ).join( '0' );
1223			}
1224
1225			return s.join( dec );
1226		},
1227
1228		/**
1229		 * Empty check similar to PHP.
1230		 *
1231		 * @link http://locutus.io/php/empty/
1232		 * @since 1.2.6
1233		 */
1234		empty: function( mixedVar ) {
1235
1236			var undef;
1237			var key;
1238			var i;
1239			var len;
1240			var emptyValues = [ undef, null, false, 0, '', '0' ];
1241
1242			for ( i = 0, len = emptyValues.length; i < len; i++ ) {
1243				if ( mixedVar === emptyValues[i] ) {
1244					return true;
1245				}
1246			}
1247
1248			if ( 'object' === typeof mixedVar ) {
1249				for ( key in mixedVar ) {
1250					if ( mixedVar.hasOwnProperty( key ) ) {
1251						return false;
1252					}
1253				}
1254				return true;
1255			}
1256
1257			return false;
1258		},
1259
1260		/**
1261		 * Set cookie container user UUID.
1262		 *
1263		 * @since 1.3.3
1264		 */
1265		setUserIndentifier: function() {
1266
1267			if ( ( ( ! window.hasRequiredConsent && typeof wpforms_settings !== 'undefined' && wpforms_settings.uuid_cookie ) || ( window.hasRequiredConsent && window.hasRequiredConsent() ) ) && ! app.getCookie( '_wpfuuid' ) ) {
1268
1269				// Generate UUID - http://stackoverflow.com/a/873856/1489528
1270				var s         = new Array( 36 ),
1271					hexDigits = '0123456789abcdef',
1272					uuid;
1273
1274				for ( var i = 0; i < 36; i++ ) {
1275					s[i] = hexDigits.substr( Math.floor( Math.random() * 0x10 ), 1 );
1276				}
1277				s[14] = '4';
1278				s[19] = hexDigits.substr( ( s[19] & 0x3 ) | 0x8, 1 );
1279				s[8]  = s[13] = s[18] = s[23] = '-';
1280
1281				uuid = s.join( '' );
1282
1283				app.createCookie( '_wpfuuid', uuid, 3999 );
1284			}
1285		},
1286
1287		/**
1288		 * Create cookie.
1289		 *
1290		 * @since 1.3.3
1291		 */
1292		createCookie: function( name, value, days ) {
1293
1294			var expires = '';
1295
1296			// If we have a days value, set it in the expiry of the cookie.
1297			if ( days ) {
1298
1299				// If -1 is our value, set a session based cookie instead of a persistent cookie.
1300				if ( '-1' === days ) {
1301					expires = '';
1302				} else {
1303					var date = new Date();
1304					date.setTime( date.getTime() + ( days * 24 * 60 * 60 * 1000 ) );
1305					expires = '; expires=' + date.toGMTString();
1306				}
1307			} else {
1308				expires = '; expires=Thu, 01 Jan 1970 00:00:01 GMT';
1309			}
1310
1311			// Write the cookie.
1312			document.cookie = name + '=' + value + expires + '; path=/';
1313		},
1314
1315		/**
1316		 * Retrieve cookie.
1317		 *
1318		 * @since 1.3.3
1319		 */
1320		getCookie: function( name ) {
1321
1322			var nameEQ = name + '=',
1323				ca     = document.cookie.split( ';' );
1324
1325			for ( var i = 0; i < ca.length; i++ ) {
1326				var c = ca[i];
1327				while ( ' ' === c.charAt( 0 ) ) {
1328					c = c.substring( 1, c.length );
1329				}
1330				if ( 0 == c.indexOf( nameEQ ) ) {
1331					return c.substring( nameEQ.length, c.length );
1332				}
1333			}
1334
1335			return null;
1336		},
1337
1338		/**
1339		 * Delete cookie.
1340		 */
1341		removeCookie: function( name ) {
1342
1343			app.createCookie( name, '', -1 );
1344		},
1345
1346		/**
1347		 * Get user browser preferred language.
1348		 *
1349		 * @since 1.5.2
1350		 *
1351		 * @returns {String} Language code.
1352		 */
1353		getFirstBrowserLanguage: function() {
1354			var nav = window.navigator,
1355				browserLanguagePropertyKeys = [ 'language', 'browserLanguage', 'systemLanguage', 'userLanguage' ],
1356				i,
1357				language;
1358
1359			// Support for HTML 5.1 "navigator.languages".
1360			if ( Array.isArray( nav.languages ) ) {
1361				for ( i = 0; i < nav.languages.length; i++ ) {
1362					language = nav.languages[ i ];
1363					if ( language && language.length ) {
1364						return language;
1365					}
1366				}
1367			}
1368
1369			// Support for other well known properties in browsers.
1370			for ( i = 0; i < browserLanguagePropertyKeys.length; i++ ) {
1371				language = nav[ browserLanguagePropertyKeys[ i ] ];
1372				if ( language && language.length ) {
1373					return language;
1374				}
1375			}
1376
1377			return '';
1378		},
1379
1380		/**
1381		 * Asynchronously fetches country code using current IP
1382		 * and executes a callback provided with a country code parameter.
1383		 *
1384		 * @since 1.5.2
1385		 *
1386		 * @param {Function} callback Executes once the fetch is completed.
1387		 */
1388		currentIpToCountry: function( callback ) {
1389
1390			$.get( 'https://geo.wpforms.com/v2/geolocate/json/' )
1391				.done( function( resp ) {
1392					if ( resp && resp.country_code ) {
1393						callback( resp.country_code );
1394					} else {
1395						fallback();
1396					}
1397				} )
1398				.fail( function( resp ) {
1399					fallback();
1400				} );
1401
1402			var fallback = function() {
1403
1404				$.get( 'https://ipapi.co/jsonp', function() {}, 'jsonp' )
1405					 .always( function( resp ) {
1406						 var countryCode = ( resp && resp.country ) ? resp.country : '';
1407						 if ( ! countryCode ) {
1408							 var lang = app.getFirstBrowserLanguage();
1409							 countryCode = lang.indexOf( '-' ) > -1 ? lang.split( '-' ).pop() : '';
1410						 }
1411						 callback( countryCode );
1412					 } );
1413			};
1414		},
1415
1416		/**
1417		 * Form submit.
1418		 *
1419		 * @since 1.5.3
1420		 *
1421		 * @param {jQuery} $form Form element.
1422		 */
1423		formSubmit: function( $form ) {
1424
1425			if ( $form.hasClass( 'wpforms-ajax-form' ) && typeof FormData !== 'undefined' ) {
1426				app.formSubmitAjax( $form );
1427			} else {
1428				app.formSubmitNormal( $form );
1429			}
1430		},
1431
1432		/**
1433		 * Normal form submit with page reload.
1434		 *
1435		 * @since 1.5.3
1436		 *
1437		 * @param {jQuery} $form Form element.
1438		 */
1439		formSubmitNormal: function( $form ) {
1440
1441			if ( ! $form.length ) {
1442				return;
1443			}
1444
1445			var $submit     = $form.find( '.wpforms-submit' ),
1446				recaptchaID = $submit.get( 0 ).recaptchaID;
1447
1448			if ( ! app.empty( recaptchaID ) || recaptchaID === 0 ) {
1449				$submit.get( 0 ).recaptchaID = false;
1450			}
1451
1452			$form.get( 0 ).submit();
1453		},
1454
1455		/**
1456		 * Reset form recaptcha.
1457		 *
1458		 * @since 1.5.3
1459		 *
1460		 * @param {jQuery} $form Form element.
1461		 */
1462		resetFormRecaptcha: function( $form ) {
1463
1464			if ( ! $form || ! $form.length ) {
1465				return;
1466			}
1467
1468			if ( typeof grecaptcha === 'undefined' ) {
1469				return;
1470			}
1471
1472			var recaptchaID;
1473
1474			// Check for invisible recaptcha first.
1475			recaptchaID = $form.find( '.wpforms-submit' ).get( 0 ).recaptchaID;
1476
1477			// Check for v2 recaptcha if invisible recaptcha is not found.
1478			if ( app.empty( recaptchaID ) && recaptchaID !== 0 ) {
1479				recaptchaID = $form.find( '.g-recaptcha' ).data( 'recaptcha-id' );
1480			}
1481
1482			// Reset recaptcha.
1483			if ( ! app.empty( recaptchaID ) || recaptchaID === 0 ) {
1484				grecaptcha.reset( recaptchaID );
1485			}
1486		},
1487
1488		/**
1489		 * Console log AJAX error.
1490		 *
1491		 * @since 1.5.3
1492		 *
1493		 * @param {string} error Error text (optional).
1494		 */
1495		consoleLogAjaxError: function( error ) {
1496
1497			if ( error ) {
1498				console.error( 'WPForms AJAX submit error:\n%s', error ); // eslint-disable-line no-console
1499			} else {
1500				console.error( 'WPForms AJAX submit error' ); // eslint-disable-line no-console
1501			}
1502		},
1503
1504		/**
1505		 * Display form AJAX errors.
1506		 *
1507		 * @since 1.5.3
1508		 *
1509		 * @param {jQuery} $form Form element.
1510		 * @param {object} errors Errors in format { general: { generalErrors }, field: { fieldErrors } }.
1511		 */
1512		displayFormAjaxErrors: function( $form, errors ) {
1513
1514			if ( 'string' === typeof errors ) {
1515				app.displayFormAjaxGeneralErrors( $form, errors );
1516				return;
1517			}
1518
1519			errors = 'errors' in errors ? errors.errors : null;
1520
1521			if ( app.empty( errors ) || ( app.empty( errors.general ) && app.empty( errors.field ) ) ) {
1522				app.consoleLogAjaxError();
1523				return;
1524			}
1525
1526			if ( ! app.empty( errors.general ) ) {
1527				app.displayFormAjaxGeneralErrors( $form, errors.general );
1528			}
1529
1530			if ( ! app.empty( errors.field ) ) {
1531				app.displayFormAjaxFieldErrors( $form, errors.field );
1532			}
1533		},
1534
1535		/**
1536		 * Display form AJAX general errors that cannot be displayed using jQuery Validation plugin.
1537		 *
1538		 * @since 1.5.3
1539		 *
1540		 * @param {jQuery} $form Form element.
1541		 * @param {object} errors Errors in format { errorType: errorText }.
1542		 */
1543		displayFormAjaxGeneralErrors: function( $form, errors ) {
1544
1545			if ( ! $form || ! $form.length ) {
1546				return;
1547			}
1548
1549			if ( app.empty( errors ) ) {
1550				return;
1551			}
1552
1553			// Safety net for random errors thrown by a third-party code. Should never be used intentionally.
1554			if ( 'string' === typeof errors ) {
1555				$form.find( '.wpforms-submit-container' ).before( '<div class="wpforms-error-container">' + errors + '</div>' );
1556				return;
1557			}
1558
1559			$.each( errors, function( type, html ) {
1560				switch ( type ) {
1561					case 'header':
1562						$form.prepend( html );
1563						break;
1564					case 'footer':
1565						$form.find( '.wpforms-submit-container' ).before( html );
1566						break;
1567					case 'recaptcha':
1568						$form.find( '.wpforms-recaptcha-container' ).append( html );
1569						break;
1570				}
1571			} );
1572		},
1573
1574		/**
1575		 * Clear forms AJAX general errors that cannot be cleared using jQuery Validation plugin.
1576		 *
1577		 * @since 1.5.3
1578		 *
1579		 * @param {jQuery} $form Form element.
1580		 */
1581		clearFormAjaxGeneralErrors: function( $form ) {
1582
1583			$form.find( '.wpforms-error-container' ).remove();
1584			$form.find( '#wpforms-field_recaptcha-error' ).remove();
1585		},
1586
1587		/**
1588		 * Display form AJAX field errors using jQuery Validation plugin.
1589		 *
1590		 * @since 1.5.3
1591		 *
1592		 * @param {jQuery} $form Form element.
1593		 * @param {object} errors Errors in format { fieldName: errorText }.
1594		 */
1595		displayFormAjaxFieldErrors: function( $form, errors ) {
1596
1597			if ( ! $form || ! $form.length ) {
1598				return;
1599			}
1600
1601			if ( app.empty( errors ) ) {
1602				return;
1603			}
1604
1605			var validator = $form.data( 'validator' );
1606
1607			if ( ! validator ) {
1608				return;
1609			}
1610
1611			validator.showErrors( errors );
1612			validator.focusInvalid();
1613		},
1614
1615		/**
1616		 * Submit a form using AJAX.
1617		 *
1618		 * @since 1.5.3
1619		 *
1620		 * @param {jQuery} $form Form element.
1621		 *
1622		 * @returns {JQueryXHR|JQueryDeferred} Promise like object for async callbacks.
1623		 */
1624		formSubmitAjax: function( $form ) {
1625
1626			if ( ! $form.length ) {
1627				return $.Deferred().reject(); // eslint-disable-line new-cap
1628			}
1629
1630			var $container = $form.closest( '.wpforms-container' ),
1631				$spinner = $form.find( '.wpforms-submit-spinner' ),
1632				$confirmationScroll,
1633				formData,
1634				args;
1635
1636			$container.css( 'opacity', 0.6 );
1637			$spinner.show();
1638
1639			app.clearFormAjaxGeneralErrors( $form );
1640
1641			formData = new FormData( $form.get( 0 ) );
1642			formData.append( 'action', 'wpforms_submit' );
1643			formData.append( 'page_url', window.location.href );
1644
1645			args = {
1646				type       : 'post',
1647				dataType   : 'json',
1648				url        : wpforms_settings.ajaxurl,
1649				data       : formData,
1650				cache      : false,
1651				contentType: false,
1652				processData: false,
1653			};
1654
1655			args.success = function( json ) {
1656
1657				if ( ! json ) {
1658					app.consoleLogAjaxError();
1659					return;
1660				}
1661
1662				if ( json.data && json.data.action_required ) {
1663					$form.trigger( 'wpformsAjaxSubmitActionRequired', json );
1664					return;
1665				}
1666
1667				if ( ! json.success ) {
1668					app.resetFormRecaptcha( $form );
1669					app.displayFormAjaxErrors( $form, json.data );
1670					$form.trigger( 'wpformsAjaxSubmitFailed', json );
1671					return;
1672				}
1673
1674				$form.trigger( 'wpformsAjaxSubmitSuccess', json );
1675
1676				if ( ! json.data ) {
1677					return;
1678				}
1679
1680				if ( json.data.redirect_url ) {
1681					$form.trigger( 'wpformsAjaxSubmitBeforeRedirect', json );
1682					window.location = json.data.redirect_url;
1683					return;
1684				}
1685
1686				if ( json.data.confirmation ) {
1687					$container.html( json.data.confirmation );
1688					$confirmationScroll = $container.find( 'div.wpforms-confirmation-scroll' );
1689					if ( $confirmationScroll.length ) {
1690						app.animateScrollTop( $confirmationScroll.offset().top - 100 );
1691					}
1692				}
1693			};
1694
1695			args.error = function( jqHXR, textStatus, error ) {
1696
1697				app.consoleLogAjaxError( error );
1698
1699				$form.trigger( 'wpformsAjaxSubmitError', [ jqHXR, textStatus, error ] );
1700			};
1701
1702			args.complete = function( jqHXR, textStatus ) {
1703
1704				// Do not make form active if the action is required.
1705				if ( jqHXR.responseJSON && jqHXR.responseJSON.data && jqHXR.responseJSON.data.action_required ) {
1706					return;
1707				}
1708
1709				var $submit     = $form.find( '.wpforms-submit' ),
1710					submitText  = $submit.data( 'submit-text' );
1711
1712				if ( submitText ) {
1713					$submit.text( submitText ).prop( 'disabled', false );
1714				}
1715
1716				$container.css( 'opacity', '' );
1717				$spinner.hide();
1718
1719				$form.trigger( 'wpformsAjaxSubmitCompleted', [ jqHXR, textStatus ] );
1720			};
1721
1722			$form.trigger( 'wpformsAjaxBeforeSubmit' );
1723
1724			return $.ajax( args );
1725		},
1726
1727		/**
1728		 * Scroll to position with animation.
1729		 *
1730		 * @since 1.5.3
1731		 *
1732		 * @param {number} position Position (in pixels) to scroll to,
1733		 * @param {number} duration Animation duration.
1734		 * @param {Function} complete Function to execute after animation is complete.
1735		 *
1736		 * @returns {JQueryPromise} Promise object for async callbacks.
1737		 */
1738		animateScrollTop: function( position, duration, complete ) {
1739
1740			duration = duration || 1000;
1741			complete = app.isFunction( complete ) ? complete : function() {};
1742			return $( 'html, body' ).animate( { scrollTop: parseInt( position, 10 ) }, { duration: duration, complete: complete } ).promise();
1743		},
1744
1745		/**
1746		 * Check if object is a function.
1747		 *
1748		 * @since 1.5.8
1749		 *
1750		 * @param {mixed} object Object to check if it is function.
1751		 *
1752		 * @returns {boolean} True if object is a function.
1753		 */
1754		isFunction: function( object ) {
1755
1756			return !! ( object && object.constructor && object.call && object.apply );
1757		},
1758	};
1759
1760	return app;
1761
1762}( document, window, jQuery ) );
1763
1764// Initialize.
1765wpforms.init();

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.