service_name = 'cloudflare'; $this->title = 'Cloudflare'; $this->icon = 'cloud'; $this->defineFields(); $this->defineInstructions(); parent::__construct(); } protected function defineFields():void { $site_key = $this->addField('site_key', 'Site Key', 'text', 'manage_options'); $site_key->setRequired(); $site_key->setHint('Public key for Turnstile Widget'); $secret_key = $this->addField('secret_key', 'Secret Key', 'text', 'manage_options'); $secret_key->setRequired(); $secret_key->setSubType('password'); $secret_key->setHint('Secret key for server-side verification of Turnstile Widget'); } protected function defineInstructions():void { $this->instructions = [ 'Go to Cloudflare Dashboard', 'Create a new Turnstile widget', 'Add your domain to the widget settings', 'Copy the Site Key and Secret Key' ]; } protected function initialize(): void { // TODO: Implement initialize() method. add_action('woocommerce_review_order_before_submit', [$this, 'addToCheckout']); add_action('wp_enqueue_scripts', [$this, 'enqueueOnCheckout']); add_action('woocommerce_checkout_process', [$this, 'checkoutTurnstile']); } public function getServiceDescription():string { return "Add Turnstile captcha protection to your forms and login pages to prevent spam and bot submissions."; } public function performConnectionTest(): bool { $credentials = $this->loadCredentials(); if (!array_key_exists('site_key', $credentials) || !array_key_exists('secret_key', $credentials)) { return false; } // Test with a dummy token to verify API connectivity $response = wp_remote_post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [ 'body' => [ 'secret' => $credentials['secret_key'], 'response' => 'test-token-connection-check' ], 'timeout' => 10 ]); // We expect this to fail with success:false, but not with a connection error if (is_wp_error($response)) { return false; } $result = json_decode(wp_remote_retrieve_body($response), true); // If we get a response with success field (even if false), connection works return array_key_exists('success', $result); } /** * Enqueue Turnstile script */ public function enqueueTurnstileScripts(): void { wp_enqueue_script( 'cloudflare-turnstile', 'https://challenges.cloudflare.com/turnstile/v0/api.js', [], null, true ); wp_script_add_data('cloudflare-turnstile', 'async', true); wp_script_add_data('cloudflare-turnstile', 'defer', true); } /** * Render Turnstile widget * Can be called directly: jvbConnect('cloudflare')->renderTurnstile() * Or Cloudflare::getInstance()->renderTurnstile() */ public function renderTurnstile(array|string $options = []): void { if (!$this->isSetUp()) { return; } if (!is_array($options)) { $options = []; } $defaults = [ 'theme' => $this->theme, 'size' => $this->size, 'wrapper_class' => 'cf-turnstile-wrapper', 'wrapper_style' => 'margin: 1em 0;' ]; $cred = $this->loadCredentials(); $options = wp_parse_args($options, $defaults); echo sprintf( '
', esc_attr($options['wrapper_class']), esc_attr($options['wrapper_style']), $cred['site_key'], $options['theme'], $options['size'] ); } /** * Verify the Turnstile response * Can be called directly via Cloudflare::getInstance()->verifyTurnstile($token) * @param string|null $token * @param string $remote_ip * @return bool */ public function verifyTurnstile(?string $token = null, string $remote_ip = ''):bool { if (!$this->isSetUp()) { return true; } // If no token provided, try to get it from $_POST if (!$token && isset($_POST['cf-turnstile-response'])) { $token = sanitize_text_field($_POST['cf-turnstile-response']); } if (empty($token)) { return false; } $cred = $this->loadCredentials(); $data = [ 'secret' => $cred['secret_key'], 'response' => $token, ]; if (empty($remote_ip) && isset($_SERVER['REMOTE_ADDR'])) { $remote_ip = sanitize_text_field($_SERVER['REMOTE_ADDR']); } if (!empty($remote_ip)) { $data['remoteip'] = $remote_ip; } $response = wp_remote_post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [ 'body' => $data, 'timeout' => 10 ]); if (is_wp_error($response)) { $this->logError('verifyTurnstile', 'Turnstile verification failed: ' . $response->get_error_message()); return false; } $result = json_decode(wp_remote_retrieve_body($response), true); if (!$result['success']??false) { $error_codes = implode(', ', $result['error-codes'] ?? ['Unknown error']); $this->logError('verifyTurnstile', 'Turnstile verification failed: ' . $error_codes); } return $result['success'] ?? false; } public function enqueueOnCheckout():void { if (function_exists('is_checkout') && is_checkout()) { self::enqueueTurnstileScripts(); } } public function addToCheckout():void { self::renderTurnstile(); } public function checkoutTurnstile():void { $verified = $this->verifyTurnstile(); if (!$verified && function_exists('wc_add_notice')) { wc_add_notice('Security check failed. Please try again', 'error'); return; } } }