Jake Vanderwerf
2026-08-07 a8ff85042ffbfe94b678fd818c02deb613bfd116
inc/integrations/services/Cloudflare.php
@@ -1,95 +1,72 @@
<?php
namespace JVBase\integrations\services;
use JVBase\integrations\Admin;
use JVBase\integrations\Instance;
use JVBase\integrations\Integrations;
if (!defined('ABSPATH')) {
   exit;
}
class Cloudflare extends Integrations
{
   private string $site_key;
   private string $secret_key;
   use Admin, Instance;
   private string $theme = 'light';
   private string $size = 'normal';
   protected static array $instances = [];
   public static function getInstance(?int $userID = null):self
   {
      $key = is_null($userID) ? 'base' : $userID;
      if (!array_key_exists($key, self::$instances)) {
         self::$instances[$key] = new self($userID);
      }
      return self::$instances[$key];
   }
   protected function __construct()
   {
      $this->service_name = 'cloudflare';
      $this->title = 'Cloudflare Turnstile';
      $this->title = 'Cloudflare';
      $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->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 <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';
      // TODO: Implement initialize() method.
   }
   /**
    * Check if scripts should be enqueued
    */
   public function shouldEnqueueScripts(): bool
   public function getServiceDescription():string
   {
      return $this->isSetUp() && !is_admin();
      return "Add Turnstile captcha protection to your forms and login pages to prevent spam and bot submissions.";
   }
   /**
    * Test connection by attempting a verification with an invalid token
    */
   public function performConnectionTest(): bool
   {
      if (empty($this->site_key) || empty($this->secret_key)) {
      $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' => $this->secret_key,
            'secret' => $credentials['secret_key'],
            'response' => 'test-token-connection-check'
         ],
         'timeout' => 10
@@ -97,46 +74,14 @@
      // 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
    */
@@ -156,10 +101,10 @@
   /**
    * Render Turnstile widget
    * Can be called directly: jvbConnect('cloudflare')->renderTurnstile()
    * Or Cloudflare::getInstance()->renderTurnstile()
    */
   public function renderTurnstile(array|string $options = []): void
   {
      $this->ensureInitialized();
      if (!$this->isSetUp()) {
         return;
      }
@@ -175,29 +120,36 @@
         'wrapper_style' => 'margin: 1em 0;'
      ];
      $cred = $this->loadCredentials();
      $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>';
      echo sprintf(
         '<div class="%s" style="%s">
            <div class="cf-turnstile" data-sitekey="%s" data-theme="%s" data-size="%s"></div>
         </div>',
         esc_attr($options['wrapper_class']),
         esc_attr($options['wrapper_style']),
         $cred['site_key'],
         $options['theme'],
         $options['size']
      );
   }
   /**
    * Verify Turnstile response
    * Can be called directly: JVB()->connect('cloudflare')->verifyTurnstile($token)
    * Verify the Turnstile response
    * Can be called directly via JVB()->connect('cloudflare')->verifyTurnstile($token)
    *   or Cloudflare::getInstance()->verifyTurnstile($token)
    * @param string|null $token
    * @param string $remote_ip
    * @return bool
    */
   public function verifyTurnstile(?string $token = null, string $remote_ip = ''): bool
   public function verifyTurnstile(?string $token = null, string $remote_ip = ''):bool
   {
      $this->ensureInitialized();
      if (!$this->isSetUp()) {
         return false;
         return true;
      }
      // If no token provided, try to get from POST
      // 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']);
      }
@@ -206,61 +158,35 @@
         return false;
      }
      $cred = $this->loadCredentials();
      $data = [
         'secret' => $this->secret_key,
         'response' => $token
         'secret' => $cred['secret_key'],
         'response'  => $token,
      ];
      if (empty($remote_ip) && isset($_SERVER['REMOTE_ADDR'])) {
         $remote_ip = $_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('Turnstile verification failed: ' . $response->get_error_message());
         $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)) {
      if (!$result['success']??false) {
         $error_codes = implode(', ', $result['error-codes'] ?? ['Unknown error']);
         $this->logError('Turnstile verification failed: ' . $error_codes);
         $this->logError('verifyTurnstile', '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 [];
   }
}