<?php
/**
 * Base class to handle Google reCAPTCHA API functionality.
 *
 * @tutorial https://developers.google.com/recaptcha/docs/faq
 * @since 4.5.3
 * @since 4.5.7.2  refactored by Günter
 */
if( ! defined('AVIA_FW') ) { exit( 'No direct script access allowed' ); }


if( ! class_exists( 'av_google_recaptcha', false ) )
{
	class av_google_recaptcha extends aviaFramework\base\object_properties
	{
		const API_URL = 'https://www.google.com/recaptcha/api.js';
		const API_VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify';
		const API_URL_LANG = 'en';

		const AJAX_VERIFY_NONCE = 'av_google_recaptcha_verify_nonce';
		const TRANSIENT_PREFIX = 'av_google_recaptcha_';

		/**
		 *
		 * @since 4.5.7.2
		 * @var av_google_recaptcha
		 */
		static protected $_instance = null;

		/**
		 * Version of reCAPTCHA
		 *
		 * @since 4.5.7.2
		 * @var string
		 */
		protected $version;

		/**
		 *
		 * @since 4.5.7.2
		 * @var array
		 */
		protected $site_keys;

		/**
		 *
		 * @since 4.5.7.2
		 * @var array
		 */
		protected $secret_keys;

		/**
		 * Stores the last verified keys (user might change keys and save without verifying)
		 *
		 * @since 4.5.7.2
		 * @var array					'' | 'string combination to verify' | 'verify_error'
		 */
		protected $verified_keys;

		/**
		 * V3 score threshold (0.0 - 1.0)
		 *
		 * @since 4.5.7.2
		 * @var float
		 */
		protected $score;

		/**
		 * Backend theme color for V2
		 *
		 * @since 4.5.7.2
		 * @var string
		 */
		protected $theme;

		/**
		 * Contains the option value
		 *
		 * @since 4.5.7.2
		 * @var string
		 */
		protected $display_badge;

		/**
		 * Contains the HTML for replacement text
		 *
		 * @since 4.6.2
		 * @var string
		 */
		protected $display_badge_html;

		/**
		 *
		 * @since 4.5.7.2
		 * @var boolean|null
		 */
		protected $loading_prohibited;

		/**
		 * Return the instance of this class
		 *
		 * @since 4.5.7.2
		 * @return av_google_recaptcha
		 */
		static public function instance()
		{
			if( is_null( av_google_recaptcha::$_instance ) )
			{
				av_google_recaptcha::$_instance = new av_google_recaptcha();
			}

			return av_google_recaptcha::$_instance;
		}

		/**
		 *
		 * @since 4.5.7.2
		 */
		protected function __construct()
		{
			$this->loading_prohibited = null;
			$this->version = '';
			$this->site_keys = array();
			$this->secret_keys = array();
			$this->verified_keys = array();
			$this->score = 0.5;
			$this->theme = 'light';
			$this->display_badge = '';
			$this->display_badge_html = '';


			//	needed because not all framework functions are loaded at this point
			add_action( 'after_setup_theme', array( $this, 'handler_after_setup_theme' ), 10 );

			add_action( 'init', array( $this, 'handler_wp_register_scripts' ), 20 );
			add_action( 'wp_enqueue_scripts', array( $this, 'handler_wp_enqueue_scripts' ), 500 );
			add_action( 'admin_enqueue_scripts', array( $this, 'handler_wp_admin_enqueue_scripts' ), 500 );

			add_filter( 'body_class', array( $this, 'handler_body_class' ), 500, 2 );

			//	We hook with low priority as reCAPTCHA verification should overrule other positive checks
			add_filter( 'avf_form_send', array( $this, 'handler_avf_form_send' ), 999999, 4 );

			add_action( 'wp_ajax_avia_recaptcha_verify_frontend', array( $this, 'handler_recaptcha_verify_frontend' ), 10 );
			add_action( 'wp_ajax_nopriv_avia_recaptcha_verify_frontend', array( $this, 'handler_recaptcha_verify_frontend' ), 10 );
		}

		/**
		 *
		 * @since 4.5.7.2
		 */
		public function __destruct()
		{
			unset( $this->site_keys );
			unset( $this->secret_keys );
			unset( $this->verified_keys );
		}

		/**
		 * @since 4.5.7.2
		 */
		public function handler_after_setup_theme()
		{
			$this->version = avia_get_option( 'avia_recaptcha_version', '' );

			$this->site_keys['avia_recaptcha_v2'] = avia_get_option( 'avia_recaptcha_pkey_v2', '' );
			$this->site_keys['avia_recaptcha_v3'] = avia_get_option( 'avia_recaptcha_pkey_v3', '' );

			$this->secret_keys['avia_recaptcha_v2'] = avia_get_option( 'avia_recaptcha_skey_v2', '' );
			$this->secret_keys['avia_recaptcha_v3'] = avia_get_option( 'avia_recaptcha_skey_v3', '' );

			$this->verified_keys['avia_recaptcha_v2'] = avia_get_option( 'recaptcha_verified_keys_v2', '' );
			$this->verified_keys['avia_recaptcha_v3'] = avia_get_option( 'recaptcha_verified_keys_v3', '' );

			$this->score = avia_get_option( 'avia_recaptcha_score', 5 );
			$this->score = is_numeric( $this->score ) ? $this->score / 10.0 : 0.5;

			$this->display_badge = avia_get_option( 'avia_recaptcha_badge', '' );
			if( ! current_theme_supports( 'avia_recaptcha_show_legal_information' ) )
			{
				$this->display_badge = 'contact_only_message';
			}

			/**
			 * see https://developers.google.com/recaptcha/docs/faq#id-like-to-hide-the-recaptcha-badge-what-is-allowed
			 *
			 * @since 4.5.7.2
			 * @param string
			 * @return string
			 */
			$badge =	'<div class="av-google-badge-message hidden">';
			$badge .=		sprintf( __( 'This site is protected by reCAPTCHA and the Google %1$s and %2$s apply.', 'avia_framework' ), sprintf( '<a href="https://policies.google.com/privacy" target="_blank" rel="noopener noreferrer">%s</a>', __( 'Privacy Policy', 'avia_framework' ) ), sprintf( '<a href="https://policies.google.com/terms" target="_blank" rel="noopener noreferrer">%s</a>', __( 'Terms of Service', 'avia_framework' ) ) );
			$badge .=	'</div>';

			$this->display_badge_html = apply_filters( 'avf_google_recaptcha_badge_content', $badge );
		}

		/**
		 * @since 4.5.7.2
		 */
		public function handler_wp_register_scripts()
		{
			$vn = avia_get_theme_version();
			$min_js = avia_minify_extension( 'js' );

			wp_register_script( 'avia_google_recaptcha_front_script' , AVIA_JS_URL . "conditional_load/avia_google_recaptcha_front{$min_js}.js", array( 'jquery' ), $vn, true );
			wp_register_script( 'avia_google_recaptcha_api_script' , AVIA_JS_URL . "conditional_load/avia_google_recaptcha_api{$min_js}.js", array( 'jquery' ), $vn, true );
		}

		/**
		 * Frontend we load conditionally. This script checks after pageload if loading of main scripts are necessary.
		 *
		 * @since 4.5.7.2
		 */
		public function handler_wp_enqueue_scripts()
		{
			if( $this->is_loading_prohibited() )
			{
				return;
			}

			wp_enqueue_script( 'avia_google_recaptcha_front_script' );

			/**
			 * @since 5.3
			 * @param string $api_url
			 * @param string $context			'backend' | 'frontend'
			 * @return string
			 */
			$api_url = apply_filters( 'avf_google_recaptcha_apiurl', av_google_recaptcha::API_URL, 'frontend' );


			//	@since 5.6.1 https://kriesi.at/support/topic/ubersetzungsfehler-translation-mistake/#post-1404182
			$local = get_locale();
			$api_url_lang = ( empty( $local ) ) ? av_google_recaptcha::API_URL_LANG : substr( $local, 0, 2 );

			/**
			 * @since 5.3
			 * @since 5.6.1						added $default
			 * @param string $api_url_lang
			 * @param string $context			'backend' | 'frontend'
			 * @param string $default
			 * @return string
			 */
			$api_url_lang = apply_filters( 'avf_google_recaptcha_apiurl_lang', $api_url_lang, 'frontend', av_google_recaptcha::API_URL_LANG );

			$args = array(
					'version'			=> $this->get_version(),
					'site_key2'			=> $this->get_site_key( 'avia_recaptcha_v2' ),
					'site_key3'			=> $this->get_site_key( 'avia_recaptcha_v3' ),
					'api'				=> $api_url,
					'api_lang'			=> $api_url_lang,
					'avia_api_script'	=> AVIA_JS_URL . 'conditional_load/avia_google_recaptcha_api.js',
					'theme'				=> $this->get_theme(),
					'score'				=> $this->get_score(),
					'verify_nonce'		=> wp_create_nonce( av_google_recaptcha::AJAX_VERIFY_NONCE ),
//					'submission_nonce'	=> wp_create_nonce( av_google_recaptcha::AJAX_SUBMISSION_NONCE ),
					'cannot_use'		=> '<h3 class="av-recaptcha-error-main">' . __( 'Sorry, a problem occurred trying to communicate with Google reCAPTCHA API. You are currently not able to submit the contact form. Please try again later - reload the page and also check your internet connection.', 'avia_framework' ) . '</h3>',
					'init_error_msg'	=> __( 'Initial setting failed. Sitekey 2 and/or sitekey 3 missing in frontend.', 'avia_framework' ),
					'v3_timeout_pageload'	=> __( 'Timeout occurred connecting to V3 API on initial pageload', 'avia_framework' ),
					'v3_timeout_verify'	=> __( 'Timeout occurred connecting to V3 API on verifying submit', 'avia_framework' ),
					'v2_timeout_verify'	=> __( 'Timeout occurred connecting to V2 API on verifying you as human. Please try again and check your internet connection. It might be necessary to reload the page.', 'avia_framework' ),
					'verify_msg'		=> __( 'Verify....', 'avia_framework' ),
					'connection_error'	=> __( 'Could not connect to the internet. Please reload the page and try again.', 'avia_framework' ),
					'validate_first'	=> __( 'Please validate that you are a human first', 'avia_framework' ),
					'validate_submit'	=> __( 'Before submitting we validate that you are a human first.', 'avia_framework' ),
					'no_token'			=> __( 'Missing internal token on valid submit - unable to proceed.', 'avia_framework' ),
					'invalid_version'	=> __( 'Invalid reCAPTCHA version found.', 'avia_framework' ),
					'api_load_error'	=> __( 'Google reCAPTCHA API could not be loaded.', 'avia_framework' ),
				);

			wp_localize_script( 'avia_google_recaptcha_front_script', 'AviaReCAPTCHA_front', $args );
		}

		/**
		 *
		 * @since 4.5.7.2
		 */
		public function handler_wp_admin_enqueue_scripts()
		{
			/**
			 * Some 3rd party plugins need to supress loading scripts
			 * Not loading the scripts might result in breaking backend !!!
			 * Check if everything is working as expected.
			 *
			 * @since 4.7.5.1
			 * @param boolean
			 * @return string			return 'skip_loading' to prohibit loading of backend scripts
			 */
			$skip_loading = apply_filters( 'avf_skip_enqueue_scripts_backend_grecaptcha', '' );

			if( 'skip_loading' === $skip_loading )
			{
				return;
			}

			/**
			 * In backend we must enqueue to validate keys and localize script
			 */
			wp_enqueue_script( 'avia_google_recaptcha_front_script' );

			/**
			 * @since 5.3
			 * @param string $api_url
			 * @param string $context			'backend' | 'frontend'
			 * @return string
			 */
			$api_url = apply_filters( 'avf_google_recaptcha_apiurl', av_google_recaptcha::API_URL, 'backend' );

			//	@since 5.6.1 https://kriesi.at/support/topic/ubersetzungsfehler-translation-mistake/#post-1404182
			$local = get_locale();
			$api_url_lang = ( empty( $local ) ) ? av_google_recaptcha::API_URL_LANG : substr( $local, 0, 2 );

			/**
			 * @since 5.3
			 * @since 5.6.1						added $default
			 * @param string $api_url_lang
			 * @param string $context			'backend' | 'frontend'
			 * @param string $default
			 * @return string
			 */
			$api_url_lang = apply_filters( 'avf_google_recaptcha_apiurl_lang', $api_url_lang, 'backend', av_google_recaptcha::API_URL_LANG );

			$args = array(
					'version'			=> $this->get_version(),
//					'site_key'			=> $this->get_site_key(),
					'api'				=> $api_url,
					'api_lang'			=> $api_url_lang,
					'theme'				=> $this->get_theme(),
					'api_load_error'	=> __( 'Google reCAPTCHA API could not be loaded. We are not able to verify keys. Check your internet connection and try again.', 'avia_framework' ),
					'invalid_version'	=> __( 'Please select Version 2 or 3 for reCAPTCHA', 'avia_framework' ),
					'invalid_keys'		=> __( 'You have to enter a site key and a secret key to verify it.', 'avia_framework' ),
					'v3_timeout'		=> __( 'A network timeout problem occurred. Could be caused by an invalid V3 sitekey. Please recheck the key and try again.', 'avia_framework' )
	            );

			wp_localize_script( 'avia_google_recaptcha_front_script', 'AviaReCAPTCHA_data', $args );
		}

		/**
		 * We add classes to allow js script to decide what to load
		 *
		 * @since 4.5.7.2
		 * @param array $classes
		 * @param array $class
		 * @return array
		 */
		public function handler_body_class( array $classes, array $class )
		{
			if( $this->is_loading_prohibited() )
			{
				return $classes;
			}

			/**
			 * Allows to disable recaptcha for special pages.
			 * Only makes sense for V3 as V2 is only loaded if needed on page/post.
			 * Be careful not to disable it on pages where you need it !!!
			 *
			 * @since 4.5.7.2
			 * @param boolean
			 * @return boolean
			 */
			$disable = apply_filters( 'avf_disable_recaptchaV3_for_post', false );

			if( false === $disable )
			{
				$classes[] = 'av-recaptcha-enabled';
			}

			if( $this->show_extended_errors() )
			{
				$classes[] = 'av-recaptcha-extended-errors';
			}

			if( in_array( $this->display_badge, array( 'contact_only_message', 'hide' ) ) )
			{
				$classes[] = 'av-google-badge-hide';
			}

			return $classes;
		}

		/**
		 * Returns the selected version for the recaptcha
		 *
		 * @since 4.5.7.2
		 * @return string		'' | 'avia_recaptcha_v2' | 'avia_recaptcha_v3'
		 */
		public function get_version()
		{
			return $this->version;
		}

		/**
		 * Returns the site key for the selected version
		 *
		 * @since 4.5.7.2
		 * @param string $version
		 * @return string
		 */
		public function get_site_key( $version = null )
		{
			if( is_null( $version ) )
			{
				$version = $this->get_version();
			}

			return isset( $this->site_keys[ $version ] ) ? $this->site_keys[ $version ] : '';
		}

		/**
		 * Returns the secret key for the selected version
		 *
		 * @since 4.5.7.2
		 * @param string $version
		 * @return string
		 */
		public function get_secret_key( $version = null )
		{
			if( is_null( $version ) )
			{
				$version = $this->get_version();
			}

			return isset( $this->secret_keys[ $version ] ) ? $this->secret_keys[ $version ] : '';
		}

		/**
		 * Returns the last saved verified key string:
		 *		"version site_key secret_key"
		 *
		 * @since 4.5.7.2
		 * @param string $version
		 * @return string
		 */
		public function get_verified_keys( $version = null )
		{
			if( is_null( $version ) )
			{
				$version = $this->get_version();
			}

			return isset( $this->verified_keys[ $version ] ) ? $this->verified_keys[ $version ] : '';
		}

		/**
		 * Returns the float value for selected score threshold (0.0 - 1.0)
		 *
		 * @since 4.5.7.2
		 * @return float
		 */
		public function get_score()
		{
			return $this->score;
		}

		/**
		 * Returns the necessary HTML if user selected to use
		 *
		 * @since 4.5.7.2
		 * @return string
		 */
		public function get_display_badge_html()
		{
			if( ! in_array( $this->display_badge, array( 'message', 'contact_only_message' ) ) )
			{
				return '';
			}

			return $this->display_badge_html;
		}

		/**
		 * Returns the selected backend theme style
		 *
		 * @since 4.5.7.2
		 * @return string				'light' | 'dark'
		 */
		public function get_theme()
		{
			if( ! in_array( $this->theme, array( 'light', 'dark' ) ) )
			{
				$this->theme = 'light';
			}

			return $this->theme;
		}

		/**
		 *
		 * @since 4.5.7.2
		 * @return boolean
		 */
		public function is_loading_prohibited()
		{
			if( is_null( $this->loading_prohibited ) )
			{
				//	Check backend setting
				$prohibited = ! $this->is_activated() || ! $this->are_keys_set();

				/**
				 * Filter allows to supress loading of script on desired pages
				 *
				 * @since 4.5.7.2
				 * @param boolean
				 * @return boolean
				 */
				$prohibited = apply_filters( 'avf_load_google_recaptcha_api_prohibited', $prohibited );

				$this->loading_prohibited = is_bool( $prohibited ) ? $prohibited : true;
			}

			return $this->loading_prohibited;
		}

		/**
		 * Checks if the version activates gRECAPTCHA
		 *
		 * @since 4.5.7.2
		 * @param string $version
		 * @return boolean
		 */
		public function is_activated( $version = null )
		{
			if( is_null( $version ) )
			{
				$version = $this->get_version();
			}

			return in_array( $version, array( 'avia_recaptcha_v2', 'avia_recaptcha_v3' ) );
		}

		/**
		 * Checks if all necessary keys are set for a version.
		 * Returns false if an invalid version or if disabled
		 *
		 * @since 4.5.7.2
		 * @param string $version
		 * @return boolean
		 */
		public function are_keys_set( $version = null )
		{
			if( is_null( $version ) )
			{
				$version = $this->get_version();
			}

			$keys2 = ! empty( $this->get_site_key( 'avia_recaptcha_v2' ) ) && ! empty( $this->get_secret_key( 'avia_recaptcha_v2' ) );
			$keys3 = ! empty( $this->get_site_key( 'avia_recaptcha_v3' ) ) && ! empty( $this->get_secret_key( 'avia_recaptcha_v3' ) );

			if( 'avia_recaptcha_v2' == $version )
			{
				return $keys2;
			}

			if( 'avia_recaptcha_v3' == $version )
			{
				return $keys2 && $keys3;
			}

			return false;
		}

		/**
		 * Allow admins to see more detailed error messages
		 *
		 * @since 4.5.7.2
		 * @return boolean
		 */
		protected function show_extended_errors()
		{
			$show_extended_errors = current_user_can( 'manage_options' );

			/**
			 *
			 *
			 * @since 4.5.7.2
			 * @param boolean
			 * @return boolean				return true to show extended messages
			 */
			return apply_filters( 'avf_recaptcha_show_extended_error_messages', $show_extended_errors );
		}

		/**
		 * Verify a token with gRECAPTCHA.
		 *
		 * @since 4.5.7.2
		 * @param string $token
		 * @param string|null $secretkey			if null -> verification of key
		 * @return array|\WP_Error					check $result['success'] = true
		 */
		protected function verify_token( $token, $secretkey = null )
		{
			if( is_null( $secretkey ) )
			{
				$secretkey = $this->get_secret_key();
			}

			$params = array(
						'body' => array(
										'secret'      => $secretkey,
										'response'    => $token,
										'remoteip'    => $_SERVER['REMOTE_ADDR'],
									)
						);

			$response = wp_safe_remote_post( av_google_recaptcha::API_VERIFY_URL, $params );

			if( $response instanceof WP_Error )
			{
				$msg = $response->get_error_messages();
				$msg = implode( '<br />', $msg );

				return new WP_Error( 'site_down', sprintf( __( 'Unable to communicate with gRECAPTCHA: <br /><br />%s', 'avia_framework' ), $msg ) );
			}

			$code = wp_remote_retrieve_response_code( $response );
			if ( 200 != $code )
			{
				$msg = wp_remote_retrieve_response_message( $response );
				if( empty( $msg ) )
				{
					$msg = __( 'Unknown error code', 'avia_framework' );
				}
				return new WP_Error( 'invalid_response', sprintf( __( 'gRECAPTCHA returned error %d (= %s).', 'avia_framework' ), $code, $msg ) );
			}


			$body = wp_remote_retrieve_body( $response );
			$result = json_decode( $body, true );

			if( true === $result['success'] )
			{
				return $result;
			}

			if( isset( $result['error-codes'] ) && is_array( $result['error-codes'] ) )
			{
				foreach( $result['error-codes'] as $key => $value )
				{
					switch( $value )
					{
						case 'missing-input-secret':
							$result['error-codes'][ $key ] = __( 'The secret parameter is missing.', 'avia_framework' );
							break;
						case 'invalid-input-secret':
							$result['error-codes'][ $key ] = __( 'The secret parameter is invalid or malformed.', 'avia_framework' );
							break;
						case 'missing-input-response':
							$result['error-codes'][ $key ] = __( 'The response parameter is missing.', 'avia_framework' );
							break;
						case 'invalid-input-response':
							$result['error-codes'][ $key ] = __( 'The response parameter is invalid or malformed.', 'avia_framework' );
							break;
						case 'bad-request':
							$result['error-codes'][ $key ] = __( 'The request is invalid or malformed.', 'avia_framework' );
							break;
						case 'timeout-or-duplicate':
							$result['error-codes'][ $key ] = __( 'The response is no longer valid: either is too old or has been used previously.', 'avia_framework' );
							break;
						default:
							$result['error-codes'][ $key ] =  sprintf( __( '%s - unknown error code', 'avia_framework' ), $value );
					}
				}
			}

			return $result;
		}

		/**
		 * Callback - verifies the token and creates a transient if valid.
		 * In case of a false score returns
		 *
		 * @since 4.5.7.2
		 */
		public function handler_recaptcha_verify_frontend()
		{
			header( "Content-Type: application/json" );
			$response = array(
								'success'		=> false,
								'alert'			=> '',
								'score_failed'	=> false,
								'transient'		=> ''
							);

			$show_extended_errors = $this->show_extended_errors();

			/**
			 * Nonce check removed with 4.7.4.1
			 *
			 * Makes problems with caching plugins.
			 * As reCaptcha is bound to a site verifying the reCaptcha should be enough to verify a valid callback.
			 *
			 */
//			$nonce = isset( $_REQUEST['_wpnonce'] ) ? $_REQUEST['_wpnonce'] : '';
//			$result = wp_verify_nonce( $nonce, av_google_recaptcha::AJAX_VERIFY_NONCE );
//
//			if( 1 !== $result )
//			{
//				$response['alert'] = __( 'Sorry, but the session time for this page has expired. Please reload the page.', 'avia_framework' );
//				if( true === $show_extended_errors )
//				{
//					$response['alert'] .= '<br />' . __( 'WP Nonce check failed.', 'avia_framework' );
//				}
//
//				echo json_encode( $response );
//				exit;
//			}

			$version = isset( $_REQUEST['version'] ) ? $_REQUEST['version'] : '';
			$token = isset( $_REQUEST['token'] ) ? $_REQUEST['token'] : '';
			$score = isset( $_REQUEST['score'] ) ? $_REQUEST['score'] : -1;
			$action = isset( $_REQUEST['recaptcha_action'] ) ? $_REQUEST['recaptcha_action'] : '';
			$secret_key = $this->get_secret_key( $version );


			$check = $this->verify_token( $token, $secret_key );

			$validate_error = __( 'Sorry, but the verification failed. Please reload the page and try again.', 'avia_framework' );

			if( true !== $check['success'] )
			{
				$response['alert'] = $validate_error;
				if( true === $show_extended_errors )
				{
					$response['alert'] .= '<br />' . __( 'API check returned false.', 'avia_framework' );
				}

				echo json_encode( $response );
				exit;
			}

			if( 'avia_recaptcha_v3' == $version )
			{
				if( ! isset( $check['score'] ) || ! isset( $check['action'] ) || ( $check['action'] != $action ) )
				{
					$response['alert'] = $validate_error;
					if( true === $show_extended_errors )
					{
						$response['alert'] .= '<br />' . __( 'Invalid V3 response. Actions:', 'avia_framework' ) . $check['action'] . '/' . $action;
					}

					echo json_encode( $response );
					exit;
				}

				if( (float) $score >= (float)$check['score'] )
				{
					if( true === $show_extended_errors )
					{
						$response['score_failed'] = sprintf( __( 'Score requested %s - returned %s', 'avia_framework' ), $score, $check['score'] );
					}
					else
					{
						$response['score_failed'] = true;
					}

					echo json_encode( $response );
					exit;
				}
			}

			$transient = uniqid( av_google_recaptcha::TRANSIENT_PREFIX, true );

			/**
			 * @since 4.5.7.2
			 * @param int
			 * @return int
			 */
			$expiration = 30 * MINUTE_IN_SECONDS;
			$expiration = apply_filters( 'avf_recaptcha_transient_expiration', $expiration );
			if( ! is_numeric( $expiration ) )
			{
				$expiration = 30 * MINUTE_IN_SECONDS;
			}

			set_transient( $transient, $version, $expiration );

			$response['success'] = true;
			$response['transient'] = $transient;

			echo json_encode( $response );
			exit;
		}

		/**
		 * Check if we have a valid token.
		 * This is also a fallback to prevent bots using same token several times.
		 * In case $proceed !== true we also remove the transient to clean up.
		 *
		 * @since 4.5.7.2
		 * @param boolean $proceed
		 * @param array $new_post
		 * @param array $form_params
		 * @param avia_form $form_class
		 * @return boolean|null						true if you want to continue | null for error message
		 */
		public function handler_avf_form_send( $proceed, array $new_post, array $form_params, avia_form $form_class )
		{
			$use_recaptcha = false;

			foreach( $form_class->form_elements as $element )
			{
				if( isset( $element['type'] ) && ( 'grecaptcha' == $element['type'] ) )
				{
					$use_recaptcha = $element;
					break;
				}
			}

			if( false === $use_recaptcha )
			{
				return $proceed;
			}

			$show_extended_errors = $this->show_extended_errors();
			$token = isset( $element['token_input'] ) ? $element['token_input'] : '';
			$token_value = ( isset( $_REQUEST[ $token ] ) ) ? trim( $_REQUEST[ $token ] ) : '';
			$requested_version = ( isset( $_REQUEST[ $token . '-version' ] ) ) ? trim( $_REQUEST[ $token . '-version' ] ) : $element['version'];

			if( true !== $proceed )
			{
				if( ! empty( $token_value ) )
				{
					delete_transient( $token_value );
				}

				return $proceed;
			}

			$reload_msg = '<br />' . __( 'Form could not be submitted. Please reload page and try again.', 'avia_framework' );

			if( empty( $token_value ) )
			{
				$form_class->submit_error .= __( 'Invalid form for reCAPTCHA sent.', 'avia_framework' ) . $reload_msg;
				if( $show_extended_errors )
				{
					$form_class->submit_error .= '<br />' . __( 'Name for token field was missing.', 'avia_framework' );
				}
				return null;
			}

			$version = get_transient( $token_value );

			if( false === $version )
			{
				$form_class->submit_error .= __( 'Token to validate form already expired.', 'avia_framework' ) . $reload_msg;
				if( $show_extended_errors )
				{
					$form_class->submit_error .= '<br />' . __( 'Transient to verify form was missing or expired.', 'avia_framework' );
				}
				return null;
			}

			delete_transient( $token_value );

			if( $version != $requested_version )
			{
				$form_class->submit_error .= __( 'Token to validate form is not valid.', 'avia_framework' ) . $reload_msg;
				if( $show_extended_errors )
				{
					$form_class->submit_error .= '<br />' . __( 'reCAPTCHA version in transient differs from selected version in element.', 'avia_framework' );
				}
				return null;
			}

			return $proceed;
		}

		/**
		 * Output options page backend HTML or perform the key verification and return HTML message
		 *
		 * @since 4.5.7.2
		 * @param string $api_key
		 * @param boolean $ajax
		 * @param array|boolean|null $check_keys
		 * @param array $element				used in backend for output of key verification
		 * @return string|array
		 */
		public function backend_html( $api_key = '', $ajax = true, $check_keys = false, $element = array() )
		{
			$return = array(
							'html'                 => '',
							'update_input_fields'  => array()
						);

			$api_key = trim( $api_key );
			$valid_key = false;

			$response_text  = __( 'Could not connect and verify these API Keys with Google reCAPTCHA.', 'avia_framework' );
			$response_class = "av-notice-error";

			$content_default  =			'<h4>' . esc_html__( 'Troubleshooting:', 'avia_framework' ) . '</h4>';
			$content_default .=			'<ol>';
			$content_default .=				'<li>';
			$content_default .=					esc_html__( 'Check if you typed the keys correctly.', 'avia_framework' );
			$content_default .=				'</li>';
			$content_default .=				'<li>';
			$content_default .=					esc_html__( 'If you use the restriction setting on Google try to remove that, wait a few minutes for google to apply your changes and then check again if the keys work here. If it does, you probably have a syntax error in your referrer url', 'avia_framework' );
			$content_default .=				'</li>';
			$content_default .=				'<li>';
			$content_default .=					esc_html__( 'If none of this helps: deactivate all plugins and then check if the API works by using the button above. If thats the case then one of your plugins is interfering.', 'avia_framework' );
			$content_default .=				'</li>';
			$content_default .=			'</ol>';


			if( $ajax )
			{
				/**
				 * called by user pressing the ajax check button
				 */
				$token = isset( $check_keys['token'] ) ? trim( $check_keys['token'] ) : '';
				$secretkey = isset( $check_keys['secretkey'] ) ? trim( $check_keys['secretkey'] ) : '';
				$version = isset( $check_keys['version'] ) ? trim( $check_keys['version'] ) : '';
				switch( $version )
				{
					case 'avia_recaptcha_v2':
						$verify_field = 'recaptcha_verified_keys_v2';
						break;
					case 'avia_recaptcha_v3':
						$verify_field = 'recaptcha_verified_keys_v3';
						break;
					default:
						$verify_field = '';
						break;
				}

				$check = $this->verify_token( $token, $secretkey );

				if( true === $check['success'] )
				{
					$valid_key = true;
					$response_class = '';
					$response_text  = __( 'We were able to properly connect and verify your API keys with Google reCAPTCHA', 'avia_framework' );

					//will be stripped from the final output but tells the ajax script to save the page after the check was performed
					$response_text .= ' avia_trigger_save';

					$keys = array(
									$check_keys['version'],
									$check_keys['sitekey'],
									$check_keys['secretkey']
								);
					if( ! empty( $verify_field ) )
					{
						$return['update_input_fields'][ $verify_field ] = implode( ' ', $keys );
					}
				}
				else
				{
					$content_default = '';
					if( $check instanceof WP_Error )
					{
						$response_text = $check->get_error_message();
					}
					else
					{
						$msg = '';

						if( is_array( $check['error-codes'] ) && count( $check['error-codes'] ) > 0 )
						{
							$msg = implode( '<br />', $check['error-codes'] );
						}

						$response_text  = __( 'Error on connecting to Google reCAPTCHA - please retry.', 'avia_framework' );

						if( ! empty( $msg) )
						{
							$response_text .= '<br /><br />' . $msg;
						}
					}

					if( ! empty( $verify_field ) )
					{
						$return['update_input_fields'][ $verify_field ] = 'verify_error';
					}
				}
			}
			else
			{
				/**
				 * called on a normal page load. in this case we either show the stored result or if we got no stored result we show nothing
				 */
				if( $this->is_activated() )
				{
					switch( $element['id'] )
					{
						case 'avia_recaptcha_key_verify_v2':
							$version = 'avia_recaptcha_v2';
							break;
						case 'avia_recaptcha_key_verify_v3':
							$version = 'avia_recaptcha_v3';
							break;
						default:
							$version = '';
							break;
					}

					$keys = array(
									$version,
									$this->get_site_key( $version ),
									$this->get_secret_key( $version )
								);

					$check = implode( ' ', $keys );

					if( $this->get_verified_keys( $version ) == $check )
					{
						$valid_key = true;
					}
					else if( '' == $this->get_site_key( $version ) && '' == $this->get_secret_key( $version ) )
					{
						$response_class = '';
						$response_text = '';
					}
					else if( 'verify_error' == $this->get_verified_keys() )
					{
						$response_text  = __( 'A connection error occurred last time we tried verify your keys with Google reCAPTCHA - please revalidate the keys.', 'avia_framework' );
					}
					else if( '' == $this->get_verified_keys( $version ) )
					{
						$response_text  = __( 'Please verify the keys', 'avia_framework' );
					}
					else
					{
						$response_text  = __( 'Please verify the keys - the last verified keys are different.', 'avia_framework' );
					}

					$content_default = '';
				}

				if( $valid_key )
				{
					$response_class = '';
					$response_text  = __( 'Last time we checked we were able to connected to Google reCAPTCHA with your API keys', 'avia_framework' );
				}
			}

			if( $valid_key )
			{
				$content_default  = __( 'If you ever change your API key or the URL restrictions of the key please verify the key here again, to test if it works properly','avia_framework');
			}

			$output  = '';

			if( ! empty( $response_text ) )
			{
				$output  =	"<div class='av-verification-response-wrapper'>";
				$output .=		"<div class='av-text-notice {$response_class}'>";
				$output .=			$response_text;
				$output .=		"</div>";
				$output .=		"<div class='av-verification-cell'>{$content_default}</div>";
				$output .=	"</div>";
			}

			if( $ajax )
			{
				$return['html'] = $output;
			}
			else
			{
				$return = $output;
			}

			return $return;
		}

	}

	/**
	 * Returns the main instance of av_google_recaptcha to prevent the need to use globals.
	 *
	 * @since 4.5.7.2
	 * @return av_google_recaptcha
	 */
	function Avia_Google_reCAPTCHA()
	{
		return av_google_recaptcha::instance();
	}
}

Avia_Google_reCAPTCHA();


if( ! function_exists( 'av_recaptcha_api_check' ) )
{
	/**
	 * Callback function:
	 *		- ajax callback from verification button
	 *		- php callback when creating output on option page
	 *
	 * @since 4.5.7.2
	 * @param string $value
	 * @param boolean $ajax
	 * @param array|null $js_value
	 * @param array $element
	 * @return string
	 */
	function av_recaptcha_api_check( $value, $ajax = true, $js_value = null, $element = array() )
	{
		$api = Avia_Google_reCAPTCHA();
		return $api->backend_html( $value, $ajax, $js_value, $element );
	}

}
