/* * AVIA GOOGLE reCAPTCHA API * ========================= * * This file holds javascript functions needed in frontend for the functionallity of the Google reCaptcha API and widget * Handles conditional loading of Google reCaptcha API script. * * As first step we limit this to contact forms (context av_contact_form) - but it can be extended in future. * * @author Günter for Christian "Kriesi" Budschedl * @copyright Copyright ( c ) Christian Budschedl * @link http://kriesi.at * @link http://aviathemes.com * @since Version 1.0 * @package AviaFramework * @since Enfold 4.5.7.2 */ (function($) { var obj_avia_recaptcha = null; var avia_recaptcha = function() { if( 'undefined' == typeof AviaReCAPTCHA_front ) { return; } this.body = $( 'body' ); if( ! this.body.hasClass( 'av-recaptcha-enabled' ) ) { return; } this.self = this; this.errors = []; // errors visible for all users this.error_report = []; // errors only visible if class 'av-recaptcha-extended-errors' is set in body (usual only for admins) this.monitor_V3_request = false; this.document = $( document ); this.version = AviaReCAPTCHA_front.version; this.site_key2 = AviaReCAPTCHA_front.site_key2; this.site_key3 = AviaReCAPTCHA_front.site_key3; this.api = AviaReCAPTCHA_front.api; this.theme = AviaReCAPTCHA_front.theme; this.score = AviaReCAPTCHA_front.score; this.size = 'normal'; this.cannot_use = AviaReCAPTCHA_front.cannot_use; this.init_error_msg = AviaReCAPTCHA_front.init_error_msg; this.v3_timeout_pageload = AviaReCAPTCHA_front.v3_timeout_pageload; this.v3_timeout_verify = AviaReCAPTCHA_front.v3_timeout_verify; this.v2_timeout_verify = AviaReCAPTCHA_front.v2_timeout_verify; this.invalid_version = AviaReCAPTCHA_front.invalid_version; this.validate_first = AviaReCAPTCHA_front.validate_first; this.validate_submit = AviaReCAPTCHA_front.validate_submit; this.verify_nonce = AviaReCAPTCHA_front.verify_nonce; this.verify_msg = AviaReCAPTCHA_front.verify_msg; this.connection_error = AviaReCAPTCHA_front.connection_error; this.no_token = AviaReCAPTCHA_front.no_token; this.api_load_error = AviaReCAPTCHA_front.api_load_error; var obj = this.self; this.body.find( '.av-recaptcha-area' ).each( function( index, element ) { obj.prepare_recaptcha_area( index, element, obj ); }); // Remember: To allow Google to monitor V3 we do not return now when no recaptcha_areas this.recaptcha_areas = this.body.find( '.av-recaptcha-area' ); if( ! this.verifySettings() ) { this.showErrors( 'remove_buttons' ); return; } this.loadMainAPI(); }; avia_recaptcha.prototype = { verifySettings: function() { /** * Provide a fallback to V2 in case any parameter does not fit */ if( -1 == $.inArray( this.version, [ 'avia_recaptcha_v2', 'avia_recaptcha_v3'] ) ) { this.version = 'avia_recaptcha_v2'; } if( 'avia_recaptcha_v3' == this.version && ( '' == this.site_key3 ) ) { this.version = 'avia_recaptcha_v2'; } if( '' == this.site_key2 ) { this.errors.push( this.cannot_use ); this.error_report.push( this.init_error_msg ); return false; } return true; }, prepare_recaptcha_area: function( index, element, self ) { var container = $( element ); var token_input = ( 'undefined' != typeof container.data( 'token_input' ) ) ? container.data( 'token_input' ) : 'avia_token_verify'; var token_input_version = token_input + '-version'; var version = ( 'undefined' != typeof container.data( 'version' ) ) ? container.data( 'version' ) : ''; var context = ( 'undefined' != typeof container.data( 'context' ) ) ? container.data( 'context' ) : 'av_contact_form'; var badge_msg = container.find( '.av-google-badge-message' ).first(); if( 'av_contact_form' != context ) { container.removeClass( 'av-recaptcha-area' ).addClass( 'av-recaptcha-area-unsupported' ); return; } if( -1 == $.inArray( version, [ 'avia_recaptcha_v2', 'avia_recaptcha_v3' ] ) ) { version = 'avia_recaptcha_v2'; } if( ( 'avia_recaptcha_v3' == version ) && ( 'avia_recaptcha_v2' == self.version ) ) { version = 'avia_recaptcha_v2'; } var id = this.unique_id( index ); container.data( 'token_input', token_input ); container.data( 'version', version ); container.data( 'context', context ); container.data( 'unique_recaptcha_id', id ); container.addClass( id ); container.before( '' ); container.before( '' ); container.before( '
' ); var form = container.closest( 'form' ); form.addClass( version ); var submit = form.find( 'input[type=submit]' ); if( badge_msg.length > 0 ) { if( 'avia_recaptcha_v3' == version ) { badge_msg.detach().insertAfter( submit ).removeClass( 'hidden' ); self.body.addClass( 'av-google-badge-hide' ); } else { badge_msg.remove(); } } else { if( 'avia_recaptcha_v3' == version ) { self.body.addClass( 'av-google-badge-visible' ); } } var new_submit = submit.clone(); new_submit.data( 'version', version ); new_submit.data( 'context', context ); new_submit.data( 'unique_recaptcha_id', id ); new_submit.addClass( id ); new_submit.attr( 'type', 'button' ).addClass( 'av-recaptcha-submit ' + id); if( 'avia_recaptcha_v2' == version ) { new_submit.attr( 'title', this.validate_first ); new_submit.addClass( 'avia_button_inactive' ); } else { new_submit.attr( 'title', this.validate_submit ); } // added 4.8.6.4 https://kriesi.at/support/topic/google-analytics-on-click-bug/ var analytics_check = new_submit.attr( 'onclick' ); if( 'undefined' != typeof analytics_check ) { analytics_check = analytics_check.toLowerCase(); if( analytics_check.indexOf( 'gtag(' ) >= 0 ) { new_submit.attr( 'onclick', null ); } } submit.before( new_submit ); submit.addClass( 'avia_button_inactive av-recaptcha-submit-real' ); submit.hide(); form.find( '.av-recaptcha-submit' ).on( 'click', self.reCaptchaSubmitButton.bind( self ) ); }, loadMainAPI: function() { if( this.recaptcha_areas.length == 0 && this.version != 'avia_recaptcha_v3' ) { return false; } var src = this.api; var defer = true; var obj = this; switch( this.version ) { case 'avia_recaptcha_v2': src += '?onload=av_recaptcha_main_api_loaded&render=explicit'; break; case 'avia_recaptcha_v3': src += '?onload=av_recaptcha_main_api_loaded&render=' + this.site_key3; break; } //find a current google recaptcha link and remove it, then append the new one $( 'script[src*="recaptcha/api.js"]' ).remove(); $( '#av-recaptcha-api-script' ).remove(); var script = document.createElement( 'script' ); script.id = 'av-recaptcha-api-script'; script.type = 'text/javascript'; script.src = src; script.onerror = function(){ obj.main_api_loading_error(); }; // script.onload = function(){ }; if( defer ) { script.defer = true; } document.body.appendChild(script); // console.log( 'loading: ' + this.version ); return true; }, main_api_loading_error: function() { // console.log( 'main_api_loading_error' ); this.errors.push( this.cannot_use ); this.error_report.push( this.api_load_error ); this.showErrors( 'remove_buttons' ); }, main_api_loaded: function() { if( 'avia_recaptcha_v3' == this.version ) { var obj = this.self; grecaptcha.ready(function() { // console.log( 'V3 API page_load' ); obj.monitor_V3_request = setTimeout( function(){ obj.monitorV3( 'pageload' ); }, 5000 ); grecaptcha.execute( obj.site_key3, { action: 'contact_forms_pageload' } ).then( function( token ) { obj.verifyCallback( token, 'avia_recaptcha_v3', 'page_load' ); } ); }); } this.show_reCaptchas(); return true; }, /** * Fix bugs in V3: * - a wrong sitekey does not return or throw any error - only a message in console * - loosing internet connection only shows a unhandled exception null */ monitorV3: function( location ) { // console.log( 'monitorLoadedV3' ); this.monitor_V3_request = false; this.errors.push( this.cannot_use ); switch( location ) { case 'pageload': this.error_report.push( this.v3_timeout_pageload ); break; case 'verify_submit': this.error_report.push( this.v3_timeout_verify ); break; default: this.error_report.push( 'v3_timeout - unknown location' ); } this.showErrors( 'remove_buttons' ); }, verifyCallback: function( token, version, context ) { // console.log( 'token: ', token, token.length ); // v3 fix: wrong sitekey interrupts program flow, if it returns sitekey is valid. if( 'avia_recaptcha_v3' == version && this.monitor_V3_request !== false ) { clearTimeout( this.monitor_V3_request ); this.monitor_V3_request = false; } if( 'avia_recaptcha_v3' == version && 'page_load' == context ) { return; } var container = null; var score = null; var action = null; if( 'avia_recaptcha_v2' == version ) { // We do not get any feedback which reCAPTCHA was clicked when multiple on a page container = this.locate_clicked_V2_recaptcha( token ); } else if( 'avia_recaptcha_v3' == version ) { var btn = $( 'input.av-recaptcha-verify-v3' ); if( btn.length > 0 ) { container = btn.first().closest( 'form' ).find( 'div.' + btn.data( 'unique_recaptcha_id' ) ).first(); if( container.length > 0 ) { score = container.data( 'score' ); action = context; } } btn.removeClass( 'av-recaptcha-verify-v3' ); } if( null == container ) { this.errors.push( this.cannot_use ); this.error_report.push( 'verifyCallback: No containeer found to clicked submit button' ); this.showErrors( 'remove_buttons' ); return; } this.serverCallback( token, version, container, score, action ); }, locate_clicked_V2_recaptcha: function( token ) { var clicked = null; this.recaptcha_areas.each( function( index, element ){ var container = $(element); if( 'avia_recaptcha_v2' != container.data( 'version' ) ) { return true; } var result = grecaptcha.getResponse( container.data( 'recaptcha_widet_id' ) ); if( token == result ) { clicked = container; return false; } }); return clicked; }, serverCallback: function( token, version, container, score, action ) { var obj = this.self; var id = container.data( 'unique_recaptcha_id' ); var button = $( 'input.av-recaptcha-submit.' + id ); var orig_msg = button.val(); var alert_msg = ''; var form = button.closest( 'form' ); var msg_container = form.find( '.av-recaptcha-error' ); $.ajax({ type: "POST", url: avia_framework_globals.ajaxurl, dataType: 'json', cache: false, data: { action: 'avia_recaptcha_verify_frontend', version: version, token: token, score: null != score ? score : -1, recaptcha_action: null != action ? action : '', _wpnonce: this.verify_nonce, }, beforeSend: function() { button.addClass( 'av-sending-button' ); button.val( this.verify_msg ); }, error: function() { alert_msg = this.connection_error; }, success: function(response) { if( response.success !== true ) { alert_msg = response.alert; if( 'avia_recaptcha_v3' == version && false !== response.score_failed ) { if( true !== response.score_failed ) { alert_msg += '
' + response.score_failed; } button.addClass( 'avia_recaptcha_v3_redirected_v2' ); button.closest('form').removeClass( 'avia_recaptcha_v3' ).addClass( 'avia_recaptcha_v2 avia_recaptcha_v2_forced' ); obj.show_reCaptchas( 'force_v2' ); } return; } if( response.transient != '' ) { $( 'input.av-recaptcha-verify-token.' + id).val(response.transient); button.addClass( 'av-recaptcha-is-verified' ); } button.removeClass( 'avia_button_inactive' ); }, complete: function(response) { if( 'avia_recaptcha_v2' == version ) { grecaptcha.reset( container.data( 'recaptcha_widet_id' )); if( alert_msg == '' ) { container.hide(); button.attr( 'title', '' ); } if( button.hasClass( 'av-recaptcha-is-verified' ) && button.hasClass( 'avia_recaptcha_v3_redirected_v2' ) ) { button.trigger( 'click' ); } } else if( 'avia_recaptcha_v3' == version ) { if( button.hasClass( 'av-recaptcha-is-verified' ) ) { button.trigger( 'click' ); } } button.removeClass( 'av-sending-button' ); button.val( orig_msg ); if( alert_msg != '' ) { msg_container.addClass( 'av-err-content' ).removeClass( 'av-recaptcha-severe-error' ); msg_container.html( alert_msg ); } else { msg_container.removeClass( 'av-err-content av-recaptcha-severe-error' ); msg_container.html( '' ); } } }); }, errorCallback: function() { // console.log( 'errorCallback: ' ); this.errors.push( this.v2_timeout_verify ); this.showErrors(); }, show_reCaptchas: function( force_recaptcha ) { var obj = this.self; this.recaptcha_areas.each( function( index, element ) { obj.show_reCaptcha( index, element, obj, force_recaptcha ); }); }, show_reCaptcha: function( index, element, self, force_recaptcha ) { var container = $( element ); var form = container.closest( 'form' ); var version = container.data( 'version' ); var token_input_version = form.find( '.av-recaptcha-verify-token-version' ); if( force_recaptcha == 'force_v2' ) { version = 'avia_recaptcha_v2'; container.data( 'version', version ); var new_submit = form.find( '.av-recaptcha-submit' ); new_submit.attr( 'title', this.validate_first ); } token_input_version.val( version ); if( 'avia_recaptcha_v3' == version ) { return; } var widget_id = container.data( 'recaptcha_widet_id' ); if( 'undefined' != typeof widget_id ) { return; } widget_id = grecaptcha.render( container.get( 0 ), { 'sitekey': self.site_key2, 'callback': av_recaptcha_verifyCallback_v2, 'expired-callback': av_recaptcha_expiredCallback, 'error-callback': av_recaptcha_errorCallback, 'theme': container.data( 'theme' ), 'size':container.data( 'size' ) }); container.data( 'recaptcha_widet_id', widget_id ); }, reCaptchaSubmitButton: function( e ) { e.preventDefault(); var obj = this.self; var btn = $(e.target); var form = btn.closest( 'form' ); var container = form.find( '.av-recaptcha-area' ); var real_submit = form.find( '.av-recaptcha-submit-real' ); var version = container.data( 'version' ); var token = container.find( '.av-recaptcha-verify-token' ); if( 'avia_recaptcha_v2' == version ) { if( btn.hasClass( 'avia_button_inactive' ) ) { if( '' != btn.attr( 'title' ) ) { alert( btn.attr( 'title' ) ); } return false; } if( '' == token.val() ) { this.errors.push( this.cannot_use ); this.error_report.push( this.no_token ); this.showErrors( 'remove_buttons' ); return false; } real_submit.removeClass( 'avia_button_inactive' ); btn.hide(); real_submit.show(); real_submit.trigger( 'click' ); return; } if( 'avia_recaptcha_v3' != version ) { this.error_report.push( this.invalid_version ); this.showErrors(); return; } if( btn.hasClass( 'av-recaptcha-is-verified' ) ) { if( '' == token.val() ) { this.errors.push( this.cannot_use ); this.error_report.push( this.no_token ); this.showErrors( 'remove_buttons' ); return false; } real_submit.removeClass( 'avia_button_inactive' ); btn.hide(); real_submit.show(); real_submit.trigger( 'click' ); return; } btn.addClass( 'av-recaptcha-verify-v3 avia_button_inactive av-sending-button' ); grecaptcha.ready(function() { if( false === obj.monitor_V3_request ) { obj.monitor_V3_request = setTimeout( function(){ obj.monitorV3( 'verify_submit' ); }, 3000 ); } grecaptcha.execute( obj.site_key3, { action: 'verify_submit' } ).then( function( token ) { obj.verifyCallback( token, 'avia_recaptcha_v3', 'verify_submit' ); } ); }); }, showErrors: function( action ) { var obj = this.self; if( 0 == this.recaptcha_areas.length ) { obj.errors = []; obj.error_report = []; return; } this.recaptcha_areas.each( function( index, element ) { var container = $( element ); var form = container.closest( 'form' ); var msg = form.find( '.av-recaptcha-error' ); msg.addClass( 'av-recaptcha-severe-error' ).removeClass( 'av-err-content' ); var output1 = obj.errors.join( '
' ).trim(); var output2 = ''; if( obj.body.hasClass( 'av-recaptcha-extended-errors' ) ) { output2 = obj.error_report.join( '
' ).trim(); if( output2 != '' && output1 != '' ) { output2 = '
' + output2; } } msg.html( output1 + output2 ); if( 'remove_buttons' == action ) { form.find( '.av-recaptcha-submit, .av-recaptcha-submit-real' ).remove(); } }); obj.errors = []; obj.error_report = []; }, unique_id: function( index ) { var body = $( 'body' ); var id = 'av-verify-recaptcha-'; var cnt = 0; var first = true; var unique = id + index; do { if( ! first ) { unique = id + cnt; } if( 0 == body.find( '[data-unique_recaptcha_id="' + unique + '"]' ).length ) { return unique; } first = false; cnt ++; }while( true ) } }; av_recaptcha_main_api_loaded = function() { obj_avia_recaptcha.main_api_loaded(); }; av_recaptcha_verifyCallback_v2 = function( token ) { // needed to get access to grecaptcha.getResponse() to check for clicked submit button setTimeout( function(){ obj_avia_recaptcha.verifyCallback( token, 'avia_recaptcha_v2', 'verify_token' ); }, 50 ); }; av_recaptcha_errorCallback = function( token ) { // console.log( 'av_recaptcha_errorCallback' ); obj_avia_recaptcha.errorCallback( token ); }; av_recaptcha_expiredCallback = function() { // console.log( 'av_recaptcha_expiredCallback' ); }; if( null == obj_avia_recaptcha ) { obj_avia_recaptcha = new avia_recaptcha(); } })(jQuery);