<?php
|
namespace JVBase\integrations;
|
|
use WP_Error;
|
if (!defined('ABSPATH')) {
|
exit;
|
}
|
|
class Cloudflare extends Integrations
|
{
|
private string $site_key;
|
private string $secret_key;
|
private string $theme = 'light';
|
private string $size = 'normal';
|
|
public function __construct()
|
{
|
$this->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 <a href="https://dash.cloudflare.com/?to=/:account/turnstile" target="_blank">Cloudflare Dashboard</a>',
|
'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 '<div class="' . esc_attr($options['wrapper_class']) . '" style="' . esc_attr($options['wrapper_style']) . '">';
|
echo '<div class="cf-turnstile" ';
|
echo 'data-sitekey="' . esc_attr($this->site_key) . '" ';
|
echo 'data-theme="' . esc_attr($options['theme']) . '" ';
|
echo 'data-size="' . esc_attr($options['size']) . '">';
|
echo '</div>';
|
echo '</div>';
|
}
|
|
/**
|
* 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 [];
|
}
|
}
|