/* global wpforms_settings, grecaptcha, hcaptcha, turnstile, wpformsRecaptchaCallback, wpformsRecaptchaV3Execute, wpforms_validate, wpforms_datepicker, wpforms_timepicker, Mailcheck, Choices, WPFormsPasswordField, WPFormsEntryPreview, punycode, tinyMCE, WPFormsUtils, JQueryDeferred, JQueryXHR, WPFormsRepeaterField, WPFormsPhoneField */
/* eslint-disable no-unused-expressions, no-shadow, no-unused-vars */
/**
* @param wpforms_settings.hn_data
*/
// noinspection ES6ConvertVarToLetConst
/**
* WPForms object.
*
* @since 1.4.0
*/
var wpforms = window.wpforms || ( function( document, window, $ ) { // eslint-disable-line no-var
/**
* Public functions and properties.
*
* @since 1.8.9
*
* @type {Object}
*/
const app = {
/**
* Cache.
*
* @since 1.8.5
*/
cache: {},
/**
* Is updating token via ajax flag.
*
* @since 1.8.8
*/
isUpdatingToken: false,
/**
* Start the engine.
*
* @since 1.2.3
*/
init() {
// Document ready.
$( app.ready );
// Page load.
$( window ).on( 'load', function() {
// In the case of jQuery 3.+, we need to wait for a ready event first.
if ( typeof $.ready.then === 'function' ) {
$.ready.then( app.load );
} else {
app.load();
}
} );
app.bindUIActions();
app.bindOptinMonster();
},
/**
* Document ready.
*
* @since 1.2.3
*/
ready() {
// Clear URL - remove wpforms_form_id.
app.clearUrlQuery();
// Set user identifier.
app.setUserIdentifier();
app.loadValidation();
app.loadHoneypot();
app.loadDatePicker();
app.loadTimePicker();
app.loadInputMask();
app.loadPayments();
app.loadMailcheck();
app.loadChoicesJS();
app.initTokenUpdater();
app.restoreSubmitButtonOnEventPersisted();
app.bindChoicesJS();
// Randomize elements.
$( '.wpforms-randomize' ).each( function() {
const $list = $( this ),
$listItems = $list.children();
while ( $listItems.length ) {
$list.append( $listItems.splice( Math.floor( Math.random() * $listItems.length ), 1 )[ 0 ] );
}
} );
// Unlock pagebreak navigation.
$( '.wpforms-page-button' ).prop( 'disabled', false );
// Init forms' start timestamp.
app.initFormsStartTime();
$( document ).trigger( 'wpformsReady' );
},
/**
* Page load.
*
* @since 1.2.3
*/
load() {
},
//--------------------------------------------------------------------//
// Initializing
//--------------------------------------------------------------------//
/**
* Remove wpforms_form_id from URL.
*
* @since 1.5.2
*/
clearUrlQuery() {
const loc = window.location;
let query = loc.search;
if ( query.indexOf( 'wpforms_form_id=' ) !== -1 ) {
query = query.replace( /([&?]wpforms_form_id=[0-9]*$|wpforms_form_id=[0-9]*&|[?&]wpforms_form_id=[0-9]*(?=#))/, '' );
history.replaceState( {}, null, loc.origin + loc.pathname + query );
}
},
/**
* Load honeypot v2 field.
*
* @since 1.9.0
*/
loadHoneypot() {
$( '.wpforms-form' ).each( function() {
const $form = $( this ),
formId = $form.data( 'formid' ),
fieldIds = [],
fieldLabels = [];
// Bail early if honeypot protection is disabled for the form.
if ( wpforms_settings.hn_data[ formId ] === undefined ) {
return;
}
// Collect all field IDs and labels.
$( `#wpforms-form-${ formId } .wpforms-field` ).each( function() {
const $field = $( this );
fieldIds.push( $field.data( 'field-id' ) );
fieldLabels.push( $field.find( '.wpforms-field-label' ).text() );
} );
const label = app.getHoneypotRandomLabel( fieldLabels.join( ' ' ).split( ' ' ) ),
honeypotFieldId = app.getHoneypotFieldId( fieldIds );
// Insert the honeypot field before a random field.
const insertBeforeId = fieldIds[ Math.floor( Math.random() * fieldIds.length ) ],
honeypotIdAttr = `wpforms-${ formId }-field_${ honeypotFieldId }`,
$insertBeforeField = $( `#wpforms-${ formId }-field_${ insertBeforeId }-container`, $form ),
inlineStyles = 'position: absolute !important; overflow: hidden !important; display: inline !important; height: 1px !important; width: 1px !important; z-index: -1000 !important; padding: 0 !important;',
labelInlineStyles = 'counter-increment: none;',
fieldHTML = `
`;
$insertBeforeField.before( fieldHTML );
// Add inline properties for honeypot field on the form.
const $fieldContainer = $( `#wpforms-${ formId }-field_${ wpforms_settings.hn_data[ formId ] }-container`, $form );
$fieldContainer.find( 'input' ).attr( {
tabindex: '-1',
'aria-hidden': 'true',
} );
$fieldContainer.find( 'label' ).text( label ).attr( 'aria-hidden', 'true' );
} );
},
/**
* Generate random Honeypot label.
*
* @since 1.9.0
*
* @param {Array} words List of words.
*
* @return {string} Honeypot label.
*/
getHoneypotRandomLabel( words ) {
let label = '';
for ( let i = 0; i < 3; i++ ) {
label += words[ Math.floor( Math.random() * words.length ) ] + ' ';
}
return label.trim();
},
/**
* Get Honeypot field ID.
*
* @since 1.9.0
*
* @param {Array} fieldIds List of the form field IDs.
*
* @return {number} Honeypot field ID.
*/
getHoneypotFieldId( fieldIds ) {
const maxId = Math.max( ...fieldIds );
let honeypotFieldId = 0;
// Find the first available field ID.
for ( let i = 1; i < maxId; i++ ) {
if ( ! fieldIds.includes( i ) ) {
honeypotFieldId = i;
break;
}
}
// If no available field ID found, use the max ID + 1.
if ( ! honeypotFieldId ) {
honeypotFieldId = maxId + 1;
}
return honeypotFieldId;
},
/**
* Load jQuery Validation.
*
* @since 1.2.3
*/
loadValidation() { // eslint-disable-line max-lines-per-function
// Only load if jQuery validation library exists.
if ( typeof $.fn.validate === 'undefined' ) {
if ( window.location.hash && '#wpformsdebug' === window.location.hash ) {
// eslint-disable-next-line no-console
console.log( 'jQuery Validation library not found.' );
}
return;
}
// jQuery Validation library will not correctly validate
// fields that do not have a name attribute, so we use the
// `wpforms-input-temp-name` class to add a temporary name
// attribute before validation is initialized, then remove it
// before the form submits.
$( '.wpforms-input-temp-name' ).each( function( index, el ) {
const random = Math.floor( Math.random() * 9999 ) + 1;
$( this ).attr( 'name', 'wpf-temp-' + random );
} );
// Prepend URL field contents with https:// if user input doesn't contain a schema.
$( document ).on( 'change', '.wpforms-validate input[type=url]', function() {
const url = $( this ).val();
if ( ! url ) {
return false;
}
if ( url.substr( 0, 7 ) !== 'http://' && url.substr( 0, 8 ) !== 'https://' ) {
$( this ).val( 'https://' + url );
}
} );
$.validator.messages.required = wpforms_settings.val_required;
$.validator.messages.url = wpforms_settings.val_url;
$.validator.messages.email = wpforms_settings.val_email;
$.validator.messages.number = wpforms_settings.val_number;
// Payments: Validate method for Credit Card Number.
if ( typeof $.fn.payment !== 'undefined' ) {
$.validator.addMethod( 'creditcard', function( value, element ) {
//var type = $.payment.cardType(value);
const valid = $.payment.validateCardNumber( value );
return this.optional( element ) || valid;
}, wpforms_settings.val_creditcard );
// @todo validate CVC and expiration
}
// Validate method for file extensions.
$.validator.addMethod( 'extension', function( value, element, param ) {
param = 'string' === typeof param ? param.replace( /,/g, '|' ) : 'png|jpe?g|gif';
return this.optional( element ) || value.match( new RegExp( '\\.(' + param + ')$', 'i' ) );
}, wpforms_settings.val_fileextension );
// Validate method for file size.
$.validator.addMethod( 'maxsize', function( value, element, param ) {
const maxSize = param,
optionalValue = this.optional( element );
let i, len, file;
if ( optionalValue ) {
return optionalValue;
}
if ( element.files && element.files.length ) {
i = 0;
len = element.files.length;
for ( ; i < len; i++ ) {
file = element.files[ i ];
if ( file.size > maxSize ) {
return false;
}
}
}
return true;
}, wpforms_settings.val_filesize );
$.validator.addMethod( 'step', function( value, element, param ) {
const decimalPlaces = function( num ) {
if ( Math.floor( num ) === num ) {
return 0;
}
return num.toString().split( '.' )[ 1 ].length || 0;
};
const decimals = decimalPlaces( param );
const decimalToInt = function( num ) {
return Math.round( num * Math.pow( 10, decimals ) );
};
const min = decimalToInt( $( element ).attr( 'min' ) );
value = decimalToInt( value ) - min;
return this.optional( element ) || decimalToInt( value ) % decimalToInt( param ) === 0;
} );
// Validate email addresses.
$.validator.methods.email = function( value, element ) {
/**
* This function combines is_email() from WordPress core
* and wpforms_is_email() to validate email addresses.
*
* @see https://developer.wordpress.org/reference/functions/is_email/
* @see https://github.com/awesomemotive/wpforms-plugin/blob/develop/wpforms/includes/functions/checks.php#L45
*
* @param {string} value The email address to validate.
*
* @return {boolean} True if the email address is valid, false otherwise.
*/
const isEmail = function( value ) { // eslint-disable-line complexity
if ( typeof value !== 'string' ) {
// Do not allow callables, arrays, and objects.
return false;
}
// Check the length and position of the @ character.
const atIndex = value.indexOf( '@', 1 );
if ( value.length < 6 || value.length > 254 || atIndex === -1 ) {
return false;
}
// Check for more than one "@" symbol.
if ( value.indexOf( '@', atIndex + 1 ) !== -1 ) {
return false;
}
// Split email address into local and domain parts.
const [ local, domain ] = value.split( '@' );
// Check local and domain parts for existence.
if ( ! local || ! domain ) {
return false;
}
// Check local part for invalid characters and length.
const localRegex = /^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+$/;
if ( ! localRegex.test( local ) || local.length > 63 ) {
return false;
}
// Check domain part for sequences of periods, leading and trailing periods, and whitespace.
const domainRegex = /\.{2,}/;
if ( domainRegex.test( domain ) || domain.trim( ' \t\n\r\0\x0B.' ) !== domain ) {
return false;
}
// Check domain part for length.
const domainArr = domain.split( '.' );
if ( domainArr.length < 2 ) {
return false;
}
// Check domain label for length, leading and trailing periods, and whitespace.
const domainLabelRegex = /^[a-z0-9-]+$/i;
for ( const domainLabel of domainArr ) {
if (
domainLabel.length > 63 ||
domainLabel.trim( ' \t\n\r\0\x0B-' ) !== domainLabel ||
! domainLabelRegex.test( domainLabel )
) {
return false;
}
}
return true;
};
// Congratulations! The email address is valid.
return this.optional( element ) || isEmail( value );
};
// Validate email by allowlist/blocklist.
$.validator.addMethod( 'restricted-email', function( value, element ) {
const $el = $( element );
if ( ! $el.val().length ) {
return true;
}
const $form = $el.closest( '.wpforms-form' ),
formId = $form.data( 'formid' );
if (
! Object.prototype.hasOwnProperty.call( app.cache, formId ) ||
! Object.prototype.hasOwnProperty.call( app.cache[ formId ], 'restrictedEmailValidation' ) ||
! Object.prototype.hasOwnProperty.call( app.cache[ formId ].restrictedEmailValidation, value )
) {
app.restrictedEmailRequest( element, value );
return 'pending';
}
return app.cache[ formId ].restrictedEmailValidation[ value ];
}, wpforms_settings.val_email_restricted );
// Validate confirmations.
$.validator.addMethod( 'confirm', function( value, element, param ) {
const field = $( element ).closest( '.wpforms-field' );
return $( field.find( 'input' )[ 0 ] ).val() === $( field.find( 'input' )[ 1 ] ).val();
}, wpforms_settings.val_confirm );
// Validate required payments.
$.validator.addMethod( 'required-payment', function( value, element ) {
return app.amountSanitize( value ) > 0;
}, wpforms_settings.val_requiredpayment );
// Validate 12-hour time.
$.validator.addMethod( 'time12h', function( value, element ) {
// noinspection RegExpRedundantEscape
return this.optional( element ) || /^((0?[1-9]|1[012])(:[0-5]\d){1,2}(\ ?[AP]M))$/i.test( value ); // eslint-disable-line no-useless-escape
}, wpforms_settings.val_time12h );
// Validate 24-hour time.
$.validator.addMethod( 'time24h', function( value, element ) {
// noinspection RegExpRedundantEscape
return this.optional( element ) || /^(([0-1]?[0-9])|([2][0-3])):([0-5]?[0-9])(\ ?[AP]M)?$/i.test( value ); // eslint-disable-line no-useless-escape
}, wpforms_settings.val_time24h );
// Validate Turnstile captcha.
$.validator.addMethod( 'turnstile', function( value ) {
return value;
}, wpforms_settings.val_turnstile_fail_msg );
// Validate time limits.
$.validator.addMethod( 'time-limit', function( value, element ) { // eslint-disable-line complexity
const $input = $( element ),
minTime = $input.data( 'min-time' ),
isLimited = typeof minTime !== 'undefined';
if ( ! isLimited ) {
return true;
}
const isRequired = $input.prop( 'required' );
if ( ! isRequired && app.empty( value ) ) {
return true;
}
const maxTime = $input.data( 'max-time' );
if ( app.compareTimesGreaterThan( maxTime, minTime ) ) {
return app.compareTimesGreaterThan( value, minTime ) && app.compareTimesGreaterThan( maxTime, value );
}
return ( app.compareTimesGreaterThan( value, minTime ) && app.compareTimesGreaterThan( value, maxTime ) ) ||
( app.compareTimesGreaterThan( minTime, value ) && app.compareTimesGreaterThan( maxTime, value ) );
}, function( params, element ) {
const $input = $( element );
let minTime = $input.data( 'min-time' ),
maxTime = $input.data( 'max-time' );
// Replace `00:**pm` with `12:**pm`.
minTime = minTime.replace( /^00:([0-9]{2})pm$/, '12:$1pm' );
maxTime = maxTime.replace( /^00:([0-9]{2})pm$/, '12:$1pm' );
// Proper format time: add space before AM/PM, make uppercase.
minTime = minTime.replace( /(am|pm)/g, ' $1' ).toUpperCase();
maxTime = maxTime.replace( /(am|pm)/g, ' $1' ).toUpperCase();
return wpforms_settings.val_time_limit
.replace( '{minTime}', minTime )
.replace( '{maxTime}', maxTime );
} );
// Validate checkbox choice limit.
$.validator.addMethod( 'check-limit', function( value, element ) {
const $ul = $( element ).closest( 'ul' ),
choiceLimit = parseInt( $ul.attr( 'data-choice-limit' ) || 0, 10 );
if ( 0 === choiceLimit ) {
return true;
}
const $checked = $ul.find( 'input[type="checkbox"]:checked' );
return $checked.length <= choiceLimit;
}, function( params, element ) {
const choiceLimit = parseInt( $( element ).closest( 'ul' ).attr( 'data-choice-limit' ) || 0, 10 );
return wpforms_settings.val_checklimit.replace( '{#}', choiceLimit );
} );
// Validate Inputmask completeness.
$.validator.addMethod( 'inputmask-incomplete', function( value, element ) {
if ( value.length === 0 || typeof $.fn.inputmask === 'undefined' ) {
return true;
}
return $( element ).inputmask( 'isComplete' );
}, wpforms_settings.val_inputmask_incomplete );
// Validate Payment item value on zero.
$.validator.addMethod( 'required-positive-number', function( value, element ) {
return app.amountSanitize( value ) > 0;
}, wpforms_settings.val_number_positive );
/**
* Validate Payment item minimum price value.
*
* @since 1.8.6
*/
$.validator.addMethod( 'required-minimum-price', function( value, element, param ) {
const $el = $( element );
/**
* The validation is passed in the following cases:
* 1) if a field is not filled in and not required.
* 2) if the minimum required price is equal to or less than the typed value.
* Note: since the param is returned in decimal format at all times, we need to format the value to compare it.
*/
return ( value === '' && ! $el.hasClass( 'wpforms-field-required' ) ) || Number( app.amountSanitize( app.amountFormat( param ) ) ) <= Number( app.amountSanitize( value ) );
}, wpforms_settings.val_minimum_price );
// Validate password strength.
$.validator.addMethod( 'password-strength', function( value, element ) {
const $el = $( element );
// Need to check if the password strength to remove the error message.
const strength = WPFormsPasswordField.passwordStrength( value, element );
/**
* The validation is passed in the following cases:
* 1) if a field is not filled in and not required.
* 2) if the password strength is equal to or greater than the specified level.
*/
return ( value === '' && ! $el.hasClass( 'wpforms-field-required' ) ) || strength >= Number( $el.data( 'password-strength-level' ) );
}, wpforms_settings.val_password_strength );
// Finally, load jQuery Validation library for our forms.
$( '.wpforms-validate' ).each( function() { // eslint-disable-line max-lines-per-function
const form = $( this ),
formID = form.data( 'formid' );
let properties;
// TODO: cleanup this BC with wpforms_validate.
if ( typeof window[ 'wpforms_' + formID ] !== 'undefined' && window[ 'wpforms_' + formID ].hasOwnProperty( 'validate' ) ) {
properties = window[ 'wpforms_' + formID ].validate;
} else if ( typeof wpforms_validate !== 'undefined' ) {
properties = wpforms_validate;
} else {
properties = {
errorElement: app.isModernMarkupEnabled() ? 'em' : 'label',
errorClass: 'wpforms-error',
validClass: 'wpforms-valid',
ignore: ':hidden:not(textarea.wp-editor-area), .wpforms-conditional-hide textarea.wp-editor-area',
ignoreTitle: true,
errorPlacement( error, element ) { // eslint-disable-line complexity
if ( app.isLikertScaleField( element ) ) {
element.closest( 'table' ).hasClass( 'single-row' )
? element.closest( '.wpforms-field' ).append( error )
: element.closest( 'tr' ).find( 'th' ).append( error );
} else if ( app.isWrappedField( element ) ) {
element.closest( '.wpforms-field' ).append( error );
} else if ( app.isDateTimeField( element ) ) {
app.dateTimeErrorPlacement( element, error );
} else if ( app.isFieldInColumn( element ) ) {
element.parent().append( error );
} else if ( app.isFieldHasHint( element ) ) {
element.parent().append( error );
} else if ( app.isLeadFormsSelect( element ) ) {
element.parent().parent().append( error );
} else if ( element.hasClass( 'wp-editor-area' ) ) {
element.parent().parent().parent().append( error );
} else {
error.insertAfter( element );
}
if ( app.isModernMarkupEnabled() ) {
error.attr( {
role: 'alert',
'aria-label': wpforms_settings.errorMessagePrefix,
for: '',
} );
}
},
highlight( element, errorClass, validClass ) { // eslint-disable-line complexity
const $element = $( element ),
$field = $element.closest( '.wpforms-field' ),
inputName = $element.attr( 'name' );
if ( 'radio' === $element.attr( 'type' ) || 'checkbox' === $element.attr( 'type' ) ) {
$field.find( 'input[name="' + inputName + '"]' ).addClass( errorClass ).removeClass( validClass );
} else {
$element.addClass( errorClass ).removeClass( validClass );
}
// Remove password strength container for empty required password field.
if (
$element.attr( 'type' ) === 'password' &&
$element.val().trim() === '' &&
window.WPFormsPasswordField &&
$element.data( 'rule-password-strength' ) &&
$element.hasClass( 'wpforms-field-required' )
) {
WPFormsPasswordField.passwordStrength( '', element );
}
$field.addClass( 'wpforms-has-error' );
},
unhighlight( element, errorClass, validClass ) {
const $element = $( element ),
$field = $element.closest( '.wpforms-field' ),
inputName = $element.attr( 'name' );
if ( 'radio' === $element.attr( 'type' ) || 'checkbox' === $element.attr( 'type' ) ) {
$field.find( 'input[name="' + inputName + '"]' ).addClass( validClass ).removeClass( errorClass );
} else {
$element.addClass( validClass ).removeClass( errorClass );
}
// Remove the error class from the field container if there are no subfield errors.
if ( ! $field.find( ':input.wpforms-error,[data-dz-errormessage]:not(:empty)' ).length ) {
$field.removeClass( 'wpforms-has-error' );
}
// Remove an error message to be sure the next time the `errorPlacement` method will be executed.
if ( app.isModernMarkupEnabled() ) {
$element.parent().find( 'em.wpforms-error' ).remove();
}
},
submitHandler( form ) {
/**
* Captcha error handler.
*
* @since 1.8.4
*
* @param {jQuery} $form current form element.
* @param {jQuery} $container current form container.
*/
const captchaErrorDisplay = function( $form, $container ) {
let errorTag = 'label',
errorRole = '';
if ( app.isModernMarkupEnabled() ) {
errorTag = 'em';
errorRole = 'role="alert"';
}
const error = `<${ errorTag } id="wpforms-field_recaptcha-error" class="wpforms-error" ${ errorRole }> ${ wpforms_settings.val_recaptcha_fail_msg }${ errorTag }>`;
$form.find( '.wpforms-recaptcha-container' ).append( error );
app.restoreSubmitButton( $form, $container );
};
const disableSubmitButton = function( $form ) {
const $submit = $form.find( '.wpforms-submit' );
$submit.prop( 'disabled', true );
WPFormsUtils.triggerEvent( $form, 'wpformsFormSubmitButtonDisable', [ $form, $submit ] );
};
/**
* Submit handler routine.
*
* @since 1.7.2
*
* @return {boolean|void} False if form won't submit.
*/
const submitHandlerRoutine = function() { // eslint-disable-line complexity
const $form = $( form ),
$container = $form.closest( '.wpforms-container' ),
$submit = $form.find( '.wpforms-submit' ),
isCaptchaInvalid = $submit.data( 'captchaInvalid' ),
altText = $submit.data( 'alt-text' ),
recaptchaID = $submit.get( 0 ).recaptchaID;
if ( $form.data( 'token' ) && 0 === $( '.wpforms-token', $form ).length ) {
$( '' )
.val( $form.data( 'token' ) )
.appendTo( $form );
}
$form.find( '#wpforms-field_recaptcha-error' ).remove();
disableSubmitButton( $form );
// Display processing text.
if ( altText ) {
$submit.text( altText );
}
if ( isCaptchaInvalid ) {
return captchaErrorDisplay( $form, $container );
}
if ( ! app.empty( recaptchaID ) || recaptchaID === 0 ) {
// The Form contains invisible reCAPTCHA.
grecaptcha.execute( recaptchaID ).then( null, function() {
if ( grecaptcha.getResponse() ) {
return;
}
captchaErrorDisplay( $form, $container );
} );
return false;
}
// Remove name attributes if needed.
$( '.wpforms-input-temp-name' ).removeAttr( 'name' );
app.formSubmit( $form );
};
// In the case of active Google reCAPTCHA v3, first, we should call `grecaptcha.execute`.
// This is needed to get a proper grecaptcha token before submitting the form.
if ( typeof wpformsRecaptchaV3Execute === 'function' ) {
disableSubmitButton( $( form ) );
return wpformsRecaptchaV3Execute( submitHandlerRoutine );
}
return submitHandlerRoutine();
},
invalidHandler( event, validator ) {
if ( typeof validator.errorList[ 0 ] !== 'undefined' ) {
app.scrollToError( $( validator.errorList[ 0 ].element ) );
}
},
onkeyup: WPFormsUtils.debounce( // eslint-disable-next-line complexity
function( element, event ) {
// This code is copied from JQuery Validate 'onkeyup' method with only one change: 'wpforms-novalidate-onkeyup' class check.
const excludedKeys = [ 16, 17, 18, 20, 35, 36, 37, 38, 39, 40, 45, 144, 225 ];
if ( $( element ).hasClass( 'wpforms-novalidate-onkeyup' ) ) {
return; // Disable onkeyup validation for some elements (e.g. remote calls).
}
// eslint-disable-next-line no-mixed-operators
if ( event.which === 9 && this.elementValue( element ) === '' || $.inArray( event.keyCode, excludedKeys ) !== -1 ) {
} else if ( element.name in this.submitted || element.name in this.invalid ) {
this.element( element );
}
},
1000
),
onfocusout: function( element ) { // eslint-disable-line complexity, object-shorthand
// This code is copied from JQuery Validate 'onfocusout' method with only one change: 'wpforms-novalidate-onkeyup' class check.
let validate = false;
if ( $( element ).hasClass( 'wpforms-novalidate-onkeyup' ) && ! element.value ) {
validate = true; // Empty value error handling for elements with onkeyup validation disabled.
}
if ( ! this.checkable( element ) && ( element.name in this.submitted || ! this.optional( element ) ) ) {
validate = true;
}
// If the error comes from server validation, we don't need to validate it again,
// because it will clean the error message too early.
if ( $( element ).data( 'server-error' ) ) {
validate = false;
}
if ( validate ) {
this.element( element );
}
},
onclick( element ) {
let validate = false;
const type = ( element || {} ).type;
let $el = $( element );
if ( [ 'checkbox', 'radio' ].indexOf( type ) > -1 ) {
if ( $el.hasClass( 'wpforms-likert-scale-option' ) ) {
$el = $el.closest( 'tr' );
} else {
$el = $el.closest( '.wpforms-field' );
}
$el.find( 'label.wpforms-error, em.wpforms-error' ).remove();
validate = true;
}
if ( validate ) {
this.element( element );
}
},
};
}
form.validate( properties );
app.loadValidationGroups( form );
} );
},
/**
* Request to check if email is restricted.
*
* @since 1.8.5
*
* @param {Element} element Email input field.
* @param {string} value Field value.
*/
restrictedEmailRequest( element, value ) {
const $el = $( element );
const $form = $el.closest( 'form' );
const validator = $form.data( 'validator' );
const formId = $form.data( 'formid' );
const $field = $el.closest( '.wpforms-field' );
const fieldId = $field.data( 'field-id' );
app.cache[ formId ] = app.cache[ formId ] || {};
validator.startRequest( element );
$.post( {
url: wpforms_settings.ajaxurl,
type: 'post',
data: {
action: 'wpforms_restricted_email',
form_id: formId, // eslint-disable-line camelcase
field_id: fieldId, // eslint-disable-line camelcase
email: value,
},
dataType: 'json',
success( response ) {
const errors = {};
const isValid = response.success && response.data;
if ( ! isValid ) {
errors[ element.name ] = wpforms_settings.val_email_restricted;
validator.showErrors( errors );
}
app.cache[ formId ].restrictedEmailValidation = app.cache[ formId ].restrictedEmailValidation || [];
if ( ! Object.prototype.hasOwnProperty.call( app.cache[ formId ].restrictedEmailValidation, value ) ) {
app.cache[ formId ].restrictedEmailValidation[ value ] = isValid;
}
validator.stopRequest( element, isValid );
},
} );
},
/**
* Is field inside column.
*
* @since 1.6.3
*
* @param {jQuery} element current form element.
*
* @return {boolean} true/false.
*/
isFieldInColumn( element ) {
return element.parent().hasClass( 'wpforms-one-half' ) ||
element.parent().hasClass( 'wpforms-two-fifths' ) ||
element.parent().hasClass( 'wpforms-one-fifth' );
},
/**
* Is field has hint (sublabel, description, limit text hint, etc.).
*
* @since 1.8.1
*
* @param {jQuery} element current form element.
*
* @return {boolean} true/false.
*/
isFieldHasHint( element ) {
return element
.nextAll( '.wpforms-field-sublabel, .wpforms-field-description, .wpforms-field-limit-text, .wpforms-pass-strength-result' )
.length > 0;
},
/**
* Is datetime field.
*
* @since 1.6.3
*
* @param {jQuery} element current form element.
*
* @return {boolean} true/false.
*/
isDateTimeField( element ) {
return element.hasClass( 'wpforms-timepicker' ) ||
element.hasClass( 'wpforms-datepicker' ) ||
( element.is( 'select' ) && element.attr( 'class' ).match( /date-month|date-day|date-year/ ) );
},
/**
* Is a field wrapped in some container.
*
* @since 1.6.3
*
* @param {jQuery} element current form element.
*
* @return {boolean} true/false.
*/
isWrappedField( element ) { // eslint-disable-line complexity
return 'checkbox' === element.attr( 'type' ) ||
'radio' === element.attr( 'type' ) ||
'range' === element.attr( 'type' ) ||
'select' === element.is( 'select' ) ||
1 === element.data( 'is-wrapped-field' ) ||
element.parent().hasClass( 'iti' ) ||
element.hasClass( 'wpforms-validation-group-member' ) ||
element.hasClass( 'choicesjs-select' ) ||
element.hasClass( 'wpforms-net-promoter-score-option' ) ||
element.hasClass( 'wpforms-field-payment-coupon-input' );
},
/**
* Is likert scale field.
*
* @since 1.6.3
*
* @param {jQuery} element current form element.
*
* @return {boolean} true/false.
*/
isLikertScaleField( element ) {
return element.hasClass( 'wpforms-likert-scale-option' );
},
/**
* Is Lead Forms select field.
*
* @since 1.8.1
*
* @param {jQuery} element current form element.
*
* @return {boolean} true/false.
*/
isLeadFormsSelect( element ) {
return element.parent().hasClass( 'wpforms-lead-forms-select' );
},
/**
* Is Coupon field.
*
* @since 1.8.2
* @deprecated 1.8.4 Deprecated.
*
* @param {jQuery} element current form element.
*
* @return {boolean} true/false.
*/
isCoupon( element ) {
// eslint-disable-next-line no-console
console.warn( 'WARNING! Function "wpforms.isCoupon( element )" has been deprecated' );
return element.closest( '.wpforms-field' ).hasClass( 'wpforms-field-payment-coupon' );
},
/**
* Print error message into date time fields.
*
* @since 1.6.3
*
* @param {jQuery} element current form element.
* @param {string} error Error message.
*/
dateTimeErrorPlacement( element, error ) {
const $wrapper = element.closest( '.wpforms-field-row-block, .wpforms-field-date-time' );
if ( $wrapper.length ) {
if ( ! $wrapper.find( 'label.wpforms-error, em.wpforms-error' ).length ) {
$wrapper.append( error );
}
} else {
element.closest( '.wpforms-field' ).append( error );
}
},
/**
* Load jQuery Date Picker.
*
* @since 1.2.3
* @since 1.8.9 Added the `$context` parameter.
*
* @param {jQuery} $context Container to search for datepicker elements.
*/
loadDatePicker( $context ) { // eslint-disable-line max-lines-per-function
// Only load if jQuery datepicker library exists.
if ( typeof $.fn.flatpickr === 'undefined' ) {
return;
}
$context = $context?.length ? $context : $( document );
$context.find( '.wpforms-datepicker-wrap' ).each( function() { // eslint-disable-line complexity, max-lines-per-function
const element = $( this ),
$input = element.find( 'input' ),
form = element.closest( '.wpforms-form' ),
formID = form.data( 'formid' ),
fieldID = element.closest( '.wpforms-field' ).data( 'field-id' );
let properties;
if ( typeof window[ 'wpforms_' + formID + '_' + fieldID ] !== 'undefined' && window[ 'wpforms_' + formID + '_' + fieldID ].hasOwnProperty( 'datepicker' ) ) {
properties = window[ 'wpforms_' + formID + '_' + fieldID ].datepicker;
} else if ( typeof window[ 'wpforms_' + formID ] !== 'undefined' && window[ 'wpforms_' + formID ].hasOwnProperty( 'datepicker' ) ) {
properties = window[ 'wpforms_' + formID ].datepicker;
} else if ( typeof wpforms_datepicker !== 'undefined' ) {
properties = wpforms_datepicker;
} else {
properties = {
disableMobile: true,
};
}
// Redefine locale only if user doesn't do that manually, and we have the locale.
if (
! properties.hasOwnProperty( 'locale' ) &&
typeof wpforms_settings !== 'undefined' &&
wpforms_settings.hasOwnProperty( 'locale' )
) {
properties.locale = wpforms_settings.locale;
}
properties.wrap = true;
properties.dateFormat = $input.data( 'date-format' );
if ( $input.data( 'disable-past-dates' ) === 1 ) {
properties.minDate = 'today';
if ( $input.data( 'disable-todays-date' ) === 1 ) {
const date = new Date();
properties.minDate = date.setDate( date.getDate() + 1 );
}
}
let limitDays = $input.data( 'limit-days' );
const weekDays = [ 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ];
if ( limitDays && limitDays !== '' ) {
limitDays = limitDays.split( ',' );
properties.disable = [ function( date ) {
let limitDay = null;
for ( const i in limitDays ) {
limitDay = weekDays.indexOf( limitDays[ i ] );
if ( limitDay === date.getDay() ) {
return false;
}
}
return true;
} ];
}
// Toggle clear date icon.
properties.onChange = function( selectedDates, dateStr, instance ) { // eslint-disable-line no-unused-vars
element.find( '.wpforms-datepicker-clear' )
.css( 'display', dateStr === '' ? 'none' : 'block' );
};
element.flatpickr( properties );
} );
},
/**
* Load jQuery Time Picker.
*
* @since 1.2.3
* @since 1.8.9 Added the `$context` parameter.
*
* @param {jQuery} $context Container to search for datepicker elements.
*/
loadTimePicker( $context ) {
// Only load if jQuery timepicker library exists.
if ( typeof $.fn.timepicker === 'undefined' ) {
return;
}
$context = $context?.length ? $context : $( document );
$context.find( '.wpforms-timepicker' ).each( function() { // eslint-disable-line complexity
const element = $( this ),
form = element.closest( '.wpforms-form' ),
formID = form.data( 'formid' ),
fieldID = element.closest( '.wpforms-field' ).data( 'field-id' );
let properties;
if (
typeof window[ 'wpforms_' + formID + '_' + fieldID ] !== 'undefined' &&
window[ 'wpforms_' + formID + '_' + fieldID ].hasOwnProperty( 'timepicker' )
) {
properties = window[ 'wpforms_' + formID + '_' + fieldID ].timepicker;
} else if (
typeof window[ 'wpforms_' + formID ] !== 'undefined' &&
window[ 'wpforms_' + formID ].hasOwnProperty( 'timepicker' )
) {
properties = window[ 'wpforms_' + formID ].timepicker;
} else if ( typeof wpforms_timepicker !== 'undefined' ) {
properties = wpforms_timepicker;
} else {
properties = {
scrollDefault: 'now',
forceRoundTime: true,
};
}
// Retrieve the value from the input element.
const inputValue = element.val();
element.timepicker( properties );
// Check if a value is available.
if ( inputValue ) {
// Set the input element's value to the retrieved value.
element.val( inputValue );
// Trigger the 'changeTime' event to update the timepicker after programmatically setting the value.
element.trigger( 'changeTime' );
}
} );
},
/**
* Load jQuery input masks.
*
* @since 1.2.3
* @since 1.8.9 Added the `$context` parameter.
*
* @param {jQuery} $context Container to search for datepicker elements.
*/
loadInputMask( $context ) {
// Only load if jQuery input mask library exists.
if ( typeof $.fn.inputmask === 'undefined' ) {
return;
}
$context = $context?.length ? $context : $( document );
// This setting has no effect when switching to the "RTL" mode.
$context.find( '.wpforms-masked-input' ).inputmask( { rightAlign: false } );
},
/**
* Fix the Phone field snippets.
*
* @since 1.8.7.1
* @deprecated 1.9.2
*
* @param {jQuery} $field Phone field element.
*/
fixPhoneFieldSnippets( $field ) {
// eslint-disable-next-line no-console
console.warn( 'WARNING! Obsolete function called. Function wpforms.fixPhoneFieldSnippets( $field ) has been deprecated, please use the wpforms.repairSmartPhoneHiddenField( $field ) function instead!' );
$field.siblings( 'input[type="hidden"]' ).each( function() {
if ( ! $( this ).attr( 'name' ).includes( 'function' ) ) {
return;
}
const data = $field.data( 'plugin_intlTelInput' );
const options = data.d || data.options;
if ( ! options ) {
return;
}
const insta = window.intlTelInputGlobals.getInstance( $field[ 0 ] );
insta.destroy();
options.initialCountry = options.initialCountry.toLowerCase();
options.onlyCountries = options.onlyCountries.map( ( v ) => v.toLowerCase() );
options.preferredCountries = options.preferredCountries.map( ( v ) => v.toLowerCase() );
window.intlTelInput( $field[ 0 ], options );
$field.siblings( 'input[type="hidden"]' ).each( function() {
const $hiddenInput = $( this );
$hiddenInput.attr( 'name', $hiddenInput.attr( 'name' ).replace( 'wpf-temp-', '' ) );
} );
} );
},
/**
* Compatibility fix with an old intl-tel-input library that may include in other addons.
* Also, for custom snippets that use `options.hiddenInput` to recieve fieldId.
*
* @since 1.9.2
* @deprecated 1.9.4
*
* @param {jQuery} $field Phone field element.
*/
repairSmartPhoneHiddenField( $field ) {
// eslint-disable-next-line no-console
console.warn( 'WARNING! Function "wpforms.repairSmartPhoneHiddenField()" has been deprecated, please use the new "WPFormsPhoneField.repairSmartHiddenField()" function instead!' );
WPFormsPhoneField?.repairSmartHiddenField?.( $field );
},
/**
* Get a list of default smart phone field options.
*
* @since 1.9.2
* @deprecated 1.9.4
*
* @return {Object} List of default options.
*/
getDefaultSmartPhoneFieldOptions() {
// eslint-disable-next-line no-console
console.warn( 'WARNING! Function "wpforms.getDefaultSmartPhoneFieldOptions()" has been deprecated, please use the new "WPFormsPhoneField.getDefaultSmartFieldOptions()" function instead!' );
return WPFormsPhoneField?.getDefaultSmartFieldOptions?.();
},
/**
* Load Smartphone field.
*
* @since 1.5.2
* @since 1.8.9 Added the `$context` parameter.
* @deprecated 1.9.4
*
* @param {jQuery} $context Context to search for smartphone elements.
*/
loadSmartPhoneField( $context ) {
// eslint-disable-next-line no-console
console.warn( 'WARNING! Function "wpforms.loadSmartPhoneField()" has been deprecated, please use the new "WPFormsPhoneField.loadSmartField()" function instead!' );
WPFormsPhoneField?.loadSmartField?.( $context );
},
/**
* Backward compatibility jQuery plugin for IntlTelInput library, to support custom snippets.
* e.g., https://wpforms.com/developers/how-to-set-a-default-flag-on-smart-phone-field-with-gdpr/.
*
* @since 1.9.2
* @deprecated 1.9.4
*/
loadJqueryIntlTelInput() {
// eslint-disable-next-line no-console
console.warn( 'WARNING! Function "wpforms.loadJqueryIntlTelInput()" has been deprecated, please use the new "WPFormsPhoneField.loadJqueryIntlTelInput()" function instead!' );
WPFormsPhoneField?.loadJqueryIntlTelInput?.();
},
/**
* Init smart phone field.
*
* @since 1.9.2
* @deprecated 1.9.4
*
* @param {jQuery} $el Input field.
* @param {Object} inputOptions Options for intlTelInput.
*/
initSmartPhoneField( $el, inputOptions ) {
// eslint-disable-next-line no-console
console.warn( 'WARNING! Function "wpforms.initSmartPhoneField()" has been deprecated, please use the new "WPFormsPhoneField.initSmartField()" function instead!' );
WPFormsPhoneField?.initSmartField?.( $el, inputOptions );
},
/**
* Bind Smartphone field event.
*
* @since 1.8.9
* @deprecated 1.9.4
*/
bindSmartPhoneField() {
// eslint-disable-next-line no-console
console.warn( 'WARNING! Function "wpforms.bindSmartPhoneField()" has been deprecated, please use the new "WPFormsPhoneField.bindSmartField()" function instead!' );
WPFormsPhoneField?.bindSmartField?.();
},
/**
* Payments: Do various payment-related tasks on a load.
*
* @since 1.2.6
*/
loadPayments() {
// Update Total field(s) with the latest calculation.
$( '.wpforms-payment-total' ).each( function( index, el ) {
app.amountTotal( this );
} );
// Credit card validation.
if ( typeof $.fn.payment !== 'undefined' ) {
$( '.wpforms-field-credit-card-cardnumber' ).payment( 'formatCardNumber' );
$( '.wpforms-field-credit-card-cardcvc' ).payment( 'formatCardCVC' );
}
},
/**
* Load mailcheck.
*
* @since 1.5.3
*/
loadMailcheck() { // eslint-disable-line max-lines-per-function
// Skip loading if `wpforms_mailcheck_enabled` filter return false.
if ( ! wpforms_settings.mailcheck_enabled ) {
return;
}
// Only load if a library exists.
if ( typeof $.fn.mailcheck === 'undefined' ) {
return;
}
if ( wpforms_settings.mailcheck_domains.length > 0 ) {
Mailcheck.defaultDomains = Mailcheck.defaultDomains.concat( wpforms_settings.mailcheck_domains );
}
if ( wpforms_settings.mailcheck_toplevel_domains.length > 0 ) {
Mailcheck.defaultTopLevelDomains = Mailcheck.defaultTopLevelDomains.concat( wpforms_settings.mailcheck_toplevel_domains );
}
// Mailcheck suggestion.
$( document ).on( 'blur', '.wpforms-field-email input', function() {
const $input = $( this ),
id = $input.attr( 'id' );
$input.mailcheck( {
suggested( $el, suggestion ) {
// decodeURI() will throw an error if the percent sign is not followed by two hexadecimal digits.
suggestion.full = suggestion.full.replace( /%(?![0-9][0-9a-fA-F]+)/g, '%25' );
suggestion.address = suggestion.address.replace( /%(?![0-9][0-9a-fA-F]+)/g, '%25' );
suggestion.domain = suggestion.domain.replace( /%(?![0-9][0-9a-fA-F]+)/g, '%25' );
if ( suggestion.address.match( /^xn--/ ) ) {
suggestion.full = punycode.toUnicode( decodeURI( suggestion.full ) );
const parts = suggestion.full.split( '@' );
suggestion.address = parts[ 0 ];
suggestion.domain = parts[ 1 ];
}
if ( suggestion.domain.match( /^xn--/ ) ) {
suggestion.domain = punycode.toUnicode( decodeURI( suggestion.domain ) );
}
const address = decodeURI( suggestion.address ).replaceAll( /[<>'"()/\\|:;=@%&\s]/ig, '' ).substr( 0, 64 ),
domain = decodeURI( suggestion.domain ).replaceAll( /[<>'"()/\\|:;=@%&+_\s]/ig, '' );
suggestion = '' + address + '@' + domain + '';
suggestion = wpforms_settings.val_email_suggestion.replace( '{suggestion}', suggestion );
$el.closest( '.wpforms-field' ).find( '#' + id + '_suggestion' ).remove();
$el.parent().append( '' );
},
empty() {
$( '#' + id + '_suggestion' ).remove();
},
} );
} );
// Apply a Mailcheck suggestion.
$( document ).on( 'click', '.wpforms-field-email .mailcheck-suggestion', function( e ) {
const $suggestion = $( this ),
$field = $suggestion.closest( '.wpforms-field' ),
id = $suggestion.data( 'id' );
e.preventDefault();
$field.find( '#' + id ).val( $suggestion.text() );
$suggestion.parent().remove();
} );
},
/**
* Load Choices.js library for all Modern style Dropdown fields (