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