HEX
Server: LiteSpeed
System: Linux node612.namehero.net 4.18.0-553.121.1.lve.el8.x86_64 #1 SMP Thu Apr 30 16:40:41 UTC 2026 x86_64
User: dfwparty (1186)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: //home/dfwparty/www/wp-content/plugins/ultimate-elementor/classes/class-uael-turnstile.php
<?php
/**
 * UAEL Cloudflare Turnstile Integration
 *
 * @package UAEL
 * @since 1.42.2
 */

namespace UltimateElementor\Classes;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

/**
 * Class UAEL_Turnstile
 *
 * @since 1.42.2
 */
class UAEL_Turnstile {

	/**
	 * Instance
	 *
	 * @since 1.42.2
	 * @var UAEL_Turnstile
	 */
	private static $instance;

	/**
	 * Get Instance
	 *
	 * @since 1.42.2
	 * @return UAEL_Turnstile
	 */
	public static function get_instance() {
		if ( ! isset( self::$instance ) ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Constructor
	 *
	 * @since 1.42.2
	 */
	public function __construct() {
		add_action( 'init', array( $this, 'init' ) );
	}

	/**
	 * Initialize Turnstile integration
	 *
	 * @since 1.42.2
	 */
	public function init() {
		
		// Hook into login validation.
		add_action( 'uael_login_validation', array( $this, 'validate_login_turnstile' ), 5, 1 );
		
		// Hook into registration validation.
		add_action( 'uael_registration_validation', array( $this, 'validate_registration_turnstile' ), 5, 1 );
		
		// Enqueue scripts when needed.
		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_turnstile_scripts' ) );
		
		// Hook into UAE AJAX submissions to inject Turnstile token.
		add_action( 'wp_ajax_uael_login_form_submit', array( $this, 'inject_turnstile_token' ), 1 );
		add_action( 'wp_ajax_nopriv_uael_login_form_submit', array( $this, 'inject_turnstile_token' ), 1 );
		add_action( 'wp_ajax_uael_register_form_submit', array( $this, 'inject_turnstile_token' ), 1 );
		add_action( 'wp_ajax_nopriv_uael_register_form_submit', array( $this, 'inject_turnstile_token' ), 1 );
	}

	/**
	 * Check if Turnstile is enabled for login forms.
	 * Turnstile is enabled if site key and secret key are configured.
	 * Individual forms can control whether to use it via widget settings.
	 *
	 * @since 1.42.2
	 * @return bool
	 */
	public function is_login_turnstile_enabled() {
		$settings = $this->get_turnstile_settings();
		return ! empty( $settings['site_key'] ) && ! empty( $settings['secret_key'] );
	}

	/**
	 * Check if Turnstile is enabled for registration forms.
	 * Turnstile is enabled if site key and secret key are configured.
	 * Individual forms can control whether to use it via widget settings.
	 *
	 * @since 1.42.2
	 * @return bool
	 */
	public function is_registration_turnstile_enabled() {
		$settings = $this->get_turnstile_settings();
		return ! empty( $settings['site_key'] ) && ! empty( $settings['secret_key'] );
	}

	/**
	 * Get Turnstile settings
	 *
	 * @since 1.42.2
	 * @return array
	 */
	public function get_turnstile_settings() {
		$integration_settings = \UltimateElementor\Classes\UAEL_Helper::get_admin_settings_option( '_uael_integration', array(), true );
		
		$default_settings = array(
			'enable_login'        => false,
			'enable_registration' => false,
			'site_key'            => '',
			'secret_key'          => '',
			'theme'               => 'auto',
			'size'                => 'normal',
		);

		// Map from integration settings to our format.
		$turnstile_settings = array(
			'enable_login'        => false, // Will be controlled per form widget.
			'enable_registration' => false, // Will be controlled per form widget.
			'site_key'            => isset( $integration_settings['cloudflare_turnstile_site_key'] ) ? $integration_settings['cloudflare_turnstile_site_key'] : '',
			'secret_key'          => isset( $integration_settings['cloudflare_turnstile_secret_key'] ) ? $integration_settings['cloudflare_turnstile_secret_key'] : '',
			'theme'               => 'auto',
			'size'                => 'normal',
		);

		return wp_parse_args( $turnstile_settings, $default_settings );
	}


	/**
	 * Validate Turnstile for login forms
	 *
	 * @since 1.42.2
	 * @param array $credentials Login credentials (unused).
	 */
	public function validate_login_turnstile( $credentials ) {
		// Prevent unused parameter warning.
		unset( $credentials );
		
		if ( ! $this->is_login_turnstile_enabled() ) {
			return;
		}

		$this->validate_turnstile_response( 'login' );
	}

	/**
	 * Validate Turnstile for registration forms
	 *
	 * @since 1.42.2
	 * @param array $user_data Registration data (unused).
	 */
	public function validate_registration_turnstile( $user_data ) {
		// Prevent unused parameter warning.
		unset( $user_data );
		
		if ( ! $this->is_registration_turnstile_enabled() ) {
			return;
		}

		$this->validate_turnstile_response( 'registration' );
	}

	/**
	 * Validate Turnstile response
	 *
	 * @since 1.42.2
	 * @param string $form_type Type of form (login/registration).
	 */
	private function validate_turnstile_response( $form_type ) {
		// Prevent unused parameter warning.
		unset( $form_type );
		$settings = $this->get_turnstile_settings();
		
		$turnstile_response = '';
		
		// Try to get turnstile response from different possible locations.
		// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce verification handled by form plugin.
		if ( isset( $_POST['data']['cf-turnstile-response'] ) && ! empty( $_POST['data']['cf-turnstile-response'] ) ) {
			// UAE forms nest field data within a data array structure.
			// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification handled by form plugin.
			$turnstile_response = sanitize_text_field( wp_unslash( $_POST['data']['cf-turnstile-response'] ) );
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification handled by form plugin.
		} elseif ( isset( $_POST['cf-turnstile-response'] ) && ! empty( $_POST['cf-turnstile-response'] ) ) {
			// Direct POST structure: $_POST['field_name'].
			// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification handled by form plugin.
			$turnstile_response = sanitize_text_field( wp_unslash( $_POST['cf-turnstile-response'] ) );
		} else {
			// Check if it's in the parsed data array (UAE sometimes structures it differently).
			// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce verification handled by form plugin.
			if ( isset( $_POST['data'] ) && is_array( $_POST['data'] ) ) {
				// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce verification handled by form plugin.
				foreach ( $_POST['data'] as $key => $value ) {
					if ( strpos( $key, 'cf-turnstile-response' ) !== false && ! empty( $value ) ) {
						$turnstile_response = sanitize_text_field( wp_unslash( $value ) );
						break;
					}
				}
			}
			
			if ( empty( $turnstile_response ) ) {
				wp_die( 
					esc_html__( 'Please complete the security verification.', 'uael' ), 
					esc_html__( 'Security Verification Required', 'uael' ), 
					array( 'back_link' => true ) 
				);
			}
		}
		$remote_ip = $this->get_client_ip();

		$validation_result = $this->verify_turnstile_token( 
			$settings['secret_key'], 
			$turnstile_response, 
			$remote_ip 
		);

		if ( ! $validation_result['success'] ) {
			$error_message = isset( $validation_result['error'] ) ? $validation_result['error'] : __( 'Security verification failed. Please try again.', 'uael' );
			
			wp_die( 
				esc_html( $error_message ), 
				esc_html__( 'Security Verification Failed', 'uael' ), 
				array( 'back_link' => true ) 
			);
		}
	}

	/**
	 * Verify Turnstile token with Cloudflare API
	 *
	 * @since 1.42.2
	 * @param string $secret_key Secret key.
	 * @param string $response Turnstile response.
	 * @param string $remote_ip Remote IP.
	 * @return array
	 */
	private function verify_turnstile_token( $secret_key, $response, $remote_ip ) {
		if ( empty( $secret_key ) ) {
			return array(
				'success' => false,
				'error'   => __( 'Turnstile secret key is not configured.', 'uael' ),
			);
		}

		if ( empty( $response ) ) {
			return array(
				'success' => false,
				'error'   => __( 'Turnstile response is missing.', 'uael' ),
			);
		}

		$body = array(
			'secret'   => $secret_key,
			'response' => $response,
			'remoteip' => $remote_ip,
		);

		$args = array(
			'body'    => $body,
			'timeout' => 15,
			'headers' => array(
				'Content-Type' => 'application/x-www-form-urlencoded',
			),
		);

		$api_response = wp_remote_post( 'https://challenges.cloudflare.com/turnstile/v0/siteverify', $args );

		if ( is_wp_error( $api_response ) ) {
			return array(
				'success' => false,
				'error'   => $api_response->get_error_message(),
			);
		}

		$response_body = wp_remote_retrieve_body( $api_response );
		$result        = json_decode( $response_body, true );

		if ( ! is_array( $result ) ) {
			return array(
				'success' => false,
				'error'   => __( 'Invalid response from Turnstile API.', 'uael' ),
			);
		}

		if ( empty( $result['success'] ) ) {
			$error_codes   = isset( $result['error-codes'] ) ? $result['error-codes'] : array();
			$error_message = $this->get_turnstile_error_message( $error_codes );
			
			return array(
				'success'     => false,
				'error'       => $error_message,
				'error-codes' => $error_codes,
			);
		}

		return array( 'success' => true );
	}

	/**
	 * Get user-friendly error message for Turnstile error codes
	 *
	 * @since 1.42.2
	 * @param array $error_codes Error codes from API.
	 * @return string
	 */
	private function get_turnstile_error_message( $error_codes ) {
		if ( empty( $error_codes ) || ! is_array( $error_codes ) ) {
			return __( 'Security verification failed. Please try again.', 'uael' );
		}

		$error_messages = array(
			'missing-input-secret'   => __( 'The secret parameter was not passed.', 'uael' ),
			'invalid-input-secret'   => __( 'The secret parameter was invalid or did not exist.', 'uael' ),
			'missing-input-response' => __( 'The response parameter (token) was not passed.', 'uael' ),
			'invalid-input-response' => __( 'The response parameter (token) is invalid or has expired.', 'uael' ),
			'bad-request'            => __( 'The request was rejected because it was malformed.', 'uael' ),
			'timeout-or-duplicate'   => __( 'The response parameter (token) has already been validated or has expired.', 'uael' ),
			'internal-error'         => __( 'An internal error happened while validating the response.', 'uael' ),
		);

		$error_code = $error_codes[0];
		
		if ( isset( $error_messages[ $error_code ] ) ) {
			return $error_messages[ $error_code ];
		}

		return __( 'Security verification failed. Please try again.', 'uael' );
	}

	/**
	 * Get client IP address
	 *
	 * @since 1.42.2
	 * @return string
	 */
	private function get_client_ip() {
		$ip_keys = array( 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR' );
		
		foreach ( $ip_keys as $key ) {
			if ( isset( $_SERVER[ $key ] ) && ! empty( $_SERVER[ $key ] ) ) {
				$ip = sanitize_text_field( wp_unslash( $_SERVER[ $key ] ) );
				if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {
					return $ip;
				}
			}
		}

		// phpcs:ignore WordPressVIPMinimum.Variables.ServerVariables.UserControlledHeaders,WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___SERVER__REMOTE_ADDR__ -- IP validation for security verification.
		$remote_addr = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
		if ( filter_var( $remote_addr, FILTER_VALIDATE_IP ) ) {
			return $remote_addr;
		}
		return '';
	}

	/**
	 * Render Turnstile widget HTML
	 *
	 * @since 1.42.2
	 * @param string $form_type Type of form.
	 * @param string $node_id Node ID.
	 */
	public function render_turnstile_widget( $form_type, $node_id ) {
		$settings  = $this->get_turnstile_settings();
		$widget_id = 'uael-turnstile-' . $form_type . '-' . $node_id;

		// Enqueue Turnstile API script.
		wp_enqueue_script(
			'cloudflare-turnstile',
			'https://challenges.cloudflare.com/turnstile/v0/api.js?onload=uaelTurnstileOnLoad',
			array( 'jquery' ),
			UAEL_VER,
			false
		);

		// Enqueue UAE Turnstile handler script.
		$folder = \UltimateElementor\Classes\UAEL_Helper::get_js_folder();
		$suffix = \UltimateElementor\Classes\UAEL_Helper::get_js_suffix();

		wp_enqueue_script(
			'uael-turnstile',
			UAEL_URL . 'assets/' . $folder . '/uael-turnstile' . $suffix . '.js',
			array( 'jquery' ),
			UAEL_VER,
			true
		);

		?>
		<div class="uael-turnstile-container">
			<div class="uael-turnstile-widget">
				<div id="<?php echo esc_attr( $widget_id ); ?>"
					class="cf-turnstile"
					data-sitekey="<?php echo esc_attr( $settings['site_key'] ); ?>"
					data-theme="<?php echo esc_attr( $settings['theme'] ); ?>"
					data-size="<?php echo esc_attr( $settings['size'] ); ?>"
					data-action="uael_<?php echo esc_attr( $form_type ); ?>_form"
					data-callback="onUAELTurnstileCallback"
					data-error-callback="onUAELTurnstileError"
					data-widget-id="<?php echo esc_attr( $widget_id ); ?>"
					data-node-id="<?php echo esc_attr( $node_id ); ?>"
					data-retry="auto"
					data-retry-interval="8000">
				</div>
			</div>
		</div>
		<?php
	}

	/**
	 * Enqueue Turnstile scripts
	 *
	 * @since 1.42.2
	 */
	public function enqueue_turnstile_scripts() {
		// Only enqueue if at least one form type is enabled.
		if ( ! $this->is_login_turnstile_enabled() && ! $this->is_registration_turnstile_enabled() ) {
			return;
		}

		// Don't load on admin pages.
		if ( is_admin() ) {
			return;
		}

		// Enqueue Turnstile API script.
		wp_enqueue_script(
			'cloudflare-turnstile',
			'https://challenges.cloudflare.com/turnstile/v0/api.js',
			array(),
			UAEL_VER,
			array(
				'strategy' => 'defer',
			)
		);

		// Add data attribute to prevent Cloudflare Rocket Loader issues.
		add_filter( 'script_loader_tag', array( $this, 'add_turnstile_script_attributes' ), 10, 2 );

		// Enqueue UAE Turnstile handler script.
		$folder = \UltimateElementor\Classes\UAEL_Helper::get_js_folder();
		$suffix = \UltimateElementor\Classes\UAEL_Helper::get_js_suffix();

		wp_enqueue_script(
			'uael-turnstile',
			UAEL_URL . 'assets/' . $folder . '/uael-turnstile' . $suffix . '.js',
			array( 'jquery' ),
			UAEL_VER,
			true
		);

		// Create nonce for secure AJAX communication.
		wp_localize_script(
			'uael-turnstile',
			'uaelTurnstileData',
			array(
				'nonce'    => wp_create_nonce( 'uael_turnstile_nonce' ),
				'ajax_url' => admin_url( 'admin-ajax.php' ),
			)
		);
	}

	/**
	 * Add data attributes to Turnstile script tag
	 *
	 * @since 1.42.2
	 * @param string $tag Script tag.
	 * @param string $handle Script handle.
	 * @return string
	 */
	public function add_turnstile_script_attributes( $tag, $handle ) {
		if ( 'cloudflare-turnstile' === $handle ) {
			$tag = str_replace( '<script ', '<script data-cfasync="false" ', $tag );
		}
		return $tag;
	}



	/**
	 * Inject Turnstile token into UAE AJAX form submissions
	 *
	 * @since 1.42.2
	 */
	public function inject_turnstile_token() {

		// Only process if we have POST data.
		if ( empty( $_POST ) ) {
			return;
		}

		// Verify nonce for security.
		// Note: Nonce verification is optional for backward compatibility.
		// If forms don't include the nonce, we allow the request to proceed.
		$nonce = '';
		if ( isset( $_POST['uael_turnstile_nonce'] ) ) {
			$nonce = sanitize_text_field( wp_unslash( $_POST['uael_turnstile_nonce'] ) );
		}

		// Verify the nonce if present - failure is allowed for backward compatibility.
		wp_verify_nonce( $nonce, 'uael_turnstile_nonce' );

		// Look for Turnstile token in various locations and inject it into the data array.
		$turnstile_token = '';
		
		// Check direct POST first.
		if ( isset( $_POST['cf-turnstile-response'] ) && ! empty( $_POST['cf-turnstile-response'] ) ) {
			$turnstile_token = sanitize_text_field( wp_unslash( $_POST['cf-turnstile-response'] ) );
		}

		// Check if there's a token in the data array already.
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Data sanitized when used.
		if ( empty( $turnstile_token ) && isset( $_POST['data'] ) && is_array( $_POST['data'] ) ) {
			// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Data sanitized when used.
			foreach ( $_POST['data'] as $key => $value ) {
				if ( strpos( $key, 'cf-turnstile-response' ) !== false && ! empty( $value ) ) {
					$turnstile_token = $value;
					break;
				}
			}
		}

		// Check for tokens that might be nested deeper or have different names.
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Data sanitized when used.
		if ( empty( $turnstile_token ) && isset( $_POST['data'] ) && is_array( $_POST['data'] ) ) {
			// Check for any field that might contain a Turnstile-like token.
			// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Data sanitized when used.
			foreach ( $_POST['data'] as $key => $value ) {
				if ( is_string( $value ) && strlen( $value ) > 100 && preg_match( '/^[A-Za-z0-9._-]+$/', $value ) ) {
					$turnstile_token = sanitize_text_field( wp_unslash( $value ) );
					break;
				}
			}
		}

		// If we found a token, ensure it's in the data array where UAE expects form fields.
		if ( ! empty( $turnstile_token ) ) {
			if ( ! isset( $_POST['data'] ) ) {
				$_POST['data'] = array();
			}
			$_POST['data']['cf-turnstile-response'] = $turnstile_token;
		}
	}
}

// Initialize the class.
UAEL_Turnstile::get_instance();