PageSourceSearch

http://plugstar.org/wp-content/plugins/wpforms-lite/assets/js/wpforms.js?ver=1.6.0.2

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