service_name = 'cloudflare'; $this->title = 'Cloudflare Turnstile'; $this->icon = 'cloud'; $this->apiBase = 'https://challenges.cloudflare.com'; $this->apiEndpoints = ['turnstile/v0/siteverify']; $this->fields = [ 'site_key' => [ 'label' => 'Site Key', 'type' => 'text', 'placeholder' => 'Enter Public key for Turnstile widget display.', 'hint' => 'Public key for Turnstile widget', 'required' => true ], 'secret_key' => [ 'label' => 'Secret Key', 'type' => 'text', 'subtype' => 'password', 'placeholder' => 'Enter Cloudflare Turnstile Secret Key', 'required' => true, 'hint' => 'Secret key for server-side verification' ] ]; $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' ]; $this->advanced = []; parent::__construct(); } protected function initialize(): void { $this->site_key = $this->credentials['site_key'] ?? ''; $this->secret_key = $this->credentials['secret_key'] ?? ''; $this->theme = $this->credentials['theme'] ?? 'light'; $this->size = $this->credentials['size'] ?? 'normal'; } /** * Check if scripts should be enqueued */ public function shouldEnqueueScripts(): bool { return $this->isSetUp() && !is_admin(); } /** * Test connection by attempting a verification with an invalid token */ public function performConnectionTest(): bool { if (empty($this->site_key) || empty($this->secret_key)) { return false; } // Test with a dummy token to verify API connectivity $response = wp_remote_post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [ 'body' => [ 'secret' => $this->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)) { $this->logError('Connection test failed: ', [ 'message' => $response->get_error_message() ]); 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); } protected function validateCredentials(array $credentials): bool { if (empty($credentials['site_key'])) { $this->logError('Missing Site Key', [ 'method' => 'validateCredentials' ]); return false; } if (empty($credentials['secret_key'])) { $this->logError('Missing Secret Key', [ 'method' => 'validateCredentials' ]); return false; } // Validate format if (!preg_match('/^[0-9a-zA-Z._-]+$/', $credentials['site_key'])) { $this->logError('Invalid Site Key', [ 'method' => 'validateCredentials' ]); return false; } return true; } /** * 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() */ public function renderTurnstile(array|string $options = []): void { $this->ensureInitialized(); 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;' ]; $options = wp_parse_args($options, $defaults); echo '
'; echo '
'; echo '
'; echo '
'; } /** * Verify Turnstile response * Can be called directly: JVB()->connect('cloudflare')->verifyTurnstile($token) */ public function verifyTurnstile(?string $token = null, string $remote_ip = ''): bool { $this->ensureInitialized(); if (!$this->isSetUp()) { return false; } // If no token provided, try to get from POST if (!$token && isset($_POST['cf-turnstile-response'])) { $token = sanitize_text_field($_POST['cf-turnstile-response']); } if (empty($token)) { return false; } $data = [ 'secret' => $this->secret_key, 'response' => $token ]; if (empty($remote_ip) && isset($_SERVER['REMOTE_ADDR'])) { $remote_ip = $_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('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('Turnstile verification failed: ' . $error_codes); } return $result['success'] ?? false; } /** * Get site key for frontend use */ public function getSiteKey(): string { return $this->site_key; } /** * Get service description */ public function getServiceDescription(): string { return "Add Turnstile captcha protection to your forms and login pages to prevent spam and bot submissions."; } /** * These methods are not used by Cloudflare but must be implemented */ protected function getRequestHeaders(): array { // Cloudflare Turnstile uses POST parameters, not headers return []; } }