--- assets/js/min/front-end-deps.js +++ assets/js/min/front-end-deps.js @@ -73,14 +73,26 @@ var fieldID = jQuery( this ).data( 'fieldid' ); if ( typeof window[ callback ] !== 'function' ){ window[ callback ] = function( response ) { + nfRecaptcha.DEBUG && console.group(callback); + nfRecaptcha.DEBUG && console.log('fieldID:', fieldID); + nfRecaptcha.DEBUG && console.log('response:', response); + nfRadio.channel( 'recaptcha' ).request( 'update:response', response, fieldID ); + nfRadio.channel( 'captcha' ).request( 'update:response', response, fieldID ); + + nfRecaptcha.DEBUG && console.groupEnd(); }; } } ); } ); var nfRecaptcha = Marionette.Object.extend( { - initialize: function() { + /** @var {boolean} - Validate only one reCAPTCHA at a time. */ + isBusy: false, + /** @var {?{ninja-forms:FormModel}} - Track which form is submitting. */ + isSubmitting: null, + + initialize: function () { /* * If we've already rendered our form view, render our recaptcha fields. */ @@ -91,33 +103,199 @@ * We haven't rendered our form view, so hook into the view render radio message, and then render. */ this.listenTo( nfRadio.channel( 'form' ), 'render:view', this.renderCaptcha ); - this.listenTo( nfRadio.channel( 'captcha' ), 'reset', this.renderCaptcha ); + this.listenTo( nfRadio.channel( 'captcha' ), 'reset', this.renderCaptcha ); + this.listenTo( nfRadio.channel( 'submit' ), 'validate:field', this.validateCaptcha ); + }, + + /** + * @param {ninja-forms:FormModel} formModel - The Ninja Forms form model. + */ + beforeSubmit: function ( formModel ) { + var DEBUG = nfRecaptcha.DEBUG; + + DEBUG && console.group('nfRecaptcha.beforeSubmit'); + + this.isSubmitting = formModel; + DEBUG && console.log('Form:', formModel); + + DEBUG && console.groupEnd(); }, - renderCaptcha: function() { + renderCaptcha: function () { + var nfController = this, + DEBUG = nfRecaptcha.DEBUG; + + DEBUG && console.group('nfRecaptcha.renderCaptcha'); + jQuery( '.g-recaptcha:empty' ).each( function() { + DEBUG && console.group('.g-recaptcha', this); + + var fieldID = jQuery( this ).data( 'fieldid' ); + DEBUG && console.log( 'Field:', fieldID ); + var opts = { - fieldid: jQuery( this ).data( 'fieldid' ), + fieldid: fieldID, size: jQuery( this ).data( 'size' ), theme: jQuery( this ).data( 'theme' ), sitekey: jQuery( this ).data( 'sitekey' ), callback: jQuery( this ).data( 'callback' ) }; - var grecaptchaID = grecaptcha.render( jQuery( this )[0], opts ); + DEBUG && console.log( 'Size:', opts.size ); + DEBUG && console.log( 'Callback:', opts.callback ); + + var grecaptchaID = grecaptcha.render( this, opts ); + DEBUG && console.log( 'reCAPTCHA:', grecaptchaID ); if ( opts.size === 'invisible' ) { - try { - nf_reprocess_recaptcha( grecaptchaID ); - setInterval(nf_reprocess_recaptcha, 110000, grecaptchaID); - } catch( e ){ - console.log( 'Notice: Error trying to execute grecaptcha.' ); + var fieldModel = nfRadio.channel( 'fields' ).request( 'get:field', fieldID ); + + if ( ! fieldModel ) { + DEBUG && console.log( 'Skipping; Missing Field Model' ); + DEBUG && console.groupEnd(); + return; } - } + + fieldModel.set( 'grecaptchaID', grecaptchaID ); + + nfController.listenTo( nfRadio.channel( 'form-' + fieldModel.get( 'formID' ) ), 'before:submit', nfController.beforeSubmit ); + + nfRadio.channel( 'captcha' ).reply( 'update:response', nfController.updateValidation, nfController ); + } + + DEBUG && console.groupEnd(); } ); + + DEBUG && console.groupEnd(); + }, + + /** + * @param {string} response - The reCAPTCHA response value. + * @param {string|number} fieldID - The Ninja Forms CAPTCHA field ID. + */ + updateValidation: function ( response, fieldID ) { + var DEBUG = nfRecaptcha.DEBUG; + + DEBUG && console.group('nfRecaptcha.updateValidation'); + + var fieldModel = nfRadio.channel( 'fields' ).request( 'get:field', fieldID ); + DEBUG && console.log('Field:', fieldModel); + + if ( ! fieldModel ) { + DEBUG && console.log( 'Skipping; Missing Field Model' ); + DEBUG && console.groupEnd(); + return; + } + + nfRadio.channel( 'fields' ).request( 'remove:error', fieldID, 'recaptcha-processing' ); + + var formModel = this.isSubmitting; + + this.isSubmitting = null; + this.isBusy = false; + + if ( formModel ) { + DEBUG && console.log( 'Attempting to re-submit form' ); + nfRadio.channel( 'form-' + formModel.id ).request( 'submit', formModel ); + } else { + DEBUG_CONTROLLER && console.log( 'Failed; Missing Form' ); + } + + DEBUG && console.groupEnd(); + }, + + /** + * @param {NFFieldModel} fieldModel + */ + validateCaptcha: function ( fieldModel ) { + var DEBUG = nfRecaptcha.DEBUG; + + DEBUG && console.group('nfRecaptcha.validateCaptcha --', 'Field:', fieldModel.id); + + var fieldID = fieldModel.id; + var fieldType = fieldModel.get( 'type' ); + var fieldSize = fieldModel.get( 'size' ); + var grecaptchaID = fieldModel.get( 'grecaptchaID' ); + + if ( this.isBusy ) { + DEBUG && console.log( 'Skipping; Busy' ); + DEBUG && console.groupEnd(); + return; + } + + if ( ! this.isSubmitting ) { + DEBUG && console.log( 'Skipping; Not Submitting' ); + DEBUG && console.groupEnd(); + return; + } + + if ( + fieldType !== 'recaptcha' || + fieldSize !== 'invisible' || + fieldModel.get( 'clean' ) + ) { + DEBUG && console.log( 'Skipping; Not reCAPTCHA Field' ); + DEBUG && console.groupEnd(); + return; + } + + if ( grecaptchaID == null ) { + DEBUG && console.log( 'Skipping; Missing reCAPTCHA Widget ID' ); + DEBUG && console.groupEnd(); + return; + } + + this.isBusy = true; + + var fieldValue = fieldModel.get( 'value' ); + var fieldOldValue = fieldModel.get( 'old_value' ); + DEBUG && console.log( 'New Value:', fieldValue ); + DEBUG && console.log( 'Old Value:', fieldOldValue ); + + nfRadio.channel( 'fields' ).request( 'remove:error', fieldID, 'required-error' ); + + if ( fieldOldValue != null && fieldValue ) { + fieldModel.set( 'old_value', null ); + + this.isBusy = false; + DEBUG && console.log( 'Validated: Same Value' ); + DEBUG && console.groupEnd(); + return; + } + + fieldModel.set( 'old_value', fieldValue ); + + var formModel = nfRadio.channel( 'form-' + fieldModel.get( 'formID' ) ).request( 'get:form' ); + if ( ! formModel ) { + this.isBusy = false; + DEBUG && console.log( 'Skipping; Missing Form Model' ); + DEBUG && console.groupEnd(); + return; + } + + nfRadio.channel( 'fields' ).request( + 'add:error', + fieldID, + 'recaptcha-processing', + formModel.get('settings').recaptchaProcessing + ); + + try { + DEBUG && console.log( 'Attempting to execute reCAPTCHA' ); + nf_reprocess_recaptcha( grecaptchaID ); + } catch ( e ) { + this.isBusy = false; + console.log( 'Notice: Error trying to execute grecaptcha.' ); + DEBUG && console.log( 'Caught error:', e ); + } + + DEBUG && console.groupEnd(); } } ); +/** @var {boolean} - Display debug information in console. */ +nfRecaptcha.DEBUG = true; + var nfRenderRecaptcha = function() { new nfRecaptcha(); }