PageSourceSearch

https://verilymag.com/wp-content/plugins/gravityforms/js/gravityforms.js?ver=3.1.2

js verilymag.com collected 2026-10-02 04:34:29 UTC 103,596 bytes, 3,095 lines download raw bytes

1/* eslint-env jquery */
2
3var gform = window.gform || {};
4
5// "prop" method fix for previous versions of jQuery (1.5 and below)
6if( typeof jQuery.fn.prop === 'undefined' ) {
7    jQuery.fn.prop = jQuery.fn.attr;
8}
9
10//----------------------------------------
11//------ INSTANCES -----------------------
12//----------------------------------------
13
14/**
15 * Namespace to store our JavaScript class instances
16 */
17
18gform.instances = gform.instances || {};
19
20//----------------------------------------
21//------ CONSOLE FUNCTIONS ---------------
22//----------------------------------------
23
24/**
25 * Console namespace for our safe to use and extendable console functions.
26 */
27
28gform.console = {
29    error: function( message ) {
30        if( window.console ) {
31            console.error( message );
32        }
33    },
34    info: function( message ) {
35        if( window.console ) {
36            console.info( message );
37        }
38    },
39    log: function( message ) {
40        if( window.console ) {
41            console.log( message );
42        }
43    },
44};
45
46//----------------------------------------
47//------ ADMIN UTIL FUNCTIONS ------------
48//----------------------------------------
49
50/**
51 * Namespace for our admin utlity functions
52 */
53
54gform.adminUtils = {
55
56	/**
57	 * Handle any unsaved changes to the current settings page.
58	 *
59	 * @since 2.4
60	 *
61	 * @param {string} elemId The ID of the current element to check for changes.
62	 */
63	handleUnsavedChanges: function( elemId ) {
64		var hasUnsavedChanges = null;
65
66		jQuery( elemId ).find( 'input, select, textarea' ).on( 'change keyup', function() {
67
68			if ( jQuery( this ).attr( 'onChange' ) === undefined && jQuery( this ).attr( 'onClick' ) === undefined )  {
69				hasUnsavedChanges = true;
70			}
71
72			// Don't trigger unsaved changes on the enable api access button.
73			if ( ( jQuery( this ).next().data("jsButton") || jQuery( this ).data("jsButton") ) === 'enable-api' ) {
74				hasUnsavedChanges = null;
75			}
76
77		} );
78
79		// Standalone logic for the web api settings page. Trigger unsaved changes if the setting doesn't match the checkbox state.
80		if ( this.getUrlParameter( 'subview' ) === 'gravityformswebapi' ) {
81			if ( window.gf_webapi_vars && window.gf_webapi_vars.api_enabled !== window.gf_webapi_vars.enable_api_checkbox_checked ) {
82				hasUnsavedChanges = true;
83			}
84		}
85
86		jQuery( elemId ).on( 'submit', function() {
87			hasUnsavedChanges = null;
88		} );
89
90		window.onbeforeunload = function() {
91			return hasUnsavedChanges;
92		};
93	},
94
95	getUrlParameter: function( param ) {
96		var url = window.location.search.substring( 1 );
97		var urlVariables = url.split( '&' );
98		for ( var i = 0; i < urlVariables.length; i++ ) {
99			var parameterName = urlVariables[i].split( '=' );
100			if ( parameterName[0] == param )
101			{
102				return parameterName[1];
103			}
104		}
105	},
106}
107
108window.HandleUnsavedChanges = gform.adminUtils.handleUnsavedChanges;
109
110//----------------------------------------
111//------ TOOL FUNCTIONS ------------------
112//----------------------------------------
113
114/**
115 * Tool namespace to house our common dom/function tools.
116 */
117
118gform.tools = {
119	/**
120	 * Wrapper to add debouncing to any given callback.
121	 *
122	 * @since 2.5.2
123	 *
124	 * @param {Function} fn             The callback to execute.
125	 * @param {integer}  debounceLength The amount of time for which to debounce (in milliseconds)
126	 * @param {bool}     isImmediate    Whether to fire this immediately, or at the tail end of the timeout.
127	 *
128	 * @returns {function}
129	 */
130	debounce: function( fn, debounceLength, isImmediate ) {
131		// Initialize var to hold our window timeout
132		var timeout;
133		var lastArgs;
134		var lastFn;
135
136		return function() {
137			// Initialize local versions of our context and arguments to pass to apply()
138			var callbackContext = this;
139			var args            = arguments;
140
141			// Create a deferred callback to fire if this shouldn't be immediate.
142			var deferredCallback = function() {
143				timeout = null;
144
145				if ( ! isImmediate ) {
146					fn.apply( callbackContext, args );
147				}
148			};
149
150			// Begin processing the actual callback.
151			var callNow = isImmediate && ! timeout;
152
153			// Reset timeout if it is the same method with the same args.
154			if ( args === lastArgs && ( ''+lastFn == ''+fn ) ) {
155				clearTimeout( timeout );
156			}
157
158			// Set the value of the last function call and arguments to help determine whether the next call is unique.
159			var cachePreviousCall = function( fn, args ) {
160				lastFn    = fn;
161				lastArgs = args;
162			}
163
164			timeout = setTimeout( deferredCallback, debounceLength );
165			cachePreviousCall( fn, args );
166
167			// Method should be executed on the trailing edge of the timeout. Bail for now.
168			if ( ! callNow ) {
169				return;
170			}
171
172			// Callback should be called immediately, and isn't currently debounced; execute it.
173			fn.apply( callbackContext, args );
174		};
175	},
176
177    /**
178     * @function gform.tools.defaultFor
179     * @description Returns a default if first arg is undefined. Once we start migrating to es6 or use babel can
180     * easily swap to default args
181     *
182     * @since 2.5
183     *
184     * @param {*} arg
185     * @param {*} val
186     * @returns {*}
187     */
188
189    defaultFor: function( arg, val ) {
190        return typeof arg !== 'undefined' ? arg : val;
191    },
192
193	/**
194	 * @function gform.tools.getFocusable
195	 * @description Get focusable elements inside a container and return as an array.
196	 *
197	 * @since 2.5
198	 *
199	 * @param container the parent to search for focusable elements inside of
200	 * @returns {*[]}
201	 */
202
203	getFocusable: function( container ) {
204		container = this.defaultFor( container, document );
205		var focusable = this.convertElements(
206			container.querySelectorAll(
207				'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
208			)
209		);
210		return focusable.filter( function( item ) {
211			return this.visible( item );
212		}.bind( this ) );
213	},
214
215	/**
216	 * @function gform.tools.htmlToElement
217	 *
218	 * Allows you to convert an HTML string to a DOM Object.
219	 *
220	 * @param {string} html
221	 *
222	 * @returns {ChildNode}
223	 */
224	htmlToElement: function( html ) {
225		var template       = document.createElement( 'template' );
226		html               = html.trim();
227		template.innerHTML = html;
228
229		return template.content.firstChild;
230	},
231
232	/**
233	 * @function gform.tools.elementToHTML
234	 *
235	 * Converts a DOM Element to an HTML string.
236	 *
237	 * @param {object} el
238	 *
239	 * @returns {string}
240	 */
241	elementToHTML: function( el ) {
242		return el.outerHTML;
243	},
244
245    /**
246     * @function gform.tools.convertElements
247     * @description Efficient function to convert a nodelist into a standard array.
248     * Allows you to run Array.forEach in ie11/saf on result of querySelector functions.
249     * Used by getNodes below.
250     *
251     * @since 2.5
252     *
253     * @param {Element|NodeList} elements Elements to convert
254     *
255     * @returns {Array} Of converted elements
256     */
257
258    convertElements: function( elements ) {
259        var converted = [];
260        var i         = elements.length;
261        for ( i; i--; converted.unshift( elements[ i ] ) ) ;
262
263        return converted;
264    },
265
266	/**
267	 * @function gform.tools.delegate
268	 * @description Simple jQuery on replacement. When migrating to ES6 bundle replace with npm delegate.
269	 *
270	 * @since 2.5
271	 *
272	 * @param {String} selector
273	 * @param {String} event
274	 * @param {String} childSelector
275	 * @param {Function} handler
276	 */
277
278	delegate: function( selector, event, childSelector, handler ) {
279		var is = function( el, selector ) {
280			return ( el.matches || el.msMatchesSelector ).call( el, selector );
281		};
282
283		var elements = document.querySelectorAll( selector );
284		[].forEach.call( elements, function( el, i ) {
285			el.addEventListener( event, function( e ) {
286				if ( is( e.target, childSelector ) ) {
287					handler( e );
288				}
289			} );
290		} );
291	},
292
293    /**
294     * @function gform.tools.getClosest
295     * @description Get a parent node based on selector plus passed in child element.
296     *
297     * @since 2.5
298     *
299     * @param {Element|EventTarget} el
300     * @param {String} selector
301     *
302     * @returns {null|*}
303     */
304
305    getClosest: function( el, selector ) {
306        var matchesFn;
307        var parent;
308
309        [ 'matches', 'webkitMatchesSelector', 'mozMatchesSelector', 'msMatchesSelector', 'oMatchesSelector' ]
310            .some( function( fn ) {
311                if ( typeof document.body[ fn ] === 'function' ) {
312                    matchesFn = fn;
313                    return true;
314                }
315                return false;
316            } );
317
318        while ( el ) {
319            parent = el.parentElement;
320            if ( parent && parent[ matchesFn ]( selector ) ) {
321                return parent;
322            }
323
324            el = parent;
325        }
326
327        return null;
328    },
329
330    /**
331     * @function gform.tools.getNodes
332     * @description Used for getting nodes. Please use the data-js attribute whenever possible.
333     *
334     * @since 2.5
335     *
336     * @param {String} selector The selector string to search for. If arg 4 is false (default) then we search for [data-js="selector"]
337     * @param {Boolean} [convert] Convert the NodeList to an array? Then we can Array.forEach directly. Uses convertElements from above.
338     * @param {Element|EventTarget|Document} [node] Parent node to search from. Defaults to document.
339     * @param {Boolean} [custom] Is this a custom selector were we don't want to use the data-js attribute?
340     *
341     * @returns {NodeList|Array}
342     */
343
344    getNodes: function( selector, convert, node, custom ) {
345        if ( ! selector ) {
346            gform.console.error( 'Please pass a selector to gform.tools.getNodes' );
347            return [];
348        }
349        node = this.defaultFor( node, document );
350        var selectorString = custom ? selector : '[data-js="' + selector + '"]';
351        var nodes          = node.querySelectorAll( selectorString );
352        if ( convert ) {
353            nodes = this.convertElements( nodes );
354        }
355        return nodes;
356    },
357
358	/**
359	 * @function gform.tools.mergeObjects
360	 * @description ES5 Object.assign. Usage: gforms.tools.mergeObjects( obj1, obj2, obj3 );
361	 *
362	 * @since 2.5
363	 *
364	 * @returns {{}}
365	 */
366
367	mergeObjects: function() {
368		var resObj = {};
369		for ( var i = 0; i < arguments.length; i += 1 ) {
370			var obj = arguments[ i ]
371			var keys = Object.keys( obj );
372			for ( var j = 0; j < keys.length; j += 1 ) {
373				resObj[ keys[ j ] ] = obj[ keys[ j ] ];
374			}
375		}
376		return resObj;
377	},
378
379    /**
380     * @function gform.tools.setAttr
381     * @description Sets attributes for a group of nodes based on a passed selector.
382     * Can apply to document or subset, and has optional delay.
383     *
384     * @since 2.5
385     *
386     * @param {String} selector A selector string, and valid js selector string for a dom element.
387     * @param {String} attr The attribute name.
388     * @param {String} value The attribute value.
389     * @param {Element|EventTarget|Document} [container] Node to search from, default is document.
390     * @param {Number} [delay] The delay to apply.
391     */
392
393    setAttr: function( selector, attr, value, container, delay ) {
394        if ( ! selector || ! attr || ! value ) {
395            gform.console.error( 'Please pass a selector, attribute and value to gform.tools.setAttr' );
396            return [];
397        }
398        container = this.defaultFor( container, document );
399        delay = this.defaultFor( delay, 0 );
400
401        setTimeout( function() {
402            gform.tools.getNodes( selector, true, container, true )
403                .forEach( function( node ) {
404                    node.setAttribute( attr, value );
405                } );
406        }, delay );
407    },
408
409	/**
410	 * @function gform.tools.isRtl
411	 * @description Determine if the page is in RTL.
412	 *
413	 * @since 2.5
414	 *
415	 */
416
417	isRtl: function() {
418		if ( jQuery( 'html' ).attr( 'dir' ) === 'rtl' ) {
419			return true;
420		}
421	},
422
423	/**
424	 * @function gform.tools.trigger
425	 * @description Trigger custom or native events on any element in a cross browser way, and pass along optional data.
426	 *
427	 * @since 2.5.1.1
428	 *
429	 * @param {String} eventName The event name.
430	 * @param {Element|EventTarget|Document} el Default document. The element to trigger the event on.
431	 * @param {Boolean} native Default fasle. Is this a custom event or native?
432	 * @param {Object} data Custom data to send along, available in event.detail on listener.
433	 */
434
435	trigger: function( eventName, el, native, data ) {
436		var event;
437		eventName =  this.defaultFor( eventName, '' );
438		el =  this.defaultFor( el, document );
439		native =  this.defaultFor( native, false );
440		data =  this.defaultFor( data, {} );
441		if ( native ) {
442			event = document.createEvent( 'HTMLEvents' );
443			event.initEvent( eventName, true, false );
444		} else {
445			try {
446				event = new CustomEvent( eventName, { detail: data } );
447			} catch ( e ) {
448				event = document.createEvent( 'CustomEvent' );
449				event.initCustomEvent( eventName, true, true, data );
450			}
451		}
452
453		el.dispatchEvent( event );
454	},
455
456	/**
457	 * @function gform.tools.uniqueId
458	 * @description Generate a unique id
459	 *
460	 * @since 2.5.5.2
461	 *
462	 * @param {String} prefix
463	 * @returns {string}
464	 */
465
466	uniqueId: function( prefix ) {
467		prefix = this.defaultFor( prefix, 'id' );
468		return prefix + '-' + Math.random().toString( 36 ).substr( 2, 9 );
469	},
470
471	/**
472	 * @function gform.tools.visible
473	 * @description Determine if an element is visible in the dom.
474	 *
475	 * @since 2.5
476	 *
477	 * @param elem The element to check
478	 * @returns {boolean}
479	 */
480
481	visible: function( elem ) {
482		return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
483	},
484
485	stripSlashes: function( str ) {
486		return (str + '').replace(/\\(.?)/g, function (s, n1) {
487			switch (n1) {
488				case '\\':
489					return '\\';
490				case '0':
491					return '\u0000';
492				case '':
493					return '';
494				default:
495					return n1;
496			}
497		});
498	},
499
500	/**
501	 * @function gform.tools.getCookie
502	 * @description Gets a specific cookie.
503	 *
504	 * @since 2.5.8
505	 *
506	 * @param name The cookie to get
507	 * @returns {boolean|string}
508	 */
509
510	getCookie: function( name ) {
511		var cookieArr = document.cookie.split( ";" );
512
513		for(var i = 0; i < cookieArr.length; i++) {
514			var cookiePair = cookieArr[i].split( "=" );
515
516			if( name == cookiePair[0].trim() ) {
517				return decodeURIComponent( cookiePair[1] );
518			}
519		}
520
521		return null;
522	},
523
524	/**
525	 * @function gform.tools.setCookie
526	 * @description Creates and sets a cookie.
527	 *
528	 * @since 2.5.8
529	 *
530	 * @param name The cookie name
531	 * @param value The cookie value
532	 * @param daysToExpire The number of days until cookie should expire. If not set,
533	 * will expire at the end of the user sessions.
534	 * @param updateExistingValue Whether or not to update the existing cookie value to include the new value.
535	 * Can be helpful for keeping cookie count lower for the browser.
536	 */
537
538	setCookie: function( name, value, daysToExpire, updateExistingValue ) {
539		var expirationDate = '';
540		var cookieValue = value;
541
542		if ( daysToExpire ) {
543			var date = new Date();
544			date.setTime( date.getTime() + ( daysToExpire * 24 * 60 * 60 * 1000 ) );
545			expirationDate = ' expires=' + date.toUTCString();
546		}
547
548		if ( updateExistingValue ) {
549			var currentValue = gform.tools.getCookie( name );
550			cookieValue = currentValue !== '' && currentValue !== null ? currentValue + ',' + value : value;
551		}
552
553		// Set cookie
554		document.cookie = encodeURIComponent( name ) + '=' + encodeURIComponent( cookieValue ) + ';' + expirationDate;
555	},
556
557	/**
558	 * @function gform.tools.removeCookie
559	 * @description Removes a cookie.
560	 *
561	 * @since 2.5.8
562	 *
563	 * @param name The cookie name to check
564	 */
565
566	removeCookie: function( name ) {
567		gform.tools.setCookie( name, '', -1 );
568	}
569};
570
571//------------------------------------------------
572//---------- A11Y FUNCTIONS ----------------------
573//------------------------------------------------
574
575/**
576 * A11y namespace to house our accessibility functions.
577 */
578
579gform.a11y = {};
580
581//------------------------------------------------
582//---------- OPTIONS -----------------------------
583//------------------------------------------------
584
585/**
586 * Options namespace to house common plugin and custom options objects for reuse across our JavaScript.
587 */
588
589gform.options = {
590
591    /**
592     * Accordions in the editor sidebar use these options. Should be applied to any accordions that want to emulate
593     * that look and feel, and patches an a11y issue with jq accordion and our custom usage.
594     */
595
596    jqEditorAccordions: {
597    	header: 'button.panel-block-tabs__toggle',
598        heightStyle: 'content',
599        collapsible: true,
600        animate: false,
601        create: function( event ) {
602            gform.tools.setAttr( '.ui-accordion-header', 'tabindex', '0', event.target, 100 );
603        },
604        activate: function( event ) {
605            gform.tools.setAttr( '.ui-accordion-header', 'tabindex', '0', event.target, 100 );
606        },
607	    beforeActivate: function( event ) {
608			// handle advanced tab operations as needed before the tab is revealed in a fields settings
609			if ( event.currentTarget.id === 'advanced_tab_toggle' ) {
610				// handle address field
611				if ( window.field && window.field.type && window.field.type === 'address' ) {
612					// regen the Autocomplete UI on every tab open to handle changes to input visibility from interactions
613					CreateAutocompleteUI( window.field );
614				}
615			}
616	    }
617    },
618
619	jqAddFieldAccordions: {
620		heightStyle: 'content',
621		collapsible: true,
622		animate: false,
623		create: function( event ) {
624			gform.tools.setAttr( '.ui-accordion-header', 'tabindex', '0', event.target, 100 );
625		},
626		activate: function( event ) {
627			gform.tools.setAttr( '.ui-accordion-header', 'tabindex', '0', event.target, 100 );
628		},
629	},
630};
631
632//------------------------------------------------
633//---------- CURRENCY ----------------------------
634//------------------------------------------------
635
636function Currency(currency){
637	console.warn( 'Currency has been deprecated since Gravity Forms 2.9. Use gform.Currency instead.' );
638	return new gform.Currency( currency );
639}
640
641/**
642 * Gets a formatted number and returns a clean "decimal dot" number.
643 *
644 * Note: Input must be formatted according to the specified parameters (symbol_right, symbol_left, decimal_separator).
645 * @example input -> $1.20, output -> 1.2
646 *
647 * @since 2.1.1.16 Modified to support additional param in Currency.toMoney.
648 *
649 * @param text              string The currency-formatted number.
650 * @param symbol_right      string The symbol used on the right.
651 * @param symbol_left       string The symbol used on the left.
652 * @param decimal_separator string The decimal separator being used.
653 *
654 * @return float The unformatted numerical value.
655 */
656function gformCleanNumber(text, symbol_right, symbol_left, decimal_separator){
657	console.warn( 'gformCleanNumber() has been deprecated since Gravity Forms 2.9. Use gform.Currency.cleanNumber() instead.' );
658	return gform.Currency.cleanNumber( text, symbol_right, symbol_left, decimal_separator );
659}
660
661function gformGetDecimalSeparator(numberFormat){
662	console.warn( 'gformGetDecimalSeparator() has been deprecated since Gravity Forms 2.9. Use gform.Currency.getDecimalSeparator() instead.' );
663	return gform.Currency.getDecimalSeparator( numberFormat );
664}
665
666function gformIsNumber(n) {
667	console.warn( 'gformIsNumber() has been deprecated since Gravity Forms 2.9. Use gform.utils.isNumber() instead.' );
668	return gform.utils.isNumber( n );
669}
670
671function gformIsNumeric(value, number_format){
672
673    switch(number_format){
674        case "decimal_dot" :
675            var r = new RegExp("^(-?[0-9]{1,3}(?:,?[0-9]{3})*(?:\.[0-9]+)?)$");
676            return r.test(value);
677        break;
678
679        case "decimal_comma" :
680            var r = new RegExp("^(-?[0-9]{1,3}(?:\.?[0-9]{3})*(?:,[0-9]+)?)$");
681            return r.test(value);
682        break;
683    }
684    return false;
685}
686
687//------------------------------------------------
688//---------- MULTI-PAGE --------------------------
689//------------------------------------------------
690function gformDeleteUploadedFile(formId, fieldId, deleteButton){
691    var parent = jQuery("#field_" + formId + "_" + fieldId);
692
693    var fileIndex = jQuery(deleteButton).parent().index();
694
695    var filePreview = jQuery( deleteButton ).closest( '.ginput_preview' )[0];
696    var fileId = filePreview.id;
697    filePreview.remove();
698
699    //removing the gform_hidden class
700    parent.find('.validation_message,#extensions_message_' + formId + '_' + fieldId).removeClass("gform_hidden");
701
702    //displaying post image label
703    parent.find(".ginput_post_image_file").show();
704
705    //clearing post image meta fields
706    parent.find("input[type=\"text\"]").val('');
707
708    //removing file from uploaded meta
709    var filesJson = jQuery('#gform_uploaded_files_' + formId).val();
710
711    if(filesJson){
712        var files = jQuery.secureEvalJSON(filesJson);
713        if(files) {
714            var inputName = "input_" + fieldId;
715            var $multfile = parent.find("#gform_multifile_upload_" + formId + "_" + fieldId );
716            if( $multfile.length > 0 ) {
717                files[inputName].splice(fileIndex, 1);
718                var settings = $multfile.data('settings');
719                var count = files[ inputName ].length;
720                if ( count === 0 ) {
721                    jQuery( '#' + settings.gf_vars.message_id ).html('');
722                    gfMultiFileUploader.toggleDisabled( settings, false );
723                } else {
724                    jQuery( '#error_' + fileId ).remove(); // Removing the file-specific validation message.
725                    var max = settings.gf_vars.max_files;
726                    if ( count < max )
727                        gfMultiFileUploader.toggleDisabled( settings, false );
728                }
729
730            } else {
731                files[inputName] = null;
732            }
733
734            jQuery('#gform_uploaded_files_' + formId).val(jQuery.toJSON(files));
735        }
736    }
737}
738
739function gformGetFieldId(element){
740	var id = jQuery(element).attr("id");
741	var pieces = id.split("_");
742	if(pieces.length <=0)
743		return 0;
744
745	var fieldId = pieces[pieces.length-1];
746	return fieldId;
747
748}
749
750function gformIsHidden(element){
751	isHidden = element.parents('.gfield').not(".gfield_hidden_product").css("display") == "none";
752
753	/**
754	 * Allows user to filter the logic for determining if a field is hidden by conditional logic..
755	 *
756	 * @since 2.8.10
757	 *
758	 * @param bool            Whether or not the field is hidden.
759	 * @param object $element jQuery object for field input.
760	 */
761	return gform.applyFilters('gform_is_hidden', isHidden, element);
762
763}
764
765function gformFormatMoney(text, isNumeric){
766	if(!gf_global.gf_currency_config)
767		return text;
768
769	var currency = new gform.Currency(gf_global.gf_currency_config);
770	return currency.toMoney(text, isNumeric);
771}
772
773function gformToNumber(text){
774	var currency = new gform.Currency(gf_global.gf_currency_config);
775	return currency.toNumber(text);
776}
777
778function gformRoundPrice(price){
779
780	var currency = new gform.Currency(gf_global.gf_currency_config);
781	var roundedPrice = currency.numberFormat( price, currency.currency['decimals'], '.', '' );
782
783	return parseFloat( roundedPrice );
784}
785
786//------------------------------------------------
787//---------- PRICE -------------------------------
788//------------------------------------------------
789var _gformPriceFields = new Array();
790var _anyProductSelected;
791
792/**
793 * Calculate total price when input is updated.
794 *
795 * @since 2.5.2 - This method is run through debounce() to avoid recursions.
796 * @since 3.0.0 - Deprecated
797 *
798 * @deprecated next - No replacement. Total price calculation is now handled by the products module in assets and will automatically be updated when product fields change.
799 *
800 * @remove-in 4.0
801 */
802var gformCalculateTotalPrice =  gform.tools.debounce(function(formId){
803
804	console.warn( 'gformCalculateTotalPrice() has been deprecated with no replacement. Price fields are now automatically initialized and calculated by the products module in assets.' );
805
806	// For backwards compatibility, we need to ensure fields have been registered.
807	// This was previously handled by gformInitPriceFields, but since that method is no longer called and deprecated, we need to register fields here.
808	if ( _gformPriceFields.length === 0 ) {
809		const priceFields = gform.tools.getNodes( '.gfield_price', true, document, true );
810		priceFields.forEach( ( field ) => {
811			const productIds = gformGetProductIds( 'gfield_price', field );
812			gformRegisterPriceField( productIds );
813		} );
814	}
815
816	if(!_gformPriceFields[formId]) {
817		return;
818	}
819	var price = 0;
820
821	_anyProductSelected = false; //Will be used by gformCalculateProductPrice().
822	for(var i=0; i<_gformPriceFields[formId].length; i++){
823		price += gformCalculateProductPrice(formId, _gformPriceFields[formId][i]);
824	}
825
826	//add shipping price if a product has been selected
827	if(_anyProductSelected){
828		//shipping price
829		var shipping = gformGetShippingPrice(formId)
830		price += shipping;
831	}
832
833	//gform_product_total filter. Allows users to perform custom price calculation
834	if(window["gform_product_total"])
835		price = window["gform_product_total"](formId, price);
836
837	price = gform.applyFilters('gform_product_total', price, formId);
838
839	gformUpdateTotalFieldPrice( formId, price );
840}, 50, false );
841
842/**
843 * Updates the value of the total field with a new price if it has changed.
844 *
845 * @since 2.5.5
846 * @since 3.0.0 - Deprecated
847 *
848 * @param {string|number} formId The ID of the form with the total field.
849 * @param {int} price The new price to apply.
850 *
851 * @deprecated next - No replacement. Total field price calculation is now handled by the products module in assets.
852 *
853 * @remove-in 4.0
854 *
855 * @return {void}
856 */
857function gformUpdateTotalFieldPrice( formId, price ) {
858
859	console.warn( 'gformUpdateTotalFieldPrice() has been deprecated with no replacement. Price fields are now automatically initialized and calculated by the products module in assets.' );
860
861	var $totalElement = jQuery( '.ginput_total_' + formId );
862	if ( ! $totalElement.length > 0 ) {
863		return;
864	}
865
866	/**
867	 * @function priceHasChanged
868	 * @description For legacy, compare numeric values, otherwise compare currency as that's what
869	 * the input stores as value.
870	 *
871	 * @param {Object} priceData
872	 * @returns {boolean}
873	 */
874	var priceHasChanged = function( priceData ) {
875		return isLegacy
876			? priceData.current !== priceData.new
877			: priceData.current !== priceData.newFormatted;
878	}
879
880	// Check whether this form is in legacy mode.
881	var isLegacy = document.querySelector( '#gform_wrapper_' + formId + '.gform_legacy_markup_wrapper' );
882	// Input is hidden in legacy mode and comes after span that displays value, currently only the input is present and visible.
883	var $totalInput = isLegacy ? $totalElement.next() : $totalElement;
884	// Contains current value (numeric or currency formatted), new numeric value and newFormatted value
885	var priceData = {
886		current: String( $totalInput.val() ),
887		new: String( price ),
888		newFormatted: gformFormatMoney( String( price ), true ),
889	}
890
891	// New value is the same as the current value, bail before updating.
892	if ( ! priceHasChanged( priceData ) ) {
893		return;
894	}
895
896	// Legacy field
897	if ( isLegacy ) {
898		// Set input value to numeric value and trigger a change event for any js listeners in conditional logic
899		// or third party integrations.
900		$totalInput.val( priceData.new ).trigger( 'change' );
901		// Inject span with currency value for display.
902		$totalElement.html( priceData.newFormatted );
903		return;
904	}
905
906	// First set the input to the numeric value and trigger the change event so that js listeners get the value in expected format.
907	$totalInput.val( priceData.new ).trigger( 'change' );
908	// Then set the input to the currency value for display. If you have a script that wants to get the value
909	// of this input without listening to the change event you will have to also handle removing the currency formatting
910	// if expecting number in your code.
911	$totalInput.val( priceData.newFormatted );
912}
913
914/**
915 * @deprecated next - Use gform.state.get( FORM_ID, 'products' ); to get access to the shipping amount.
916 *
917 * @remove-in 4.0
918 */
919function gformGetShippingPrice(formId){
920
921	console.warn( 'gformGetShippingPrice() has been deprecated. Use gform.state.get( FORM_ID, \'products\' ); to get access to the shipping amount.' );
922
923    var shippingField = jQuery(".gfield_shipping_" + formId + " input[readonly], .gfield_shipping_" + formId + " select, .gfield_shipping_" + formId + " input:checked");
924    var shipping = 0;
925    if(shippingField.length == 1 && !gformIsHidden(shippingField)){
926        if(shippingField.attr("readonly"))
927            shipping = shippingField.val();
928        else
929            shipping = gformGetPrice(shippingField.val());
930    }
931
932    return gformToNumber(shipping);
933}
934
935/**
936 * @deprecated next - No replacement. Product price calculation is now handled by the products module in assets.
937 *
938 * @remove-in 4.0
939 */
940function gformCalculateProductPrice(form_id, productFieldId){
941	console.warn( 'gformCalculateProductPrice() has been deprecated with no replacement.' );
942
943    var suffix = '_' + form_id + '_' + productFieldId;
944
945
946    //Drop down auto-calculating labels
947    jQuery('.gfield_option' + suffix + ', .gfield_shipping_' + form_id).find('select').each(function(){
948
949        var dropdown_field = jQuery(this);
950        var selected_price = gformGetPrice(dropdown_field.val());
951        var field_id = dropdown_field.attr('id').split('_')[2];
952        dropdown_field.children('option').each(function(){
953            var choice_element = jQuery(this);
954            var label = gformGetOptionLabel(choice_element, choice_element.val(), selected_price, form_id, field_id);
955            choice_element.html(label);
956        });
957    });
958
959
960    //Checkboxes labels with prices
961    jQuery('.gfield_option' + suffix).find('.gfield_checkbox').find('input:checkbox').each(function(){
962        var checkbox_item = jQuery(this);
963        var id = checkbox_item.attr('id');
964        var field_id = id.split('_')[2];
965        var label_id = id.replace('choice_', '#label_');
966        var label_element = jQuery(label_id);
967        var label = gformGetOptionLabel(label_element, checkbox_item.val(), 0, form_id, field_id);
968        label_element.html(label);
969    });
970
971
972    //Radio button auto-calculating lables
973    jQuery('.gfield_option' + suffix + ', .gfield_shipping_' + form_id).find('.gfield_radio').each(function(){
974        var selected_price = 0;
975        var radio_field = jQuery(this);
976        var id = radio_field.attr('id');
977        var fieldId = id.split('_')[2];
978        var selected_value = radio_field.find('input:radio:checked').val();
979
980        if(selected_value)
981            selected_price = gformGetPrice(selected_value);
982
983        radio_field.find('input:radio').each(function(){
984            var radio_item = jQuery(this);
985            var label_id = radio_item.attr('id').replace('choice_', '#label_');
986            var label_element = jQuery(label_id);
987            if ( label_element ) {
988                var label = gformGetOptionLabel(label_element, radio_item.val(), selected_price, form_id, fieldId);
989                label_element.html(label);
990            }
991        });
992    });
993
994	var price = gformGetBasePrice(form_id, productFieldId);
995	var quantity = gformGetProductQuantity( form_id, productFieldId );
996
997	//calculating options if quantity is more than 0 (a product was selected).
998	if( quantity > 0 ) {
999
1000		jQuery('.gfield_option' + suffix).find('input:checked, select').each(function(){
1001			if(!gformIsHidden(jQuery(this)))
1002				price += gformGetPrice(jQuery(this).val());
1003		});
1004
1005		//setting global variable if quantity is more than 0 (a product was selected). Will be used when calculating total
1006		_anyProductSelected = true;
1007	}
1008
1009    price = price * quantity;
1010
1011	price = gformRoundPrice(price) ;
1012
1013
1014    return price;
1015}
1016
1017/**
1018 * @deprecated next - Use gform.state.get( FORM_ID, 'products' ); to get access to the product quantity.
1019 *
1020 * @remove-in 4.0
1021 */
1022function gformGetProductQuantity(formId, productFieldId) {
1023	console.warn( 'gformGetProductQuantity() has been deprecated. Use gform.state.get( FORM_ID, \'products\' ); to get access to the product quantity.' );
1024
1025    //If product is not selected
1026    if (!gformIsProductSelected(formId, productFieldId)) {
1027        return 0;
1028    }
1029
1030    var quantity,
1031        quantityInput = jQuery( '#ginput_quantity_' + formId + '_' + productFieldId ),
1032        numberFormat;
1033
1034    // New input ID starts from 2.5, for the single product and calculation fields.
1035    if ( ! quantityInput.length ) {
1036        quantityInput = jQuery( '#input_' + formId + '_' + productFieldId + '_1' );
1037    }
1038
1039    if (gformIsHidden(quantityInput)) {
1040        return 0;
1041    }
1042
1043    if (quantityInput.length > 0) {
1044
1045        quantity = quantityInput.val();
1046
1047    } else {
1048
1049        quantityInput = jQuery('.gfield_quantity_' + formId + '_' + productFieldId + ' :input');
1050        quantity = 1;
1051
1052        if (quantityInput.length > 0) {
1053            quantity = quantityInput.val();
1054
1055            var htmlId = quantityInput.attr('id'),
1056                fieldId = gf_get_input_id_by_html_id(htmlId);
1057
1058            numberFormat = gf_get_field_number_format( fieldId, formId, 'value' );
1059        }
1060
1061    }
1062
1063    if (!numberFormat)
1064        numberFormat = 'currency';
1065
1066    var decimalSeparator = gform.Currency.getDecimalSeparator(numberFormat);
1067
1068    quantity = gform.Currency.cleanNumber(quantity, '', '', decimalSeparator);
1069    if (!quantity)
1070        quantity = 0;
1071
1072    return quantity;
1073}
1074
1075/**
1076 * @deprecated next - Use gform.state.get( FORM_ID, 'products' ); to get access to selected products.
1077 *
1078 * @remove-in 4.0
1079 */
1080function gformIsProductSelected( formId, productFieldId ) {
1081	console.warn( 'gformIsProductSelected() has been deprecated. Use gform.state.get( FORM_ID, \'products\' ); to get access to selected products.' );
1082
1083	var suffix = "_" + formId + "_" + productFieldId;
1084
1085	var productField = jQuery("#ginput_base_price" + suffix + ", .gfield_donation" + suffix + " input[type=\"text\"], .gfield_product" + suffix + " .ginput_amount");
1086	if( productField.val() && ! gformIsHidden(productField) ){
1087		return true;
1088	}
1089	else
1090	{
1091		productField = jQuery(".gfield_product" + suffix + " select, .gfield_product" + suffix + " input:checked, .gfield_donation" + suffix + " select, .gfield_donation" + suffix + " input:checked");
1092		if( productField.val() && ! gformIsHidden(productField) ){
1093			return true;
1094		}
1095	}
1096	return false;
1097}
1098
1099/**
1100 * @deprecated next - Use gform.state.get( FORM_ID, 'products' ); to get access to the product base price.
1101 *
1102 * @remove-in 4.0
1103 */
1104function gformGetBasePrice(formId, productFieldId){
1105	console.warn( 'gformGetBasePrice() has been deprecated. Use gform.state.get( FORM_ID, \'products\' ); to get access to the product base price.' );
1106
1107    var suffix = "_" + formId + "_" + productFieldId;
1108    var price = 0;
1109    var productField = jQuery("#ginput_base_price" + suffix+ ", .gfield_donation" + suffix + " input[type=\"text\"], .gfield_product" + suffix + " .ginput_amount");
1110    if(productField.length > 0){
1111        price = productField.val();
1112
1113        //If field is hidden by conditional logic, don't count it for the total
1114        if(gformIsHidden(productField)){
1115            price = 0;
1116        }
1117    }
1118    else
1119    {
1120        productField = jQuery(".gfield_product" + suffix + " select, .gfield_product" + suffix + " input:checked, .gfield_donation" + suffix + " select, .gfield_donation" + suffix + " input:checked");
1121        var val = productField.val();
1122        if(val){
1123            const value = gformParseChoiceValue( val );
1124            val = value.name;
1125            price = value.price || 0;
1126        }
1127
1128        //If field is hidden by conditional logic, don't count it for the total
1129        if(gformIsHidden(productField))
1130            price = 0;
1131
1132    }
1133
1134    var c = new gform.Currency(gf_global.gf_currency_config);
1135    price = c.toNumber(price);
1136    return price === false ? 0 : price;
1137}
1138
1139/**
1140 * @function gformParseChoiceValue
1141 * @description Parse a choice value into its name and price components.
1142 *
1143 * @since 2.9.30
1144 *
1145 * @param {string} value The choice value string in the format "name|price".
1146 *
1147 * @return {object} Returns an object in the format: { price: PRODUCT_PRICE, name: PRODUCT_NAME }
1148 */
1149function gformParseChoiceValue( value ) {
1150	if ( window.gform?.products?.parser?.parseChoiceValue ) {
1151		return window.gform.products.parser.parseChoiceValue( value );
1152	}
1153
1154	if ( ! value ) {
1155		return { name: null, price: null };
1156	}
1157
1158	const idx = value.lastIndexOf( '|' );
1159	if ( idx === -1 ) {
1160		return { name: value, price: null };
1161	}
1162
1163	const name = value.slice( 0, idx );
1164	const price = gformToNumber( value.slice( idx + 1 ) );
1165
1166	return { name, price };
1167}
1168
1169/**
1170 * @deprecated next - No replacement. Price fields are now automatically formatted by the products module in assets.
1171 *
1172 * @remove-in 4.0
1173 */
1174function gformFormatPricingField(element){
1175	console.warn( 'gformFormatPricingField() has been deprecated with no replacement. Price fields are now automatically formatted by the products module in assets.' );
1176    if(gf_global.gf_currency_config){
1177        var currency = new gform.Currency(gf_global.gf_currency_config);
1178        var price = currency.toMoney(jQuery(element).val());
1179        jQuery(element).val(price);
1180    }
1181}
1182
1183/**
1184 * @deprecated next - No replacement. Product price calculation is now handled by the products module in assets.
1185 *
1186 * @remove-in 4.0
1187 */
1188function gformGetPriceDifference(currentPrice, newPrice){
1189
1190    //getting price difference
1191    var diff = parseFloat(newPrice) - parseFloat(currentPrice);
1192    price = gformFormatMoney(diff, true);
1193    if(diff > 0)
1194        price = "+" + price;
1195
1196    return price;
1197}
1198
1199/**
1200 * @deprecated next - No replacement. Product price calculation is now handled by the products module in assets.
1201 *
1202 * @remove-in 4.0
1203 */
1204function gformGetOptionLabel(element, selected_value, current_price, form_id, field_id){
1205	console.warn('gformGetOptionLabel() has been deprecated with no replacement. Product price calculation is now handled by the products module in assets.' );
1206    element = jQuery(element);
1207    var price = gformGetPrice(selected_value);
1208    var current_diff = element.attr('price');
1209    var original_label = element.html().replace(/<span(.*)<\/span>/i, "").replace(current_diff, "");
1210
1211    var diff = gformGetPriceDifference(current_price, price);
1212    diff = gformToNumber(diff) == 0 ? "" : " " + diff;
1213    element.attr('price', diff);
1214
1215    //don't add <span> for drop down items (not supported)
1216    var price_label = element[0].tagName.toLowerCase() == "option" ? diff : "<span class='ginput_price'>" + diff + "</span>";
1217    var label = original_label + price_label;
1218
1219    //calling hook to allow for custom option formatting
1220    if(window["gform_format_option_label"])
1221        label = gform_format_option_label(label, original_label, price_label, current_price, price, form_id, field_id);
1222
1223    return label;
1224}
1225
1226/**
1227 * @deprecated next - No replacement. Product price calculation is now handled by the products module in assets.
1228 *
1229 * @remove-in 4.0
1230 */
1231function gformGetProductIds(parent_class, element){
1232	console.warn('gformGetProductIds() has been deprecated with no replacement. Product price calculation is now handled by the products module in assets.' );
1233    var classes = jQuery(element).hasClass(parent_class) ? jQuery(element).attr("class").split(" ") : jQuery(element).parents("." + parent_class).attr("class").split(" ");
1234    for(var i=0; i<classes.length; i++){
1235        if(classes[i].substr(0, parent_class.length) == parent_class && classes[i] != parent_class)
1236            return {formId: classes[i].split("_")[2], productFieldId: classes[i].split("_")[3]};
1237    }
1238    return {formId:0, fieldId:0};
1239}
1240
1241/**
1242 * @deprecated next - No replacement. Product price calculation is now handled by the products module in assets.
1243 *
1244 * @remove-in 4.0
1245 */
1246function gformGetPrice(text){
1247	console.warn('gformGetPrice() has been deprecated with no replacement. Product price calculation is now handled by the products module in assets.' );
1248    var val = gformParseChoiceValue( text );
1249
1250    if(val.price)
1251         return val.price;
1252
1253    return 0;
1254}
1255
1256/**
1257 * @deprecated next - No replacement. Price fields are now automatically registered by the products module in assets.
1258 *
1259 * @remove-in 4.0
1260 */
1261function gformRegisterPriceField(item){
1262	console.warn( 'gformRegisterPriceField() has been deprecated with no replacement. Price fields are now automatically registered by the products module in assets.' );
1263
1264	if( ! item.formId ) {
1265		return;
1266	}
1267
1268    if(!_gformPriceFields[item.formId]) {
1269		_gformPriceFields[item.formId] = new Array();
1270	}
1271
1272    //ignore price fields that have already been registered
1273    for(var i=0; i<_gformPriceFields[item.formId].length; i++)
1274        if(_gformPriceFields[item.formId][i] == item.productFieldId)
1275            return;
1276
1277    //registering new price field
1278    _gformPriceFields[item.formId].push(item.productFieldId);
1279}
1280
1281/**
1282 * @deprecated next - No replacement. Price fields are now automatically initialized by the products module in assets.
1283 *
1284 * @remove-in 4.0
1285 */
1286function gformInitPriceFields(){
1287	console.warn( 'gformInitPriceFields() has been deprecated with no replacement. Price fields are now automatically initialized by the products module in assets.' );
1288
1289	// Getting all product fields and registering them.
1290    const priceFields = gform.tools.getNodes('.gfield_price', true, document, true );
1291	priceFields.forEach( ( field ) => {
1292		const productIds = gformGetProductIds( 'gfield_price', field );
1293		gformRegisterPriceField( productIds );
1294	});
1295
1296
1297	// Getting all forms that have product fields.
1298	const formIds = Object.keys( _gformPriceFields );
1299	formIds.forEach( ( formId ) => {
1300		gformCalculateTotalPrice( formId );
1301	} );
1302
1303	bindProductChangeEvent();
1304}
1305
1306/**
1307 * @deprecated next - No replacement. gform_price_change will be removed in 3.0 in favor of gform/products/product_field_changed, so this method isn't needed.
1308 *
1309 * @remove-in 3.0
1310 */
1311function bindProductChangeEvent() {
1312	// For backwards compatibility, fire jQuery gform_price_change event.
1313	document.addEventListener( 'gform/products/product_field_changed', function( event ) {
1314		const productIds = { formId : event.detail.formId, productFieldId : event.detail.productFieldId }
1315
1316		/**
1317		 * @deprecated next. Use gform/products/product_field_changed instead.
1318		 *
1319		 * @remove-in 3.0
1320		 */
1321		jQuery( document ).trigger( 'gform_price_change', [ productIds, event.detail.htmlInput, this ] );
1322	} );
1323}
1324
1325//-------------------------------------------
1326//---------- PASSWORD -----------------------
1327//-------------------------------------------
1328function gformShowPasswordStrength(fieldEl){
1329	if ( ! fieldEl || typeof fieldEl.closest !== 'function' ) {
1330		return;
1331	}
1332
1333	var wrapper = fieldEl.closest('.gfield--type-password');
1334	if ( ! wrapper ) {
1335		return;
1336	}
1337
1338	var passwordEl = wrapper.querySelector('.password_input_container input');
1339	var confirmEl = wrapper.querySelectorAll('.password_input_container input')[1] || null;
1340	var strengthEl = wrapper.querySelector('input[name*="strength"]');
1341	var strengthIndicatorEl = wrapper.querySelector('.gfield_password_strength');
1342
1343    var password = passwordEl.value;
1344    var confirm = confirmEl !== null ? confirmEl.value : '';
1345
1346    var result = gformPasswordStrength( password, confirm ),
1347        resultClass = result === 'unknown' ? 'blank' : result;
1348
1349    if (strengthEl) {
1350        strengthEl.value = result;
1351    }
1352    if (strengthIndicatorEl) {
1353        strengthIndicatorEl.classList.remove("blank", "mismatch", "short", "good", "bad", "strong");
1354        strengthIndicatorEl.classList.add(resultClass);
1355		var key = "password_" + result;
1356		var text = ( window.gf_text && key in window.gf_text ) ? window.gf_text[ key ] : '';
1357        strengthIndicatorEl.innerHTML = text;
1358    }
1359}
1360
1361// Password strength meter
1362function gformPasswordStrength( password1, password2 ) {
1363
1364    if ( password1.length <= 0 ) {
1365        return 'blank';
1366    }
1367
1368	var disallowedList = wp.passwordStrength.hasOwnProperty( 'userIn
1368putDisallowedList' ) ? wp.passwordStrength.userInputDisallowedList() : wp.passwordStrength.userInputBlacklist(),
1369	    strength = wp.passwordStrength.meter( password1, disallowedList, password2 );
1370
1371    switch ( strength ) {
1372
1373        case -1:
1374            return 'unknown';
1375
1376        case 2:
1377            return 'bad';
1378
1379        case 3:
1380            return 'good';
1381
1382        case 4:
1383            return 'strong';
1384
1385        case 5:
1386            return 'mismatch';
1387
1388        default:
1389            return 'short';
1390
1391    }
1392
1393}
1394
1395function gformToggleShowPassword(buttonEl) {
1396    var passwordContainer = buttonEl.closest('.password_input_container');
1397    if (!passwordContainer) return;
1398
1399    var passwordInput = passwordContainer.querySelector('input');
1400    if (!passwordInput) return;
1401
1402    var icon = buttonEl.querySelector('span');
1403    var currentType = passwordInput.getAttribute('type');
1404
1405    switch (currentType) {
1406        case 'password':
1407            passwordInput.setAttribute('type', 'text');
1408            buttonEl.setAttribute('aria-label', buttonEl.getAttribute('data-label-hide'));
1409            icon.classList.remove('dashicons-hidden');
1410            icon.classList.add('dashicons-visibility');
1411            break;
1412        case 'text':
1413            passwordInput.setAttribute('type', 'password');
1414            buttonEl.setAttribute('aria-label', buttonEl.getAttribute('data-label-show'));
1415            icon.classList.remove('dashicons-visibility');
1416            icon.classList.add('dashicons-hidden');
1417            break;
1418    }
1419}
1420
1421//----------------------------
1422//------ CHECKBOX FIELD ------
1423//----------------------------
1424
1425function gformToggleCheckboxes( toggleElement ) {
1426
1427	var checked,
1428        $toggleElement        = jQuery( toggleElement ),
1429        toggleElementCheckbox = $toggleElement.is( 'input[type="checkbox"]' ),
1430        $toggle               = $toggleElement.parent(),
1431	    $toggleLabel          = $toggle.find( 'label' ),
1432	    $checkboxes           = $toggle.parent().find( '.gchoice:not( .gchoice_select_all )' ),
1433	    formId         = gf_get_form_id_by_html_id( $toggle.parents( '.gfield' ).attr( 'id' ) ),
1434	    calcObj               = rgars( window, 'gf_global/gfcalc/' + formId );
1435
1436    // Determine checked state.
1437    if ( toggleElementCheckbox ) {
1438
1439        checked = toggleElement.checked;
1440
1441    } else {
1442
1443        // Get checked data.
1444        var checkedData = $toggleElement.data( 'checked' );
1445
1446        if ( typeof checkedData === 'boolean' ) {
1447            checked = !checkedData;
1448        } else {
1449            checked = !( parseInt( checkedData ) === 1 )
1450        }
1451
1452    }
1453
1454    // Set checkboxes state.
1455	$checkboxes.each( function() {
1456
1457		// Set checkbox checked state.
1458		jQuery( 'input[type="checkbox"]', this ).prop( 'checked', checked ).trigger( 'change' );
1459
1460		// Execute onclick event.
1461		if ( typeof jQuery( 'input[type="checkbox"]', this )[0].onclick === 'function' ) {
1462			jQuery( 'input[type="checkbox"]', this )[0].onclick();
1463		}
1464
1465	} );
1466
1467	// Change toggle label, checked state.
1468	gformToggleSelectAll( toggleElement, checked ? 'deselect' : 'select' );
1469
1470    // Announce change.
1471    wp.a11y.speak( checked ? gf_field_checkbox.strings.selected : gf_field_checkbox.strings.deselected );
1472
1473	if ( calcObj ) {
1474		calcObj.runCalcs( formId, calcObj.formulaFields );
1475	}
1476
1477}
1478
1479function gformToggleSelectAll( selectAllElement, action ) {
1480	var $selectAllElement = jQuery( selectAllElement ),
1481		toggleElementCheckbox = $selectAllElement.is( 'input[type="checkbox"]' ),
1482		$toggle               = toggleElementCheckbox ? $selectAllElement.parent() : $selectAllElement.prev(),
1483		$toggleLabel          = $toggle.find( 'label' );
1484
1485	if ( ! toggleElementCheckbox ) {
1486		$selectAllElement.html( action === 'deselect' ? $selectAllElement.data( 'label-deselect' ) : $selectAllElement.data( 'label-select' ) );
1487		$selectAllElement.data( 'checked', action === 'deselect' ? 1 : 0 );
1488	}
1489}
1490
1491jQuery(document).on('click', '.gfield_choice--select_all_enabled *', function() {
1492	var $select_all = jQuery( this ).closest( '.gfield_choice--select_all_enabled' ).find( '.gfield_choice_all_toggle' );
1493
1494	// if any of the checkboxes are unchecked, turn the "deselect all" button/checkbox into a "select all" button/checkbox
1495	if ( jQuery( this ).is( '.gchoice input[type="checkbox"]' ) ) {
1496		if( $select_all.is( 'input[type="checkbox"]' ) ) {
1497			if ( !jQuery( this ).prop( 'checked' ) ) {
1498				$select_all.prop( 'checked', false );
1499			}
1500		} else {
1501			gformToggleSelectAll( $select_all, 'select' );
1502		}
1503	}
1504
1505	// if all checkboxes that are not the "select all" checkbox are checked, turn the "select all" button/checkbox into a "deselect all" button/checkbox
1506	if ( jQuery( this ).is( '.gchoice input[type="checkbox"]' ) ) {
1507		var $checkboxes = jQuery( this ).closest( '.gfield_choice--select_all_enabled' ).find( '.gchoice input[type="checkbox"]:not(".gfield_choice_all_toggle")' );
1508		if ( $checkboxes.length === $checkboxes.filter( ':checked' ).length ) {
1509			if( $select_all.is( 'input[type="checkbox"]' ) ) {
1510				$select_all.prop( 'checked', true );
1511				gformToggleSelectAll( $select_all, 'deselect' );
1512			} else {
1513				gformToggleSelectAll( $select_all, 'deselect' );
1514			}
1515		}
1516	}
1517
1518});
1519
1520//----------------------------
1521//------ RADIO FIELD ------
1522//----------------------------
1523
1524function gformToggleRadioOther( radioElement ) {
1525
1526    // Get Other input element.
1527    var $other = gform.tools.getClosest( radioElement, '.ginput_container_radio' ).querySelector( 'input.gchoice_other_control' );
1528
1529    if ( $other ) {
1530        $other.disabled = radioElement.value !== 'gf_other_choice';
1531    }
1532
1533}
1534
1535//----------------------------
1536//------ LIST FIELD ----------
1537//----------------------------
1538
1539function gformAddListItem( addButton, max ) {
1540
1541    var $addButton = jQuery( addButton );
1542
1543    if( $addButton.hasClass( 'gfield_icon_disabled' ) ) {
1544        return;
1545    }
1546
1547    var $group     = $addButton.parents( '.gfield_list_group' ),
1548        $clone     = $group.clone(),
1549        $container = $group.parents( '.gfield_list_container' ),
1550        tabindex   = $clone.find( ':input:last' ).attr( 'tabindex' );
1551
1552    // reset all inputs to empty state
1553    $clone
1554        .find( 'input, select, textarea' ).attr( 'tabindex', tabindex )
1555        .not( ':checkbox, :radio' ).val( '' ).attr( 'value', '' );
1556    $clone.find( ':checkbox, :radio' ).prop( 'checked', false );
1557
1558    $clone = gform.applyFilters( 'gform_list_item_pre_add', $clone, $group );
1559
1560    $group.after( $clone );
1561
1562    gformToggleIcons( $container, max );
1563    gformAdjustClasses( $container );
1564    gformAdjustRowAttributes( $container );
1565
1566    gform.doAction( 'gform_list_post_item_add', $clone, $container );
1567
1568    wp.a11y.speak( window.gf_global.strings.newRowAdded );
1569
1570}
1571
1572function gformDeleteListItem( deleteButton, max ) {
1573
1574	var $deleteButton = jQuery( deleteButton );
1575	if ( $deleteButton.prop( 'disabled' ) ) {
1576		return;
1577	} else {
1578		$deleteButton.prop( 'disabled', true );
1579	}
1580
1581	var $group     = $deleteButton.parents( '.gfield_list_group' ),
1582		$container = $group.parents( '.gfield_list_container' );
1583
1584    $group.remove();
1585
1586    gformToggleIcons( $container, max );
1587    gformAdjustClasses( $container );
1588    gformAdjustRowAttributes( $container );
1589
1590    gform.doAction( 'gform_list_post_item_delete', $container );
1591
1592    wp.a11y.speak( window.gf_global.strings.rowRemoved );
1593
1594}
1595
1596function gformAdjustClasses( $container ) {
1597
1598    var $groups = $container.find( '.gfield_list_group' );
1599
1600    $groups.each( function( i ) {
1601
1602        var $group       = jQuery( this ),
1603            oddEvenClass = ( i + 1 ) % 2 == 0 ? 'gfield_list_row_even' : 'gfield_list_row_odd';
1604
1605        $group.removeClass( 'gfield_list_row_odd gfield_list_row_even' ).addClass( oddEvenClass );
1606
1607    } );
1608
1609}
1610
1611function gformAdjustRowAttributes( $container ) {
1612
1613    if( $container.parents( '.gform_wrapper' ).hasClass( 'gform_legacy_markup_wrapper' ) ) {
1614        return;
1615    }
1616
1617    $container.find( '.gfield_list_group' ).each( function( i ) {
1618
1619        var $input = jQuery( this ).find( 'input, select, textarea' );
1620        $input.each( function( index, input ) {
1621            var $this = jQuery( input );
1622            $this.attr( 'aria-label', $this.data( 'aria-label-template' ).gformFormat( i + 1 ) );
1623        } );
1624
1625        var $remove = jQuery( this ).find( '.delete_list_item' );
1626        $remove.attr( 'aria-label', $remove.data( 'aria-label-template' ).gformFormat( i + 1 ) );
1627
1628    } );
1629
1630}
1631
1632function gformToggleIcons( $container, max ) {
1633
1634    var groupCount  = $container.find( '.gfield_list_group' ).length,
1635        $addButtons = $container.find( '.add_list_item' ),
1636        isLegacy    =  typeof gf_legacy !== 'undefined' && gf_legacy.is_legacy;
1637
1638	if ( groupCount === 1 ) {
1639		$container.find( '.delete_list_item' ).prop( 'disabled', true ).css( 'visibility', 'hidden' );
1640	} else {
1641		$container.find( '.delete_list_item' ).prop( 'disabled', false ).css( 'visibility', 'visible' );
1642	}
1643
1644    if ( max > 0 && groupCount >= max ) {
1645
1646        // store original title in the add button
1647        $addButtons.data( 'title', $container.find( '.add_list_item' ).attr( 'title' ) );
1648        $addButtons.addClass( 'gfield_icon_disabled' ).attr( 'title', '' );
1649
1650		if ( ! isLegacy ) {
1651			$addButtons.prop( 'disabled', true );
1652		}
1653
1654    } else if( max > 0 ) {
1655
1656        $addButtons.removeClass( 'gfield_icon_disabled' );
1657
1658	    if ( ! isLegacy ) {
1659		    $addButtons.prop( 'disabled', false );
1660	    }
1661
1662        if( $addButtons.data( 'title' ) )   {
1663            $addButtons.attr( 'title', $addButtons.data( 'title' ) );
1664        }
1665
1666    }
1667}
1668
1669//-----------------------------------
1670//------ CREDIT CARD FIELD ----------
1671//-----------------------------------
1672function gformMatchCard(id) {
1673
1674    var cardType = gformFindCardType(jQuery('#' + id).val());
1675    var cardContainer = jQuery('#' + id).parents('.gfield').find('.gform_card_icon_container');
1676
1677    if(!cardType) {
1678
1679        jQuery(cardContainer).find('.gform_card_icon').removeClass('gform_card_icon_selected gform_card_icon_inactive');
1680
1681    } else {
1682
1683        jQuery(cardContainer).find('.gform_card_icon').removeClass('gform_card_icon_selected').addClass('gform_card_icon_inactive');
1684        jQuery(cardContainer).find('.gform_card_icon_' + cardType).removeClass('gform_card_icon_inactive').addClass('gform_c
1684ard_icon_selected');
1685    }
1686}
1687
1688function gformFindCardType(value) {
1689
1690    if(value.length < 4)
1691        return false;
1692
1693    var rules = window['gf_cc_rules'];
1694    var validCardTypes = new Array();
1695
1696    for(type in rules) {
1697
1698        //needed when implementing for in loops
1699        if(!rules.hasOwnProperty(type))
1700            continue;
1701
1702
1703        for(i in rules[type]) {
1704
1705            if(!rules[type].hasOwnProperty(i))
1706                continue;
1707
1708            if(rules[type][i].indexOf(value.substring(0, rules[type][i].length)) === 0) {
1709                validCardTypes[validCardTypes.length] = type;
1710                break;
1711            }
1712
1713        }
1714    }
1715
1716    return validCardTypes.length == 1 ? validCardTypes[0].toLowerCase() : false;
1717}
1718
1719function gformToggleCreditCard(){
1720    if(jQuery("#gform_payment_method_creditcard").is(":checked"))
1721        jQuery(".gform_card_fields_container").slideDown();
1722    else
1723        jQuery(".gform_card_fields_container").slideUp();
1724}
1725
1726
1727//----------------------------------------
1728//------ CHOSEN DROP DOWN FIELD ----------
1729//----------------------------------------
1730
1731function gformInitChosenFields( fieldList, noResultsText ) {
1732    return jQuery( fieldList ).each( function(){
1733		var element = jQuery( this );
1734
1735		// Cloned (repeater) fields will have noResultsText set to undefined, so we need to cache the original value.
1736		noResultsText = noResultsText || element.data('noResultsText');
1737		element.attr('data-noResultsText', noResultsText);
1738
1739	    var isConvoForm = typeof gfcf_theme_config !== 'undefined' ? ( gfcf_theme_config !== null && typeof gfcf_theme_config.data !== 'undefined' ? gfcf_theme_config.data.is_conversational_form : undefined ) : false;
1740
1741        // RTL support
1742        if( jQuery( 'html' ).attr( 'dir' ) == 'rtl' ) {
1743            element.addClass( 'chosen-rtl chzn-rtl' );
1744        }
1745
1746        // only initialize once
1747        if( ( element.is( ':visible' ) || isConvoForm ) && element.siblings( '.chosen-container' ).length == 0 ) {
1748			var chosenOptions = { no_results_text: noResultsText };
1749			if ( isConvoForm ) {
1750				chosenOptions.width = element.css( 'inline-size' );
1751			}
1752            var options = gform.applyFilters( 'gform_chosen_options', chosenOptions, element );
1753            element.chosen( options );
1754        }
1755    });
1756}
1757
1758//----------------------------------------
1759//--- CURRENCY FORMAT NUMBER FIELD -------
1760//----------------------------------------
1761
1762function gformInitCurrencyFormatFields(fieldList){
1763    jQuery(fieldList).each(function(){
1764        var $this = jQuery(this);
1765        $this.val( gformFormatMoney( jQuery(this).val() ) );
1766    }).change( function( event ) {
1767            jQuery(this).val( gformFormatMoney( jQuery(this).val() ) );
1768        });
1769}
1770
1771
1772
1773//----------------------------------------
1774//------ JS MERGE TAGS -------------------
1775//----------------------------------------
1776
1777/**
1778 * @var {Object} GFMergeTag Handles MergeTag Operations.
1779 * @remove-in 4.0
1780 * @deprecated Use gform.mergeTags instead.
1781 */
1782var GFMergeTag = function() {
1783	/**
1784     * Gets the merge tag value for the specified input Id
1785	 * @param formId  The current form Id
1786	 * @param inputId The input Id to get the merge tag from. This could be a field id (i.e. 1) or a specific input Id for multi-input fields (i.e. 1.2)
1787	 * @param modifier The merge tag modifier to be used. i.e. value, currency, price, etc...
1788	 * @returns       Returns a string containing the merge tag value for the specified input Id
1789     * @remove-in 4.0
1790	 * @deprecated Use gform.mergeTags.getFieldValue() instead.
1791	 */
1792	GFMergeTag.getMergeTagValue = function( formId, inputId, modifier ) {
1793
1794		const mergeTagInfo = gform.mergeTags.getMergeTagInfo( formId, inputId, modifier );
1795
1796		if ( ! mergeTagInfo.isVisible ) {
1797			return '';
1798		}
1799
1800		const inputForFilter = jQuery( mergeTagInfo.input );
1801		let value = window.gform.applyFilters( 'gform_value_merge_tag_' + formId + '_' + mergeTagInfo.fieldId, false, inputForFilter, mergeTagInfo.modifier );
1802		if ( value !== false ) {
1803			return value;
1804		}
1805
1806		return gform.mergeTags.getFieldValue( formId, inputId, modifier, mergeTagInfo );
1807	}
1808
1809	/**
1810     * Parses the specified text for merge tags, and replaces all of them with the appropriate merge tag values. Returns the resulting string
1811	 * @param formId    The current form Id
1812	 * @param text      The text containing merge tags
1813	 * @returns         Retuns the original "text" strings with all merge tags replaced with the appropriate merge tag values
1814     * @remove-in 4.0
1815	 * @deprecated Use gform.mergeTags.replaceMergeTags() instead.
1816	 */
1817	GFMergeTag.replaceMergeTags = function( formId, text ) {
1818		return gform.mergeTags.replaceMergeTags( formId, text );
1819	}
1820
1821	/**
1822	 * @deprecated Use gform.mergeTags.formatValue() instead.
1823     * @remove-in 4.0
1824	 */
1825	GFMergeTag.formatValue = function( value, modifier ) {
1826		return gform.mergeTags.formatValue( value, modifier );
1827	}
1828
1829	/**
1830     * Parses the merge tags in the specified text and returns an array of all the matched merge tags
1831	 *
1832	 * @param text  The text with merge tags to be parsed
1833	 * @param regEx The regular expression to be used to parse for merge tags.
1834	 *
1835	 * @returns Returns an array with all the merge tags that were matched in the original text
1836	 * @deprecated Use gform.mergeTags.parseMergeTags() instead.
1837     * @remove-in 4.0
1838	 */
1839	GFMergeTag.parseMergeTags = function( text, regEx ) {
1840		return gform.mergeTags.parseMergeTags( text, regEx );
1841	}
1842}
1843
1844new GFMergeTag();
1845
1846
1847//----------------------------------------
1848//------ CALCULATION FUNCTIONS -----------
1849//----------------------------------------
1850
1851var GFCalc = function(formId, formulaFields){
1852
1853	this.formId = formId;
1854	this.formulaFields = formulaFields;
1855
1856    this.exprPatt = /^[0-9 -/*\(\)]+$/i;
1857    this.isCalculating = {};
1858
1859    this.init = function(formId, formulaFields) {
1860
1861        var calc = this;
1862
1863        // @since 2.5.10 - namespace event to avoid multiple bindings.
1864	    jQuery(document)
1865		    .off("gform_post_conditional_logic.gfCalc_{0}".gformFormat(formId))
1866		    .on("gform_post_conditional_logic.gfCalc_{0}".gformFormat(formId), function(){
1867			    calc.runCalcs( formId, formulaFields );
1868	    } );
1869
1870        for(var i=0; i<formulaFields.length; i++) {
1871            var formulaField = jQuery.extend({}, formulaFields[i]);
1872            this.runCalc(formulaField, formId);
1873            this.bindCalcEvents(formulaField, formId);
1874        }
1875
1876    }
1877
1878    this.runCalc = function(formulaField, formId) {
1879        var calcObj      = this,
1880            field        = jQuery('#field_' + formId + '_' + formulaField.field_id),
1881            formulaInput = field.hasClass( 'gfield_price' ) ? jQuery( '#ginput_base_price_' + formId + '_' + formulaField.field_id ) : jQuery( '#input_' + formId + '_' + formulaField.field_id ),
1882            previous_val = formulaInput.val(),
1883            formula      = gform.applyFilters( 'gform_calculation_formula', formulaField.formula, formulaField, formId, calcObj ),
1884            expr         = calcObj.replaceFieldTags( formId, formula, formulaField ).replace(/(\r\n|\n|\r)/gm,""),
1885            result       = '';
1886
1887        if(calcObj.exprPatt.test(expr)) {
1888            try {
1889
1890                //run calculation
1891                result = eval(expr);
1892
1893            } catch( e ) { }
1894        } else {
1895        	return;
1896        }
1897
1898        // if result is positive infinity, negative infinity or a NaN, defaults to 0
1899        if( ! isFinite( result ) )
1900            result = 0;
1901
1902        // allow users to modify result with their own function
1903        if( window["gform_calculation_result"] ) {
1904            result = window["gform_calculation_result"](result, formulaField, formId, calcObj);
1905            if( window.console )
1906                console.log( '"gform_calculation_result" function is deprecated since version 1.8! Use "gform_calculation_result" JS hook instead.' );
1907        }
1908
1909        // allow users to modify result with their own function
1910        result = gform.applyFilters( 'gform_calculation_result', result, formulaField, formId, calcObj );
1911
1912        // allow result to be custom formatted
1913        var formattedResult = gform.applyFilters( 'gform_calculation_format_result', false, result, formulaField, formId, calcObj );
1914
1915        var numberFormat = gf_get_field_number_format(formulaField.field_id, formId);
1916
1917        //formatting number
1918        if( formattedResult !== false) {
1919            result = formattedResult;
1920        }
1921        else if( field.hasClass( 'gfield_price' ) || numberFormat == "currency") {
1922
1923            result = gformFormatMoney(result ? result : 0, true);
1924        }
1925        else {
1926
1927            var decimalSeparator = ".";
1928            var thousandSeparator = ",";
1929
1930            if(numberFormat == "decimal_comma"){
1931                decimalSeparator = ",";
1932                thousandSeparator = ".";
1933            }
1934
1935            result = gformFormatNumber(result, !gform.utils.isNumber(formulaField.rounding) ? -1 : formulaField.rounding, decimalSeparator, thousandSeparator);
1936        }
1937
1938        //If value doesn't change, abort.
1939        //This is needed to prevent an infinite loop condition with conditional logic
1940        if( result == previous_val )
1941            return;
1942
1943        // if this is a calculation product, handle differently
1944        if(field.hasClass('gfield_price')) {
1945            jQuery('#input_' + formId + '_' + formulaField.field_id).text(result);
1946
1947			// Firing jQuery change event for backwards compatibility with legacy code.
1948			formulaInput.val(result).trigger('change');
1949
1950			// Firing native change event for compatibility with new code in JS bundle.
1951			if ( formulaInput && formulaInput.length > 0 ) {
1952				window.gform.utils.trigger( { event: 'change', el: formulaInput[0], native: true } );
1953			}
1954
1955            // Announce the price change of the product only if there's no Total field.
1956            if ( jQuery( '.gfield_label_product' ).length && ! jQuery( '.ginput_total' ).length ) {
1957                result = jQuery( 'label[ for=input_' + formId + '_' + formulaField.field_id + '_1 ]' ).find( '.gfield_label_product' ).text() + ' ' + result;
1958                wp.a11y.speak( result );
1959            }
1960        } else {
1961            formulaInput.val(result).trigger('change');
1962        }
1963
1964    };
1965
1966    this.runCalcs = function( formId, formulaFields ) {
1967	    for(var i=0; i<formulaFields.length; i++) {
1968		    var formulaField = jQuery.extend({}, formulaFields[i]);
1969		    this.runCalc( formulaField, formId );
1970	    }
1971    }
1972
1973    this.bindCalcEvents = function(formulaField, formId) {
1974
1975        var calcObj = this;
1976        var formulaFieldId = formulaField.field_id;
1977        var matches = GFMergeTag.parseMergeTags( formulaField.formula );
1978
1979        calcObj.isCalculating[formulaFieldId] = false;
1980
1981        for(var i in matches) {
1982
1983            if(! matches.hasOwnProperty(i))
1984                continue;
1985
1986            var inputId = matches[i][1];
1987            var fieldId = parseInt(inputId,10);
1988            var input = jQuery('#field_' + formId + '_' + fieldId).find('input[name="input_' + inputId + '"], select[name="input_' + inputId + '"]');
1989
1990            if(input.prop('type') == 'checkbox' || input.prop('type') == 'radio') {
1991                jQuery(input).click(function(){
1992                    calcObj.bindCalcEvent(inputId, formulaField, formId, 0);
1993                });
1994                // Bind calc event to the image in an image choice field.
1995                var imageChoice = input.closest('.gfield--type-image_choice .gchoice');
1996                if ( imageChoice.length > 0 ) {
1997                    jQuery(imageChoice).click(function(){
1998                    	calcObj.bindCalcEvent(inputId, formulaField, formId, 0);
1999                    });
2000                }
2001            } else if(input.is('select') || input.prop('type') == 'hidden') {
2002                jQuery(input).change(function(){
2003                    calcObj.bindCalcEvent(inputId, formulaField, formId, 0);
2004                });
2005            } else {
2006                jQuery(input).keydown(function(){
2007                    calcObj.bindCalcEvent(inputId, formulaField, formId);
2008                }).change(function(){
2009                    calcObj.bindCalcEvent(inputId, formulaField, formId, 0);
2010                });
2011            }
2012
2013            // allow users to add custom methods for triggering calculations
2014            gform.doAction( 'gform_post_calculation_events', matches[i], formulaField, formId, calcObj );
2015
2016        }
2017
2018    }
2019
2020    this.bindCalcEvent = function(inputId, formulaField, formId, delay) {
2021
2022        var calcObj = this;
2023        var formulaFieldId = formulaField.field_id;
2024
2025        delay = delay == undefined ? 345 : delay;
2026
2027        if(calcObj.isCalculating[formulaFieldId][inputId])
2028            clearTimeout(calcObj.isCalculating[formulaFieldId][inputId]);
2029
2030        calcObj.isCalculating[formulaFieldId][inputId] = window.setTimeout(function() {
2031            calcObj.runCalc(formulaField, formId);
2032        }, delay);
2033
2034    }
2035
2036    this.replaceFieldTags = function( formId, expr, formulaField ) {
2037
2038        var matches = GFMergeTag.parseMergeTags( expr );
2039
2040        for(i in matches) {
2041
2042            if(! matches.hasOwnProperty(i))
2043                continue;
2044
2045            var inputId = matches[i][1];
2046            var fieldId = parseInt(inputId,10);
2047
2048            if ( fieldId == formulaField.field_id && fieldId == inputId ) {
2049            	continue;
2050            }
2051
2052            var modifier = 'value';
2053			if( matches[i][3] ){
2054				modifier = matches[i][3];
2055			}
2056			else {
2057				var is_product_radio =  jQuery('.gfield_price input[name=input_' + fieldId + ']').is('input[type=radio]');
2058                var is_product_dropdown = jQuery('.gfield_price select[name=input_' + fieldId + ']').length > 0;
2059                var is_option_checkbox = jQuery('.gfield_price input[name="input_' + inputId + '"]').is('input[type=checkbox]');
2060
2061                if( is_product_dropdown || is_product_radio || is_option_checkbox ) {
2062					modifier = 'price';
2063				}
2064			}
2065
2066			var isVisible = window['gf_check_field_rule'] ? gf_check_field_rule( formId, fieldId, true, '' ) == 'show' : true;
2067
2068			var value = isVisible ? GFMergeTag.getMergeTagValue( formId, inputId, modifier ) : 0;
2069
2070            // allow users to modify value with their own function
2071            value = gform.applyFilters( 'gform_merge_tag_value_pre_calculation', value, matches[i], isVisible, formulaField, formId );
2072
2073            value = this.cleanNumber( value, formId, fieldId, formulaField );
2074
2075            expr = expr.replace( matches[i][0], value );
2076        }
2077
2078        return expr;
2079    }
2080
2081	this.cleanNumber = function ( value, formId, fieldId, formulaField ) {
2082
2083		var numberFormat = gf_get_field_number_format( fieldId, formId );
2084
2085		if( ! numberFormat ) {
2086			numberFormat = gf_get_field_number_format(formulaField.field_id, formId);
2087		}
2088
2089		var decimalSeparator = gform.Currency.getDecimalSeparator(numberFormat);
2090
2091		value = gform.Currency.cleanNumber( value, '', '', decimalSeparator );
2092		if( ! value )
2093			value = 0;
2094
2095		return value;
2096	}
2097
2098    this.init(formId, formulaFields);
2099
2100
2101}
2102
2103function gformFormatNumber(number, rounding, decimalSeparator, thousandSeparator){
2104
2105    if(typeof decimalSeparator == "undefined"){
2106        if(window['gf_global']){
2107            var currency = new gform.Currency(gf_global.gf_currency_config);
2108            decimalSeparator = currency.currency["decimal_separator"];
2109        }
2110        else{
2111            decimalSeparator = ".";
2112        }
2113    }
2114
2115    if(typeof thousandSeparator == "undefined"){
2116        if(window['gf_global']){
2117            var currency = new gform.Currency(gf_global.gf_currency_config);
2118            thousandSeparator = currency.currency["thousand_separator"];
2119        }
2120        else{
2121            thousandSeparator = ",";
2122        }
2123    }
2124
2125    var currency = new gform.Currency();
2126    return currency.numberFormat(number, rounding, decimalSeparator, thousandSeparator, false)
2127}
2128
2129function gf_get_field_number_format(fieldId, formId, context) {
2130
2131    var fieldNumberFormats = rgars(window, 'gf_global/number_formats/{0}/{1}'.gformFormat(formId, fieldId)),
2132        format = false;
2133
2134    if (fieldNumberFormats === '') {
2135        return format;
2136    }
2137
2138    if (typeof context == 'undefined') {
2139        format = fieldNumberFormats.price !== false ? fieldNumberFormats.price : fieldNumberFormats.value;
2140    } else {
2141        format = fieldNumberFormats[context];
2142    }
2143
2144    return format;
2145}
2146
2147//----------------------------------------
2148//------ reCAPTCHA FUNCTIONS -------------
2149//----------------------------------------
2150
2151gform.recaptcha = {
2152	/**
2153	 * Callback function on the reCAPTCAH API script.
2154	 *
2155	 * @see GF_Field_CAPTCHA::get_field_input() in /includes/fields/class-gf-field-catpcha.php
2156	 */
2157	renderRecaptcha: function() {
2158		jQuery( '.ginput_recaptcha:not(.gform-initialized)' ).each( function() {
2159			let $elem      = jQuery( this ),
2160				parameters = {
2161					'sitekey':        $elem.data( 'sitekey' ),
2162					'theme':          $elem.data( 'theme' ),
2163					'tabindex':       $elem.data( 'tabindex' ),
2164					'error-callback': () => {
2165						console.error( 'Gravity Forms: There was an error initializing reCAPTCHA v2. Please ensure your reCAPTCHA API keys are valid.' );
2166						$elem.attr( 'data-recaptcha-error', '1' );
2167					}
2168				};
2169
2170			if ( $elem.data( 'stoken' ) ) {
2171				parameters.stoken = $elem.data( 'stoken' );
2172			}
2173
2174			/**
2175			 * Allows a custom callback function to be executed when the user successfully submits the captcha.
2176			 *
2177			 * @since 2.4.x     The callback will be a function if reCAPTCHA v2 Invisible is used.
2178			 * @since 2.2.5.20
2179			 *
2180			 * @param string|false|object   The name of the callback function or the function object itself to be executed when the user successfully submits the captcha.
2181			 * @param object       $elem    The jQuery object containing the div element with the ginput_recaptcha class for the current reCaptcha field.
2182			 */
2183			const callback = gform.applyFilters( 'gform_recaptcha_callback', false, $elem );
2184			if ( callback ) {
2185				parameters.callback = callback;
2186			}
2187
2188			// Rendering recaptcha and saving the widget id as an attribute.
2189			const widgetId = grecaptcha.render( this.id, parameters );
2190			$elem[0].setAttribute( 'data-widget-id', widgetId );
2191
2192			if ( parameters.tabindex ) {
2193				$elem.find( 'iframe' ).attr( 'tabindex', parameters.tabindex );
2194			}
2195
2196			$elem.addClass( 'gform-initialized' );
2197
2198			gform.doAction( 'gform_post_recaptcha_render', $elem );
2199		} );
2200
2201		gform.recaptcha.bindRecaptchaSubmissionEvents();
2202	},
2203
2204	isSubmissionEventsInitialized: false,
2205	bindRecaptchaSubmissionEvents: function() {
2206		// If already initialized, abort.
2207		if ( gform.recaptcha.isSubmissionEventsInitialized ) {
2208			return;
2209		}
2210		// Setting initialized flag.
2211		gform.recaptcha.isSubmissionEventsInitialized = true;
2212
2213		// Subscribe to the pre_submission filter to execute invisible recaptcha when form is submitted.
2214		window.gform.utils.addAsyncFilter( 'gform/submission/pre_submission', async ( data ) => {
2215
2216			const requiresRecaptcha = data.submissionType === gform.submission.SUBMISSION_TYPE_SUBMIT || data.submissionType === gform.submission.SUBMISSION_TYPE_NEXT;
2217
2218			// Execute recaptcha if this is the right submission type and the submission hasn't been flagged to be aborted.
2219			if ( requiresRecaptcha && ! data.abort ) {
2220				await gform.recaptcha.maybeExecuteInvisibleRecaptcha( data );
2221			}
2222			return data;
2223		});
2224
2225		// Subscribe to the pre_ajax_validation filter to execute invisible recaptcha when form is validated via AJAX.
2226		window.gform.utils.addAsyncFilter( 'gform/ajax/pre_ajax_validation', gform.recaptcha.maybeExecuteInvisibleRecaptcha );
2227
2228		// Subscribe to the AJAX submission and validation events to save the recaptcha result.
2229		window.gform.utils.addFilter( 'gform/ajax/post_ajax_submission', gform.recaptcha.handleAjaxPostSubmission );
2230		window.gform.utils.addFilter( 'gform/ajax/post_ajax_validation', gform.recaptcha.handleAjaxPostValidation );
2231	},
2232
2233	/**
2234	 * @function maybeExecuteInvisibleRecaptcha
2235	 * @description Executes the invisible recaptcha and waits for the response.
2236
2237	 * @since 2.9.0
2238	 *
2239	 * @param {object} data Data passed by the pre submission filter.
2240	 * @returns {Promise<*>} Returns the pre submission data object unchanged.
2241	 */
2242	maybeExecuteInvisibleRecaptcha: async function( data ) {
2243
2244		if ( gform.recaptcha.gformIsRecaptchaPending( jQuery( data.form ) ) ) {
2245			const recaptcha = gform.utils.getNode( '.ginput_recaptcha', data.form, true );
2246
2247			await gform.recaptcha.executeRecaptcha( recaptcha.getAttribute( 'data-widget-id' ), data.form );
2248		}
2249		return data;
2250	},
2251
2252	/**
2253	 * @function executeRecaptcha
2254	 * @description Executes recaptcha and waits for the response by polling the .g-recaptcha-response field.
2255
2256	 * @since 2.9.0
2257	 *
2258	 * @param {string}          widgetId The recaptcha widgetId.
2259	 * @param {HTMLFormElement} form     The form being submitted
2260	 * @returns {Promise<string>} Returns the recaptcha response when it becomes available in the .g-recaptcha-response
2261	 */
2262	executeRecaptcha: async function( widgetId, form ) {
2263
2264		// If there was an error loading recaptcha, just abort and let the submission fail validation.
2265		const recaptcha = gform.utils.getNode( '.ginput_recaptcha', form, true );
2266		if ( recaptcha.getAttribute( 'data-recaptcha-error' ) === '1' ) {
2267			return;
2268		}
2269
2270		// Executes recaptcha.
2271		window.grecaptcha.execute( widgetId );
2272
2273		// Resolve promise when response is available.
2274		return new Promise(( resolve, reject ) => {
2275			const intervalId = setInterval(() => {
2276				const response = gform.utils.getNode( '.g-recaptcha-response', form, true );
2277
2278				if ( response && response.value ) {
2279					clearInterval( intervalId );
2280					resolve( response.value );
2281				}
2282			}, 100 );
2283		});
2284	},
2285
2286	/**
2287	 * @function handleAjaxPostValidation
2288	 * @description Saves the recaptcha response after an AJAX validation request.
2289
2290	 * @since 2.9.0
2291	 *
2292	 * @param {object} data Data passed by the ajax post validation filter.
2293	 * @returns {object}  Returns the data object unchanged.
2294	 */
2295	handleAjaxPostValidation: function( data ) {
2296		gform.recaptcha.saveRecaptchaResponse( data.validationResult.data.recaptcha_response, data.form );
2297		return data;
2298	},
2299
2300	/**
2301	 * @function handleAjaxPostSubmission
2302	 * @description Saves the recaptcha response after an AJAX submission request.
2303
2304	 * @since 2.9.0
2305	 *
2306	 * @param {object} data Data passed by the ajax post submission filter.
2307	 * @returns {object}  Returns the data object unchanged.
2308	 */
2309	handleAjaxPostSubmission: function( data ) {
2310		gform.recaptcha.saveRecaptchaResponse( data.submissionResult.data.recaptcha_response, data.form );
2311		return data;
2312	},
2313
2314	/**
2315	 * @function saveRecaptchaResponse
2316	 * @description Saves the specified recaptcha response in a hidden field.
2317
2318	 * @since 2.9.0
2319	 *
2320	 * @param {string} recaptchaResponse The recaptcha response to be saved.
2321	 * @param {form}   form              The form being submitted.
2322	 *
2323	 * @returns {void}
2324	 */
2325	saveRecaptchaResponse: function( recaptchaResponse, form ) {
2326
2327		if ( ! recaptchaResponse ) {
2328			return;
2329		}
2330
2331		let recaptchaInput = gform.tools.getNodes( 'input[name=g-recaptcha-response]', true, form, true );
2332		if ( recaptchaInput.length === 0 ) {
2333			recaptchaInput = document.createElement( 'input' );
2334			recaptchaInput.type = 'hidden';
2335			recaptchaInput.name = 'g-recaptcha-response';
2336			form.appendChild( recaptchaInput );
2337		} else {
2338			recaptchaInput = recaptchaInput[0];
2339		}
2340		recaptchaInput.value = recaptchaResponse;
2341	},
2342
2343	/**
2344	 * Helper function to determine whether a recaptcha is pending.
2345	 *
2346	 * @since 2.4.23
2347	 *
2348	 * @param {Object} form jQuery form object.
2349	 * @returns {boolean}
2350	 */
2351	gformIsRecaptchaPending: function( form ) {
2352		const recaptcha = form.find( '.ginput_recaptcha' );
2353
2354		if ( ! recaptcha.length || recaptcha.data( 'size' ) !== 'invisible' ) {
2355			return false;
2356		}
2357
2358		const recaptchaResponse = recaptcha.find( '.g-recaptcha-response' );
2359
2360		return !( recaptchaResponse.length && recaptchaResponse.val() );
2361	},
2362
2363	/**
2364	 * @function gform.recaptcha.needsRender
2365	 * @description Is there a non-rendered Recaptcha field on the page?
2366	 *
2367	 * @since 2.5.6
2368	 */
2369	needsRender: function() {
2370		return document.querySelectorAll( '.ginput_recaptcha:not(.gform-initialized)' )[ 0 ];
2371	},
2372
2373	/**
2374	 * @function gform.recaptcha.renderOnRecaptchaLoaded
2375	 * @description Render recaptcha fields once the library is available, only if non rendered elements are present.
2376	 *
2377	 * @since 2.5.6
2378	 */
2379	renderOnRecaptchaLoaded: function() {
2380		// if nothing to render, exit
2381		if ( ! gform.recaptcha.needsRender() ) {
2382			return;
2383		}
2384		var gfRecaptchaPoller = setInterval( function() {
2385			if ( ! window.grecaptcha || ! window.grecaptcha.render ) {
2386				return;
2387			}
2388			this.renderRecaptcha();
2389			clearInterval( gfRecaptchaPoller );
2390		}, 100 );
2391	}
2392};
2393
2394jQuery( document ).on( 'gform_post_render', gform.recaptcha.renderOnRecaptchaLoaded );
2395
2396window.renderRecaptcha = gform.recaptcha.renderRecaptcha;
2397window.gformIsRecaptchaPending = gform.recaptcha.gformIsRecaptchaPending;
2398
2399
2400//----------------------------------------
2401//----- SINGLE FILE UPLOAD FUNCTIONS -----
2402//----------------------------------------
2403
2404function gformValidateFileSize( field, max_file_size ) {
2405	var validation_element;
2406
2407	// Get validation message element.
2408	if ( jQuery( field ).closest( 'div' ).siblings( '.validation_message' ).length > 0 ) {
2409		validation_element = jQuery( field ).closest( 'div' ).siblings( '.validation_message' );
2410	} else {
2411		validation_element = jQuery( field ).siblings( '.validation_message' );
2412	}
2413
2414	// If file API is not supported within browser, return.
2415	if ( ! window.FileReader || ! window.File || ! window.FileList || ! window.Blob ) {
2416		return;
2417	}
2418
2419	// Get selected file.
2420	var file = field.files[0];
2421
2422	// If selected file is larger than maximum file size, set validation message and unset file selection.
2423	if ( file && file.size > max_file_size ) {
2424
2425		// Set validation message.
2426		validation_element.text(file.name + " - " + gform_gravityforms.strings.file_exceeds_limit);
2427		// Announce error.
2428		wp.a11y.speak( file.name + " - " + gform_gravityforms.strings.file_exceeds_limit );
2429
2430    } else {
2431
2432		// Reset validation message.
2433		validation_element.remove();
2434
2435	}
2436
2437}
2438
2439//----------------------------------------
2440//------ MULTIFILE UPLOAD FUNCTIONS ------
2441//----------------------------------------
2442
2443(function (gfMultiFileUploader, $) {
2444    gfMultiFileUploader.uploaders = {};
2445    var strings = typeof gform_gravityforms != 'undefined' ? gform_gravityforms.strings : {};
2446    var imagesUrl = typeof gform_gravityforms != 'undefined' ? gform_gravityforms.vars.images_url : "";
2447
2448	$(document).on('gform_post_render', function(e, formID){
2449		$( "form#gform_" + formID + " .gform_fileupload_multifile" ).each( function(){
2450			setup( this );
2451		} );
2452
2453		bindFileUploadSubmissionEvents();
2454	});
2455
2456	$(document).on("gform_post_conditional_logic", function(e,formID, fields, isInit){
2457		if(!isInit){
2458			$.each(gfMultiFileUploader.uploaders, function(i, uploader){
2459				uploader.refresh();
2460			});
2461		}
2462	});
2463
2464    $(document).ready(function () {
2465        if((typeof adminpage !== 'undefined' && adminpage === 'toplevel_page_gf_edit_forms')|| typeof plupload == 'undefined'){
2466            $(".gform_button_select_files").prop("disabled", true);
2467        } else if (typeof adminpage !== 'undefined' && adminpage.indexOf('_page_gf_entries') > -1) {
2468            $(".gform_fileupload_multifile").each(function(){
2469                setup(this);
2470            });
2471        }
2472    });
2473
2474    gfMultiFileUploader.setup = function (uploadElement){
2475        setup( uploadElement );
2476    };
2477
2478	let isInitialized = false;
2479
2480	/**
2481	 * Binds the file upload to the pre_submission event so that it can abort submission if there are pending files being uploaded.
2482	 *
2483	 * @since 2.9.0
2484	 */
2485	function bindFileUploadSubmissionEvents() {
2486
2487		// If already initialized, abort.
2488		if ( isInitialized ) {
2489			return;
2490		}
2491		isInitialized = true;
2492
2493		// Making sure there aren't any pending file uploads.
2494		window.gform.utils.addFilter( 'gform/submission/pre_submission', ( data ) => {
2495			if ( hasPendingUploads() ) {
2496				alert( strings.currently_uploading );
2497				data.abort = true;
2498			}
2499
2500			return data;
2501		}, 8);
2502	}
2503
2504	/**
2505	 * Check if there are any files currently in the process of being uploaded.
2506	 *
2507	 * @since 2.9.0
2508	 *
2509	 * @return {boolean} Returns true if there are files that haven't finished being uploaded yet. Returns false otherwise.
2510	 */
2511	function hasPendingUploads() {
2512		let pendingUploads = false;
2513		$.each( gfMultiFileUploader.uploaders, function( i, uploader ) {
2514			if( uploader.total.queued > 0 ) {
2515				pendingUploads = true;
2516				return false;
2517			}
2518		});
2519		return pendingUploads;
2520	}
2521
2522    function setup(uploadElement){
2523        if ( typeof plupload === 'undefined' ) {
2524            return;
2525        }
2526
2527        var settings = $(uploadElement).data('settings');
2528
2529        var uploader = new plupload.Uploader(settings);
2530        formID = uploader.settings.multipart_params.form_id;
2531        gfMultiFileUploader.uploaders[settings.container] = uploader;
2532        var formID;
2533        var uniqueID;
2534
2535	    uploader.bind( 'Init', function( up, params ) {
2536		    if ( ! up.features.dragdrop ) {
2537			    $( ".gform_drop_instructions" ).hide();
2538		    }
2539
2540		    setFieldAccessibility( up.settings.container );
2541		    toggleLimitReached( up.settings );
2542	    } );
2543
2544	    gfMultiFileUploader.toggleDisabled = function (settings, disabled){
2545
2546            var button = typeof settings.browse_button == "string" ? $("#" + settings.browse_button) : $(settings.browse_button);
2547            button.prop("disabled", disabled);
2548        };
2549
2550	    /**
2551	     * @function setFieldAccessibility
2552	     * @description Patches accessibility issues with the plupload multi file container.
2553	     *
2554	     * @since 2.5.1
2555	     *
2556	     * @param {Node} container The generated plupload container.
2557	     */
2558
2559	    function setFieldAccessibility( container ) {
2560		    var input = container.querySelectorAll( 'input[type="file"]' )[ 0 ];
2561		    var button = container.querySelectorAll( '.gform_button_select_files' )[ 0 ];
2562		    var label = $( uploadElement ).closest( '.gfield' ).find( '.gfield_label' )[ 0 ];
2563		    if ( ! input || ! label || ! button ) {
2564			    return;
2565		    }
2566
2567		    label.setAttribute( 'for', input.id );
2568		    button.setAttribute( 'aria-label', button.innerText.toLowerCase() + ', ' + label.innerText.toLowerCase() );
2569		    input.setAttribute( 'tabindex', '-1' );
2570		    input.setAttribute( 'aria-hidden', 'true' );
2571	    }
2572
2573		function addMessage( messagesID, message) {
2574			$( "#" + messagesID ).prepend( "<li class='gfield_description gfield_validation_message'>" + htmlEncode( message ) + "</li>" );
2575			// Announce errors.
2576			setTimeout(function () {
2577				wp.a11y.speak( $( "#" + messagesID ).text() );
2578			}, 1000 );
2579		}
2580
2581	    function removeMessage(messagesID, message) {
2582		    $("#" + messagesID + " li:contains('" + message + "')").remove();
2583	    }
2584
2585	    function toggleLimitReached(settings) {
2586		    var limit = parseInt(settings.gf_vars.max_files, 10);
2587		    if (limit > 0) {
2588			    var totalCount = countFiles(settings.multipart_params.field_id),
2589				    limitReached = totalCount >= limit;
2590
2591			    gfMultiFileUploader.toggleDisabled(settings, limitReached);
2592			    if (!limitReached) {
2593				    removeMessage(settings.gf_vars.message_id, strings.max_reached);
2594			    }
2595		    }
2596	    }
2597
2598        uploader.init();
2599
2600		uploader.bind('BeforeUpload', function(up, file){
2601			up.settings.multipart_params.original_filename = file.name;
2602		});
2603
2604        uploader.bind('FilesAdded', function(up, files) {
2605            var max = parseInt(up.settings.gf_vars.max_files,10),
2606                fieldID = up.settings.multipart_params.field_id,
2607                totalCount = countFiles(fieldID),
2608                disallowed = up.settings.gf_vars.disallowed_extensions,
2609                extension;
2610
2611            if( max > 0 && totalCount >= max){
2612                $.each(files, function(i, file) {
2613                    up.removeFile(file);
2614                    return;
2615                });
2616                return;
2617            }
2618            $.each(files, function(i, file) {
2619
2620                extension = file.name.split('.').pop();
2621
2622                if($.inArray(extension, disallowed) > -1){
2623                    addMessage(up.settings.gf_vars.message_id, file.name + " - " + strings.illegal_extension);
2624                    up.removeFile(file);
2625                    return;
2626                }
2627
2628                if ((file.status == plupload.FAILED) || (max > 0 && totalCount >= max)){
2629                    up.removeFile(file);
2630                    return;
2631                }
2632
2633                var size         = typeof file.size !== 'undefined' ? plupload.formatSize(file.size) : strings.in_progress,
2634                    removeFileJs = '$this=jQuery(this); var uploader = gfMultiFileUploader.uploaders.' + up.settings.container.id + ';uploader.stop();uploader.removeFile(uploader.getFile(\'' + file.id +'\'));$this.after(\'' + strings.cancelled + '\'); uploader.start();$this.remove();',
2635                    statusMarkup = '<div id="{0}" class="ginput_preview"><span class="gfield_fileupload_filename">{1}</span><span class="gfield_fileupload_filesize">{2}</span><span class="gfield_fileupload_progress"><span class="gfield_fileupload_progressbar"><span class="gfield_fileupload_progressbar_progress"></span></span><span class="gfield_fileupload_percent"></span></span><a class="gfield_fileupload_cancel gform-theme-button gform-theme-button--simple" href="javascript:void(0)" title="{3}" onclick="{4}" onkeypress="{4}">{5}</a>';
2636
2637                /**
2638                 *  Filer the file upload markup as it is being uploaded.
2639                 *
2640                 *  @param {string}            statusMarkup Markup template used to render the status of the file being uploaded.
2641                 *  @param {plupload.File}     file         Instance of File being uploaded. See: https://www.plupload.com/docs/v2/File.
2642                 *  @param {int|string}        size         File size.
2643                 *  @param {object}            strings      Array of localized strings relating to the file upload UI.
2644                 *  @param {string}            removeFileJs JS used to remove the file when the "Cancel" link is click/pressed.
2645                 *  @param {plupload.Uploader} up           Instance of Uploader responsible for uploading current file. See: https://www.plupload.com/docs/v2/Uploader.
2646                 */
2647                statusMarkup = gform.applyFilters( 'gform_file_upload_status_markup', statusMarkup, file, size, strings, removeFileJs, up )
2648	                .gformFormat( file.id, htmlEncode( file.name ), size, strings.cancel_upload, removeFileJs, strings.cancel );
2649
2650                $( '#' + up.settings.filelist ).prepend( statusMarkup );
2651
2652                totalCount++;
2653
2654            });
2655
2656            up.refresh(); // Reposition Flash
2657
2658            var formElementID = "form#gform_" + formID;
2659            var uidElementID = "input:hidden[name='gform_unique_id']";
2660            var uidSelector = formElementID + " " + uidElementID;
2661            var $uid = $(uidSelector);
2662            if($uid.length==0){
2663                $uid = $(uidElementID);
2664            }
2665
2666            uniqueID = $uid.val();
2667            if('' === uniqueID){
2668                uniqueID = generateUniqueID();
2669                $uid.val(uniqueID);
2670            }
2671
2672
2673            if(max > 0 && totalCount >= max){
2674                gfMultiFileUploader.toggleDisabled(up.settings, true);
2675                addMessage(up.settings.gf_vars.message_id, strings.max_reached)
2676            }
2677
2678
2679            up.settings.multipart_params.gform_unique_id = uniqueID;
2680            up.start();
2681
2682        });
2683
2684        uploader.bind('UploadProgress', function(up, file) {
2685            var html = file.percent + "%";
2686            $('#' + file.id + ' span.gfield_fileupload_percent').html(html);
2687			$('#' + file.id + ' span.gfield_fileupload_progressbar_progress').css('width', file.percent + '%');
2688        });
2689
2690        uploader.bind('Error', function(up, err) {
2691            if(err.code === plupload.FILE_EXTENSION_ERROR){
2692                var extensions = typeof up.settings.filters.mime_types != 'undefined' ? up.settings.filters.mime_types[0].extensions /* plupoad 2 */ : up.settings.filters[0].extensions;
2693                addMessage(up.settings.gf_vars.message_id, err.file.name + " - " + strings.invalid_file_extension + " " + extensions);
2694            } else if (err.code === plupload.FILE_SIZE_ERROR) {
2695                addMessage(up.settings.gf_vars.message_id, err.file.name + " - " + strings.file_exceeds_limit);
2696            } else {
2697                const errorResponse = JSON.parse( err.response );
2698                const errorCode = errorResponse?.error?.code || err.code;
2699                const errorMessage = errorResponse?.error?.message || err.message;
2700                const filePart = err.file?.name ? `${ err.file.name } - ` : '';
2701                const m = `${ filePart }${ strings.error }: ${ errorCode }, ${ strings.message }: ${ errorMessage }`;
2702
2703                addMessage(up.settings.gf_vars.message_id, m);
2704            }
2705            $('#' + err.file.id ).html('');
2706            up.removeFile( err.file );
2707            up.refresh(); // Reposition Flash
2708        });
2709
2710		uploader.bind('ChunkUploaded', function(up, file, result) {
2711			var response = $.secureEvalJSON(result.response);
2712			if(response.status == "error"){
2713				up.removeFile(file);
2714				addMessage(up.settings.gf_vars.message_id, file.name + " - " + response.error.message);
2715				$('#' + file.id ).html('');
2716			} else {
2717				up.settings.multipart_params[file.target_name] = response.data;
2718			}
2719		});
2720
2721		uploader.bind('FileUploaded', function(up, file, result) {
2722			if (!up.getFile(file.id)) {
2723				// The file has been removed from the queue.
2724				return;
2725			}
2726
2727			var response = $.secureEvalJSON(result.response);
2728			if (response.status == "error") {
2729				addMessage(up.settings.gf_vars.message_id, file.name + " - " + response.error.message);
2730				$('#' + file.id).html('');
2731				toggleLimitReached(up.settings);
2732				return;
2733			}
2734
2735			var uploadedName = rgars(response, 'data/uploaded_filename');
2736			var html = '<span class="gfield_fileupload_filename">' + htmlEncode(uploadedName) + '</span><span class="gfield_fileupload_filesize">' + plupload.formatSize(file.size) + '</span>';
2737			html += '<span class="gfield_fileupload_progress gfield_fileupload_progress_complete"><span class="gfield_fileupload_progressbar"><span class="gfield_fileupload_progressbar_progress"></span></span><span class="gfield_fileupload_percent">' + file.percent + '%</span></span>';
2738			var formId = up.settings.multipart_params.form_id;
2739			var fieldId = up.settings.multipart_params.field_id;
2740
2741			if (typeof gf_legacy !== 'undefined' && gf_legacy.is_legacy) {
2742				html = "<img "
2743					+ "class='gform_delete' "
2744					+ "src='" + imagesUrl + "/delete.png' "
2745					+ "onclick='gformDeleteUploadedFile(" + formId + "," + fieldId + ", this);' "
2746					+ "onkeypress='gformDeleteUploadedFile(" + formId + "," + fieldId + ", this);' "
2747					+ "alt='" + strings.delete_file + "' "
2748					+ "title='" + strings.delete_file
2749					+ "' /> "
2750					+ html;
2751			} else {
2752				html = html + "<button class='gform_delete_file gform-theme-button gform-theme-button--simple' onclick='gformDeleteUploadedFile(" + formId + "," + fieldId + ", this);'><span class='dashicons dashicons-trash' aria-hidden='true'></span><span class='screen-reader-text'>" + strings.delete_file + ': ' + htmlEncode(uploadedName) + "</span></button>";
2753			}
2754
2755			/**
2756			 * Allows the markup for the file to be overridden.
2757			 *
2758			 * @since 1.9
2759			 * @since 2.4.23 Added the response param.
2760			 *
2761			 * @param {string} html      The HTML for the file name and delete button.
2762			 * @param {object} file      The file upload properties. See: https://www.plupload.com/docs/v2/File.
2763			 * @param {object} up        The uploader properties. See: https://www.plupload.com/docs/v2/Uploader.
2764			 * @param {object} strings   Localized strings relating to file uploads.
2765			 * @param {string} imagesURL The base URL to the Gravity Forms images directory.
2766			 * @param {object} response  The response from GFAsyncUpload.
2767			 */
2768			html = gform.applyFilters('gform_file_upload_markup', html, file, up, strings, imagesUrl, response);
2769
2770			$('#' + file.id).html(html);
2771			$('#' + file.id + ' span.gfield_fileupload_progressbar_progress').css('width', file.percent + '%');
2772
2773			if (file.percent == 100) {
2774				if (response.status && response.status == 'ok') {
2775					response.data.id = file.id;
2776					addFile(fieldId, response.data);
2777					window.wp.a11y.speak( ( strings.file_uploaded ) + ': ' + uploadedName );
2778				} else {
2779					addMessage(up.settings.gf_vars.message_id, strings.unknown_error + ': ' + file.name);
2780				}
2781			}
2782
2783		});
2784
2785		uploader.bind('FilesRemoved', function (up, files) {
2786			toggleLimitReached(up.settings);
2787		});
2788
2789		function getAllFiles(){
2790			var selector = '#gform_uploaded_files_' + formID,
2791				$uploadedFiles = $(selector), files;
2792
2793			files = $uploadedFiles.val();
2794			files = (typeof files === "undefined") || files === '' ? {} : $.parseJSON(files);
2795
2796			return files;
2797		}
2798
2799        function getFiles(fieldID){
2800            var allFiles = getAllFiles();
2801            var inputName = getInputName(fieldID);
2802
2803            if(typeof allFiles[inputName] == 'undefined')
2804                allFiles[inputName] = [];
2805            return allFiles[inputName];
2806        }
2807
2808        function countFiles(fieldID){
2809            var files = getFiles(fieldID);
2810            return files.length;
2811        }
2812
2813        function addFile(fieldID, fileInfo){
2814
2815            var files = getFiles(fieldID);
2816
2817            files.unshift(fileInfo);
2818            setUploadedFiles(fieldID, files);
2819        }
2820
2821        function setUploadedFiles(fieldID, files){
2822            var allFiles = getAllFiles();
2823            var $uploadedFiles = $('#gform_uploaded_files_' + formID);
2824            var inputName = getInputName(fieldID);
2825            allFiles[inputName] = files;
2826            $uploadedFiles.val($.toJSON(allFiles));
2827        }
2828
2829        function getInputName(fieldID){
2830            return "input_" + fieldID;
2831        }
2832
2833        // fixes drag and drop in IE10
2834        $("#" + settings.drop_element).on({
2835            "dragenter": ignoreDrag,
2836            "dragover": ignoreDrag
2837        });
2838
2839        function ignoreDrag( e ) {
2840            e.preventDefault();
2841        }
2842    }
2843
2844
2845    function generateUniqueID() {
2846        return 'xxxxxxxx'.replace(/[xy]/g, function (c) {
2847            var r = Math.random() * 16 | 0, v = c == 'x' ? r : r & 0x3 | 0x8;
2848            return v.toString(16);
2849        });
2850    }
2851
2852	function htmlEncode(value){
2853		return $('<div/>').text(value).html();
2854	}
2855
2856}(window.gfMultiFileUploader = window.gfMultiFileUploader || {}, jQuery));
2857
2858//----------------------------------------
2859//------ TINYMCE FUNCTIONS ---------------
2860//----------------------------------------
2861
2862/**
2863 * @function gformReInitTinymceInstance
2864 * @description Reinitializes a tinymce instance bound to a gform field if found.
2865 *
2866 * @since 2.5
2867 *
2868 * @param formId {int} Required. The form id.
2869 * @param fieldId {int} Required. The field id.
2870 */
2871
2872function gformReInitTinymceInstance( formId, fieldId ) {
2873    // check for required arguments
2874    if ( ! formId || ! fieldId ) {
2875        gform.console.error( 'gformReInitTinymceInstance requires a form and field id.' );
2876        return;
2877    }
2878    // make sure we have tinymce
2879    var tinymce = window.tinymce;
2880    if ( ! tinymce ) {
2881        gform.console.error( 'gformReInitTinymceInstance requires tinymce to be available.' );
2882        return;
2883    }
2884    // get the editor instance by form and field id and bail if not found
2885    var editor = tinymce.get( 'input_' + formId + '_' + fieldId );
2886    if ( ! editor ) {
2887        gform.console.error( 'gformReInitTinymceInstance did not find an instance for input_' + formId + '_' + fieldId + '.' );
2888        return;
2889    }
2890    // get the settings, destroy the instance and reinitialize
2891    var settings = jQuery.extend( {}, editor.settings );
2892    editor.remove();
2893    tinymce.init( settings );
2894    gform.console.log( 'gformReInitTinymceInstance reinitialized TinyMCE on input_' + formId + '_' + fieldId + '.' );
2895}
2896
2897//----------------------------------------
2898//------ EVENT FUNCTIONS -----------------
2899//----------------------------------------
2900
2901var __gf_keyup_timeout;
2902
2903jQuery( document ).on( 'change keyup', '.gfield input, .gfield select, .gfield textarea', function( event ) {
2904    gf_raw_input_change( event, this );
2905} );
2906
2907function gf_raw_input_change( event, elem ) {
2908
2909    // clear regardless of event type for maximum efficiency ;)
2910    clearTimeout( __gf_keyup_timeout );
2911
2912    var $input    = jQuery( elem ),
2913        htmlId    = $input.attr( 'id' ),
2914        fieldId   = gf_get_input_id_by_html_id( htmlId ),
2915        formId    = gf_get_form_id_by_html_id( htmlId ),
2916	    /**
2917	     * Filter the field meta generated by a raw input change.
2918	     *
2919	     * @since 2.4.1
2920	     *
2921	     * @param object fieldMeta An object containing the field ID and form ID of the triggering Gravity Forms field.
2922	     * @param object $input    The jQuery object for the triggering field element.
2923	     * @param object event     The raw JS event.
2924	     */
2925        fieldMeta = gform.applyFilters( 'gform_field_meta_raw_input_change', { fieldId: fieldId, formId: formId }, $input, event );
2926
2927    fieldId = fieldMeta.fieldId;
2928    formId = fieldMeta.formId;
2929
2930    if( ! fieldId ) {
2931        return;
2932    }
2933
2934    var isChangeElem = $input.is( ':checkbox' ) || $input.is( ':radio' ) || $input.is( 'select' ),
2935        isKeyupElem  = ! isChangeElem || $input.is( 'textarea' );
2936
2937    if( event.type == 'keyup' && ! isKeyupElem ) {
2938        return;
2939    } else if( event.type == 'change' && ! isChangeElem && ! isKeyupElem ) {
2940        return;
2941    }
2942
2943    if( event.type == 'keyup' ) {
2944        __gf_keyup_timeout = setTimeout( function() {
2945            gf_input_change( elem, formId, fieldId );
2946        }, 300 );
2947    } else {
2948        gf_input_change( elem, formId, fieldId );
2949    }
2950
2951}
2952
2953/**
2954 * Get the input id from a form element's HTML id.
2955 *
2956 * @param {string} htmlId The HTML id of a form element.
2957 *
2958 * @returns {string} inputId The input id.
2959 */
2960function gf_get_input_id_by_html_id( htmlId ) {
2961
2962    var ids = gf_get_ids_by_html_id( htmlId ),
2963        id  = ids[ ids.length - 1 ];
2964
2965    if ( ids.length == 3 ) {
2966        ids.shift();
2967        id = ids.join( '.' );
2968    }
2969
2970    return id;
2971}
2972
2973/**
2974 * Get the form id from a form element's HTML id.
2975 *
2976 * @param {string} htmlId The HTML id of a form element.
2977 *
2978 * @returns {string} formId The form id.
2979 */
2980function gf_get_form_id_by_html_id( htmlId ) {
2981    var ids = gf_get_ids_by_html_id( htmlId );
2982    return ids[0];
2983}
2984
2985/**
2986 * Get the form, field, and input id by a form elements HTML id.
2987 *
2988 * Note: Only multi-input fields will be return an input ID.
2989 *
2990 * @param {string} htmlId The HTML id of a form element.
2991 *
2992 * @returns {array} ids An array contain the form, field and input id.
2993 */
2994function gf_get_ids_by_html_id( htmlId ) {
2995    var ids = htmlId ? htmlId.split( '_' ) : [];
2996    for( var i = ids.length - 1; i >= 0; i-- ) {
2997        if ( ! gform.utils.isNumber( ids[ i ] ) ) {
2998            ids.splice( i, 1 );
2999        }
3000    }
3001    return ids;
3002}
3003
3004function gf_input_change( elem, formId, fieldId ) {
3005    gform.doAction( 'gform_input_change', elem, formId, fieldId );
3006}
3007
3008function gformExtractFieldId( inputId ) {
3009    var fieldId = parseInt( inputId.toString().split( '.' )[0],10 );
3010    return ! fieldId ? inputId : fieldId;
3011}
3012
3013function gformExtractInputIndex( inputId ) {
3014    var inputIndex = parseInt( inputId.toString().split( '.' )[1],10 );
3015    return ! inputIndex ? false : inputIndex;
3016}
3017
3018
3019
3020//----------------------------------------
3021//------ HELPER FUNCTIONS ----------------
3022//----------------------------------------
3023
3024if( ! window['rgars'] ) {
3025    function rgars( array, prop ) {
3026
3027        var props = prop.split( '/' ),
3028            value = array;
3029
3030        for( var i = 0; i < props.length; i++ ) {
3031            value = rgar( value, props[ i ] );
3032        }
3033
3034        return value;
3035    }
3036}
3037
3038if( ! window['rgar'] ) {
3039    function rgar( array, prop ) {
3040        if ( typeof array[ prop ] != 'undefined' ) {
3041            return array[ prop ];
3042        }
3043        return '';
3044    }
3045}
3046
3047if ( ! String.prototype.gformFormat ) {
3048	String.prototype.gformFormat = function() {
3049		var args = arguments;
3050		return this.replace( /{(\d+)}/g, function( match, number ) {
3051			return typeof args[ number ] != 'undefined' ? args[ number ] : match;
3052		} );
3053	};
3054}
3055
3056
3057/**
3058 * Toggle the dropdown submenus in the form editor menu bar.
3059 *
3060 * @since 2.5
3061 */
3062jQuery( document ).ready( function() {
3063	jQuery( '#gform-form-toolbar__menu' )
3064	.on( 'mouseenter focus', '> li',function() {
3065			jQuery( this ).find( '.gform-form-toolbar__submenu' ).toggleClass( 'open' );
3066			jQuery( this ).find( '.has_submenu' ).toggleClass( 'submenu-open' );
3067		} );
3068	jQuery( '#gform-form-toolbar__menu' )
3069		.on( 'mouseleave blur', '> li',function() {
3070			jQuery( '.gform-form-toolbar__submenu.open' ).removeClass( 'open' );
3071			jQuery( '.has_submenu.submenu-open' ).removeClass( 'submenu-open' );
3072		} );
3073	jQuery( '#gform-form-toolbar__menu .has_submenu' )
3074		.on( 'click', function( e ) {
3075			e.preventDefault();
3076		} );
3077} );
3078
3079/**
3080 * Add a containing class to fields with multiple inputs that we want to display inline.
3081 *
3082 * @since 2.5
3083 */
3084jQuery( document ).ready( function() {
3085	var settingsFields = jQuery( '.gform-settings-field' );
3086	settingsFields.each( function() {
3087		if ( jQuery( this ).find( '> .gform-settings-input__container' ).length > 1 ) {
3088			jQuery( this ).addClass( 'gform-settings-field--multiple-inputs' );
3089		}
3090	} );
3091} );
3092
3093jQuery( function() {
3094	gform.tools.trigger( 'gform_main_scripts_loaded' );
3095} );

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.