PageSourceSearch

https://www.glashuette-original.com/app/plugins/gravityforms/js/gravityforms.js?ver=2.8.16

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

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.