<?php
/**
 * Name: Cloudflare Turnstile
 * Description: Protect your forms from spam and bots with Cloudflare Turnstile. Privacy-focused CAPTCHA alternative that works with WordPress login, registration, comments, Contact Form 7, Fluent Forms, and more.
 * Category: Security
 * Icon: -
 * Version: 1.0
 * Database: no
 * UseCases: [
 *   {
 *     "title": "Login Protection",
 *     "description": "Add Turnstile to WordPress login form to prevent brute force attacks."
 *   },
 *   {
 *     "title": "Registration Protection",
 *     "description": "Prevent spam user registrations with Turnstile verification."
 *   },
 *   {
 *     "title": "Comment Spam Prevention",
 *     "description": "Stop spam bots from flooding your comments with Turnstile."
 *   },
 *   {
 *     "title": "Simple Forms Integration",
 *     "description": "Protect Switchboard Simple Forms with Turnstile validation."
 *   }
 * ]
 * Added: 2025-01-07
 * Tags: turnstile, captcha, cloudflare, spam, security, bot protection, login, registration, comments, privacy
 * WhereToFind: {
 *   "location": "Module Settings",
 *   "instructions": "Configure your Cloudflare Turnstile API keys and select which forms to protect."
 * }
 */

if (!defined('ABSPATH')) exit;

class SwitchboardCloudflareTurnstile {

    private $option_key = 'switchboard_cloudflare-turnstile_settings';
    private $default_settings;

    public function __construct() {
        $this->default_settings = [
            'site_key' => '',
            'secret_key' => '',
            'appearance' => 'always', // always, interaction-only, execute
            'protect_login' => false,
            'protect_registration' => false,
            'protect_password_reset' => false,
            'protect_comments' => false,
            'protect_magic_login' => false,
            'protect_otp_login' => false,
            'protect_simple_forms' => true,
            'protect_cf7' => false,
            'protect_fluent_forms' => false,
            'whitelist_logged_in' => true,
            'whitelist_ips' => '',
            'error_message' => 'Turnstile verification failed. Please try again.',
        ];

        // Admin settings
        add_action('switchboard_module_settings_cloudflare-turnstile', [$this, 'render_settings']);

        $settings = $this->get_settings();

        // Only add protection if keys are configured
        if (empty($settings['site_key']) || empty($settings['secret_key'])) {
            return;
        }

        // Login form protection
        if ($settings['protect_login']) {
            add_action('login_form', [$this, 'render_captcha_login']);
            add_action('login_footer', [$this, 'enqueue_scripts_login']);
            add_filter('authenticate', [$this, 'verify_login'], 999, 3);
        }

        // Registration form protection
        if ($settings['protect_registration']) {
            add_action('register_form', [$this, 'render_captcha_registration']);
            add_action('login_footer', [$this, 'enqueue_scripts_login']);
            add_filter('registration_errors', [$this, 'verify_registration'], 10, 3);
        }

        // Password reset protection
        if ($settings['protect_password_reset']) {
            add_action('lostpassword_form', [$this, 'render_captcha_lostpassword']);
            add_action('login_footer', [$this, 'enqueue_scripts_login']);
            add_action('lostpassword_post', [$this, 'verify_password_reset'], 10, 2);
        }

        // Comment form protection
        if ($settings['protect_comments']) {
            add_action('comment_form_after_fields', [$this, 'render_captcha_comments']);
            add_action('comment_form_logged_in_after', [$this, 'render_captcha_comments']);
            add_filter('preprocess_comment', [$this, 'verify_comment']);
        }

        // Simple Forms integration
        if ($settings['protect_simple_forms']) {
            add_filter('switchboard_simple_form_validate', [$this, 'verify_simple_form'], 10, 2);
            add_action('switchboard_simple_form_after_fields', [$this, 'render_captcha_for_form']);
        }

        // Magic Login integration hook
        if ($settings['protect_magic_login']) {
            add_filter('switchboard_magic_login_validate', [$this, 'verify_magic_login'], 10, 2);
            add_action('switchboard_magic_login_form_fields', [$this, 'render_captcha_for_form']);
        }

        // OTP Login integration hook
        if ($settings['protect_otp_login']) {
            add_filter('switchboard_otp_validate', [$this, 'verify_otp_form'], 10, 2);
            add_action('switchboard_otp_form_fields', [$this, 'render_captcha_for_form']);
        }

        // Contact Form 7 integration
        if (class_exists('WPCF7')) {
            // Always add tag generator in admin so users can add the tag before enabling protection
            if (is_admin()) {
                add_action('wpcf7_admin_init', [$this, 'cf7_add_tag_generator'], 60);
            }
            // Only add protection/validation hooks if enabled
            if ($settings['protect_cf7']) {
                $this->init_cf7_integration();
            }
        }

        // Fluent Forms integration
        if ($settings['protect_fluent_forms']) {
            $this->init_fluent_forms_integration();
        }

        // Frontend scripts (non-login pages)
        add_action('wp_enqueue_scripts', [$this, 'enqueue_scripts_frontend']);

        // Add data-cfasync attribute to prevent Cloudflare Rocket Loader issues
        add_filter('script_loader_tag', [$this, 'add_cfasync_attribute'], 10, 2);

        // Provide filter for other modules
        add_filter('switchboard_turnstile_is_valid', [$this, 'check_turnstile_response'], 10, 2);
    }

    /**
     * Get settings
     */
    public function get_settings() {
        $saved = get_option($this->option_key, []);
        return wp_parse_args($saved, $this->default_settings);
    }

    /**
     * Enqueue Turnstile scripts on login page
     */
    public function enqueue_scripts_login() {
        $settings = $this->get_settings();
        ?>
        <script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=auto" async defer data-cfasync="false"></script>
        <script>
        // Force render Turnstile if widget is empty after page load (handles caching issues)
        document.addEventListener('DOMContentLoaded', function() {
            setTimeout(function() {
                document.querySelectorAll('.cf-turnstile').forEach(function(el) {
                    if (el && !el.innerHTML.trim() && typeof turnstile !== 'undefined') {
                        turnstile.render(el, { sitekey: '<?php echo esc_js($settings['site_key']); ?>' });
                    }
                });
            }, 100);
        });
        </script>
        <?php
    }

    /**
     * Enqueue scripts on frontend
     */
    public function enqueue_scripts_frontend() {
        $settings = $this->get_settings();

        // Only enqueue if needed on this page
        $needs_script = $settings['protect_comments']
            || $settings['protect_magic_login']
            || $settings['protect_otp_login']
            || $settings['protect_simple_forms']
            || $settings['protect_cf7']
            || $settings['protect_fluent_forms'];

        if (!$needs_script) {
            return;
        }

        wp_enqueue_script('cloudflare-turnstile', 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=auto', [], null, true);

        // Add inline script for re-rendering on AJAX and submit button protection
        wp_add_inline_script('cloudflare-turnstile', '
            // Re-render Turnstile widgets after AJAX (for dynamic forms)
            function switchboardTurnstileRerender() {
                if (typeof turnstile === "undefined") return;
                document.querySelectorAll(".cf-turnstile").forEach(function(el) {
                    if (!el.innerHTML.trim()) {
                        turnstile.render(el);
                    }
                });
            }
            // Listen for common AJAX events (check for jQuery first)
            if (typeof jQuery !== "undefined") {
                jQuery(document).on("ajaxComplete", function() {
                    setTimeout(switchboardTurnstileRerender, 100);
                });
            }

            // Track if Turnstile has completed verification
            window.switchboardTurnstileReady = false;

            // Handle sb-turnstile-protect class for invisible mode
            function switchboardDisableProtectedButtons() {
                // Only disable if Turnstile has not yet verified
                if (window.switchboardTurnstileReady) return;
                document.querySelectorAll(".sb-turnstile-protect:not([data-sb-turnstile-processed])").forEach(function(btn) {
                    btn.setAttribute("data-sb-turnstile-processed", "1");
                    btn.disabled = true;
                    btn.style.opacity = "0.6";
                    btn.style.cursor = "not-allowed";
                });
            }

            function switchboardEnableProtectedButtons() {
                window.switchboardTurnstileReady = true;
                document.querySelectorAll(".sb-turnstile-protect").forEach(function(btn) {
                    btn.disabled = false;
                    btn.style.opacity = "";
                    btn.style.cursor = "";
                });
            }

            // Disable buttons immediately when script runs
            switchboardDisableProtectedButtons();

            // Also run on DOMContentLoaded for dynamically added buttons
            document.addEventListener("DOMContentLoaded", switchboardDisableProtectedButtons);

            // Watch for dynamically added buttons (AJAX forms)
            if (typeof MutationObserver !== "undefined") {
                new MutationObserver(function(mutations) {
                    mutations.forEach(function(mutation) {
                        if (mutation.addedNodes.length) {
                            switchboardDisableProtectedButtons();
                        }
                    });
                }).observe(document.body || document.documentElement, { childList: true, subtree: true });
            }

            // Global callback for Turnstile success - enables protected buttons
            window.switchboardTurnstileCallback = switchboardEnableProtectedButtons;

            // Handle token expiry - re-disable buttons when Turnstile needs to re-verify
            window.switchboardTurnstileExpired = function() {
                window.switchboardTurnstileReady = false;
                // Reset processed flag so buttons can be disabled again
                document.querySelectorAll(".sb-turnstile-protect").forEach(function(btn) {
                    btn.removeAttribute("data-sb-turnstile-processed");
                });
                switchboardDisableProtectedButtons();
            };
        ');
    }

    /**
     * Add data-cfasync="false" attribute to prevent Cloudflare Rocket Loader issues
     */
    public function add_cfasync_attribute($tag, $handle) {
        if ($handle === 'cloudflare-turnstile') {
            $tag = str_replace("src='", "data-cfasync='false' src='", $tag);
            $tag = str_replace('src="', 'data-cfasync="false" src="', $tag);
        }
        return $tag;
    }

    /**
     * Check if current request should be whitelisted
     */
    public function is_whitelisted() {
        $settings = $this->get_settings();

        // Skip for logged-in users if whitelist enabled
        if ($settings['whitelist_logged_in'] && is_user_logged_in()) {
            return true;
        }

        // Check IP whitelist
        if (!empty($settings['whitelist_ips'])) {
            $whitelist_ips = preg_split('/[\s,]+/', $settings['whitelist_ips'], -1, PREG_SPLIT_NO_EMPTY);
            $current_ip = $this->get_client_ip();
            foreach ($whitelist_ips as $whitelist_ip) {
                $whitelist_ip = trim($whitelist_ip);
                if (!filter_var($whitelist_ip, FILTER_VALIDATE_IP)) {
                    continue;
                }
                if ($current_ip && $current_ip === $whitelist_ip) {
                    return true;
                }
            }
        }

        // Allow external filtering
        return apply_filters('switchboard_turnstile_whitelisted', false);
    }

    /**
     * Render Turnstile widget
     */
    public function render_captcha($form_context = 'default', $button_selector = '') {
        // Check if another CAPTCHA module already rendered
        global $switchboard_captcha_rendered;
        if (!empty($switchboard_captcha_rendered)) {
            return;
        }
        $switchboard_captcha_rendered = 'turnstile';

        if ($this->is_whitelisted()) {
            return;
        }

        $settings = $this->get_settings();

        $data_attrs = [
            'data-sitekey' => esc_attr($settings['site_key']),
            'data-appearance' => esc_attr($settings['appearance']),
            'data-retry' => 'auto',
            'data-retry-interval' => '1000',
            'data-refresh-expired' => 'auto',
            'data-action' => esc_attr($form_context),
            'data-callback' => 'switchboardTurnstileCallback',
            'data-expired-callback' => 'switchboardTurnstileExpired',
        ];

        $unique_id = wp_unique_id('cf-turnstile-');

        $attrs_string = '';
        foreach ($data_attrs as $key => $value) {
            $attrs_string .= sprintf(' %s="%s"', $key, $value);
        }
        ?>
        <div id="<?php echo esc_attr($unique_id); ?>" class="cf-turnstile"<?php echo $attrs_string; ?> style="margin: 10px 0;"></div>
        <?php
    }

    /**
     * Render captcha for WordPress login form
     */
    public function render_captcha_login() {
        $this->render_captcha('login', '#wp-submit');
    }

    /**
     * Render captcha for WordPress registration form
     */
    public function render_captcha_registration() {
        $this->render_captcha('registration', '#wp-submit');
    }

    /**
     * Render captcha for WordPress lost password form
     */
    public function render_captcha_lostpassword() {
        $this->render_captcha('lostpassword', '#wp-submit');
    }

    /**
     * Render captcha for comment forms
     */
    public function render_captcha_comments() {
        $this->render_captcha('comment', '#submit');
    }

    /**
     * Render captcha for form (with form context)
     * This bypasses duplicate prevention since Magic Login etc. are separate forms
     */
    public function render_captcha_for_form($form_id = null) {
        if ($this->is_whitelisted()) {
            return;
        }

        $settings = $this->get_settings();

        $data_attrs = [
            'data-sitekey' => esc_attr($settings['site_key']),
            'data-appearance' => esc_attr($settings['appearance']),
            'data-retry' => 'auto',
            'data-retry-interval' => '1000',
            'data-refresh-expired' => 'auto',
            'data-action' => esc_attr($form_id ?: 'form'),
            'data-callback' => 'switchboardTurnstileCallback',
            'data-expired-callback' => 'switchboardTurnstileExpired',
        ];

        $unique_id = wp_unique_id('cf-turnstile-');

        $attrs_string = '';
        foreach ($data_attrs as $key => $value) {
            $attrs_string .= sprintf(' %s="%s"', $key, $value);
        }
        ?>
        <div id="<?php echo esc_attr($unique_id); ?>" class="cf-turnstile"<?php echo $attrs_string; ?> style="margin: 10px 0;"></div>
        <?php
    }

    /**
     * Verify Turnstile response
     */
    public function verify_response($response = null) {
        $settings = $this->get_settings();

        // Skip for whitelisted requests
        if ($this->is_whitelisted()) {
            return true;
        }

        if ($response === null) {
            $response = isset($_POST['cf-turnstile-response']) ? sanitize_text_field($_POST['cf-turnstile-response']) : '';
        }

        if (empty($response)) {
            return false;
        }

        $verify_url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
        $verify_response = wp_remote_post($verify_url, [
            'body' => [
                'secret' => $settings['secret_key'],
                'response' => $response,
                'remoteip' => $this->get_client_ip(),
            ],
            'timeout' => 30,
        ]);

        if (is_wp_error($verify_response)) {
            return false;
        }

        $result = json_decode(wp_remote_retrieve_body($verify_response), true);

        if (!isset($result['success']) || !$result['success']) {
            return false;
        }

        return true;
    }

    /**
     * Get client IP with improved detection
     */
    private function get_client_ip() {
        // Helper: validate public IP (no private/reserved ranges)
        $is_public_ip = function($ip) {
            return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
        };

        // 1. Cloudflare header - trusted if site is behind Cloudflare
        if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
            $ip = sanitize_text_field(trim(wp_unslash($_SERVER['HTTP_CF_CONNECTING_IP'])));
            if (filter_var($ip, FILTER_VALIDATE_IP)) {
                return $ip;
            }
        }

        // 2. Common proxy headers
        $proxy_headers = [
            'HTTP_X_FORWARDED_FOR',
            'HTTP_X_REAL_IP',
            'HTTP_X_CLIENT_IP',
            'HTTP_CLIENT_IP',
            'HTTP_X_CLUSTER_CLIENT_IP',
        ];

        foreach ($proxy_headers as $header) {
            if (empty($_SERVER[$header])) {
                continue;
            }

            // Can be comma-separated list (especially X-Forwarded-For)
            $ips = explode(',', wp_unslash($_SERVER[$header]));

            // First pass: look for public IPs
            foreach ($ips as $ip) {
                $ip = sanitize_text_field(trim($ip));
                if ($is_public_ip($ip)) {
                    return $ip;
                }
            }

            // Second pass: fall back to first valid IP
            foreach ($ips as $ip) {
                $ip = sanitize_text_field(trim($ip));
                if (filter_var($ip, FILTER_VALIDATE_IP)) {
                    return $ip;
                }
            }
        }

        // 3. Fallback to REMOTE_ADDR
        if (!empty($_SERVER['REMOTE_ADDR'])) {
            $ip = sanitize_text_field(trim(wp_unslash($_SERVER['REMOTE_ADDR'])));
            if (filter_var($ip, FILTER_VALIDATE_IP)) {
                return $ip;
            }
        }

        return false;
    }

    /**
     * Check if another CAPTCHA module is handling this form
     * Skip validation if another CAPTCHA's response is present
     */
    private function another_captcha_is_active() {
        // Check if reCAPTCHA response is present (Google reCAPTCHA is active)
        if (!empty($_POST['g-recaptcha-response'])) {
            return true;
        }
        // Check if Simple CAPTCHA response is present
        if (!empty($_POST['switchboard_captcha_token'])) {
            return true;
        }
        return false;
    }

    /**
     * Verify login
     */
    public function verify_login($user, $username, $password) {
        // Don't check if no credentials provided
        if (empty($username) || empty($password)) {
            return $user;
        }

        // Don't check if there's already an authentication error
        if (is_wp_error($user)) {
            return $user;
        }

        // Skip if another CAPTCHA module is handling this form
        if ($this->another_captcha_is_active()) {
            return $user;
        }

        if (!$this->verify_response()) {
            return new WP_Error('turnstile_failed', $this->get_settings()['error_message']);
        }

        return $user;
    }

    /**
     * Verify registration
     */
    public function verify_registration($errors, $sanitized_user_login, $user_email) {
        // Skip if another CAPTCHA module is handling this form
        if ($this->another_captcha_is_active()) {
            return $errors;
        }
        if (!$this->verify_response()) {
            $errors->add('turnstile_failed', $this->get_settings()['error_message']);
        }
        return $errors;
    }

    /**
     * Verify password reset
     */
    public function verify_password_reset($errors, $user_data) {
        // Skip if another CAPTCHA module is handling this form
        if ($this->another_captcha_is_active()) {
            return;
        }
        if (!$this->verify_response()) {
            $errors->add('turnstile_failed', $this->get_settings()['error_message']);
        }
    }

    /**
     * Verify comment
     */
    public function verify_comment($commentdata) {
        // Skip if another CAPTCHA module is handling this form
        if ($this->another_captcha_is_active()) {
            return $commentdata;
        }
        if (!$this->verify_response()) {
            wp_die(
                '<p>' . esc_html($this->get_settings()['error_message']) . '</p>',
                esc_html__('Verification Failed', 'wpswitchboard'),
                [
                    'back_link' => true,
                    'response' => 403
                ]
            );
        }
        return $commentdata;
    }

    /**
     * Verify Simple Form submission
     * Only validates if this module rendered the CAPTCHA
     */
    public function verify_simple_form($valid, $form_id) {
        global $switchboard_captcha_rendered;
        // Only validate if Turnstile was the one that rendered, or if no flag (backwards compat)
        if (!empty($switchboard_captcha_rendered) && $switchboard_captcha_rendered !== 'turnstile') {
            return $valid;
        }
        if (!$this->verify_response()) {
            return new WP_Error('turnstile_failed', $this->get_settings()['error_message']);
        }
        return $valid;
    }

    /**
     * Verify Magic Login submission
     * Only validates if this module rendered the CAPTCHA
     */
    public function verify_magic_login($valid, $email) {
        global $switchboard_captcha_rendered;
        // Only validate if Turnstile was the one that rendered, or if no flag (backwards compat)
        if (!empty($switchboard_captcha_rendered) && $switchboard_captcha_rendered !== 'turnstile') {
            return $valid;
        }
        // Skip if another CAPTCHA module is handling this form
        if ($this->another_captcha_is_active()) {
            return $valid;
        }
        if (!$this->verify_response()) {
            return new WP_Error('turnstile_failed', $this->get_settings()['error_message']);
        }
        return $valid;
    }

    /**
     * Verify OTP form submission
     * Only validates if this module rendered the CAPTCHA
     */
    public function verify_otp_form($valid, $user_id) {
        global $switchboard_captcha_rendered;
        // Only validate if Turnstile was the one that rendered, or if no flag (backwards compat)
        if (!empty($switchboard_captcha_rendered) && $switchboard_captcha_rendered !== 'turnstile') {
            return $valid;
        }
        // Skip if another CAPTCHA module is handling this form
        if ($this->another_captcha_is_active()) {
            return $valid;
        }
        if (!$this->verify_response()) {
            return new WP_Error('turnstile_failed', $this->get_settings()['error_message']);
        }
        return $valid;
    }

    /**
     * Initialize Contact Form 7 integration
     */
    private function init_cf7_integration() {
        // CF7's wpcf7_init fires on 'init' priority 10
        // Switchboard also loads modules on 'init' priority 10
        // If wpcf7_add_form_tag exists, wpcf7_init has already fired - register immediately
        // Otherwise, hook into wpcf7_init for later registration
        if (function_exists('wpcf7_add_form_tag')) {
            $this->cf7_add_form_tag();
        }
        // Also add the hook in case wpcf7_init fires later (e.g., different load order)
        add_action('wpcf7_init', [$this, 'cf7_add_form_tag'], 1);

        // Validate submission - use wpcf7_validate filter with our tag
        add_filter('wpcf7_validate_switchboard_turnstile', [$this, 'cf7_validate_tag'], 10, 2);
        add_filter('wpcf7_validate_switchboard_turnstile*', [$this, 'cf7_validate_tag'], 10, 2);

        // Enqueue scripts on pages with CF7 forms
        add_action('wpcf7_enqueue_scripts', [$this, 'enqueue_scripts_frontend']);

        // Tag generator button is added in constructor (unconditionally in admin)
    }

    /**
     * Register CF7 form tag
     * Users add [switchboard_turnstile] to their form
     */
    public function cf7_add_form_tag() {
        static $registered = false;

        if ($registered || !function_exists('wpcf7_add_form_tag')) {
            return;
        }
        $registered = true;

        wpcf7_add_form_tag(
            ['switchboard_turnstile', 'switchboard_turnstile*'],
            [$this, 'cf7_render_tag'],
            ['name-attr' => true]
        );
    }

    /**
     * Render CF7 form tag
     */
    public function cf7_render_tag($tag) {
        // Skip for whitelisted requests
        if ($this->is_whitelisted()) {
            return '';
        }

        $settings = $this->get_settings();
        $validation_error = wpcf7_get_validation_error($tag->name);

        $data_attrs = [
            'data-sitekey' => esc_attr($settings['site_key']),
            'data-appearance' => esc_attr($settings['appearance']),
            'data-retry' => 'auto',
            'data-retry-interval' => '1000',
            'data-refresh-expired' => 'auto',
            'data-action' => 'contact_form_7',
            'data-callback' => 'switchboardTurnstileCallback',
            'data-expired-callback' => 'switchboardTurnstileExpired',
        ];

        $attrs_string = '';
        foreach ($data_attrs as $key => $value) {
            $attrs_string .= sprintf(' %s="%s"', $key, $value);
        }

        $html = '<span class="wpcf7-form-control-wrap" data-name="' . esc_attr($tag->name) . '">';
        $html .= '<div class="cf-turnstile wpcf7-form-control"' . $attrs_string . ' style="margin: 10px 0;"></div>';
        $html .= wp_kses_post($validation_error);
        $html .= '</span>';

        return $html;
    }

    /**
     * Validate CF7 form tag
     */
    public function cf7_validate_tag($result, $tag) {
        // Skip for whitelisted requests
        if ($this->is_whitelisted()) {
            return $result;
        }

        if (!$this->verify_response()) {
            $result->invalidate($tag, $this->get_settings()['error_message']);
        }

        return $result;
    }

    /**
     * Add tag generator button in CF7 form editor
     */
    public function cf7_add_tag_generator() {
        if (!function_exists('wpcf7_add_tag_generator')) {
            return;
        }

        wpcf7_add_tag_generator(
            'switchboard_turnstile',
            __('Cloudflare Turnstile', 'wpswitchboard'),
            'wpcf7-tg-pane-switchboard-turnstile',
            [$this, 'cf7_tag_generator_panel']
        );
    }

    /**
     * Render tag generator panel in CF7 form editor
     */
    public function cf7_tag_generator_panel($contact_form, $args = '') {
        $args = wp_parse_args($args, []);
        ?>
        <div class="control-box">
            <fieldset>
                <legend><?php esc_html_e('Add a Cloudflare Turnstile widget to your form.', 'wpswitchboard'); ?></legend>

                <table class="form-table">
                    <tbody>
                        <tr>
                            <th scope="row">
                                <label for="<?php echo esc_attr($args['content'] . '-name'); ?>"><?php esc_html_e('Name', 'wpswitchboard'); ?></label>
                            </th>
                            <td>
                                <input type="text" name="name" class="tg-name oneline" id="<?php echo esc_attr($args['content'] . '-name'); ?>" value="turnstile" />
                            </td>
                        </tr>
                    </tbody>
                </table>
            </fieldset>
        </div>

        <div class="insert-box">
            <input type="text" name="switchboard_turnstile" class="tag code" readonly="readonly" onfocus="this.select()" />

            <div class="submitbox">
                <input type="button" class="button button-primary insert-tag" value="<?php esc_attr_e('Insert Tag', 'wpswitchboard'); ?>" />
            </div>
        </div>
        <?php
    }

    /**
     * Initialize Fluent Forms integration
     * Registers a custom field type using BaseFieldManager
     */
    private function init_fluent_forms_integration() {
        // Only initialize if Fluent Forms is active
        if (!class_exists('FluentForm\App\Services\FormBuilder\BaseFieldManager')) {
            return;
        }
        // Initialize the Fluent Forms field manager
        new SwitchboardFluentFormsTurnstile($this);
    }

    /**
     * Filter for other modules to check Turnstile
     */
    public function check_turnstile_response($valid, $response = null) {
        return $this->verify_response($response);
    }

    /**
     * Render settings page
     */
    public function render_settings() {
        $settings = $this->get_settings();
        ?>
        <div class="sb-module-settings-form">
            <input type="hidden" name="module" value="cloudflare-turnstile">

            <!-- Info Box -->
            <div class="sb-sp-alert sb-sp-alert-info">
                <div class="sb-sp-alert-title">Cloudflare Turnstile</div>
                <div class="sb-sp-alert-text">A privacy-preserving CAPTCHA alternative that validates users without puzzles. Get your API keys from <a href="https://dash.cloudflare.com/?to=/:account/turnstile" target="_blank">Cloudflare Dashboard</a>.</div>
            </div>

            <?php
            // Check for other active CAPTCHA modules
            $other_captchas = [];
            if (class_exists('SwitchboardGoogleReCaptcha')) {
                $recaptcha_settings = get_option('switchboard_google-recaptcha_settings', []);
                if (!empty($recaptcha_settings['site_key']) && !empty($recaptcha_settings['secret_key'])) {
                    $other_captchas[] = 'Google reCAPTCHA';
                }
            }
            if (class_exists('SwitchboardSimpleCaptcha')) {
                $other_captchas[] = 'Simple CAPTCHA';
            }
            if (!empty($other_captchas)): ?>
            <div class="sb-sp-alert sb-sp-alert-warning" style="margin-bottom: 16px;">
                <div class="sb-sp-alert-title"><?php esc_html_e('Other CAPTCHA Module Active', 'wpswitchboard'); ?></div>
                <div class="sb-sp-alert-text">
                    <?php echo esc_html(implode(' and ', $other_captchas)); ?> <?php echo count($other_captchas) > 1 ? esc_html__('are', 'wpswitchboard') : esc_html__('is', 'wpswitchboard'); ?> <?php esc_html_e('also enabled. Only one CAPTCHA will be shown per form - enabling multiple CAPTCHA modules is unnecessary and may cause conflicts. We recommend using only one CAPTCHA solution.', 'wpswitchboard'); ?>
                </div>
            </div>
            <?php endif; ?>

            <!-- API Configuration -->
            <h3 class="sb-sp-heading">API Configuration</h3>

            <div class="sb-sp-group sb-sp-field-horizontal" style="margin-bottom: 24px;">
                <div class="sb-sp-field-horizontal-left">
                    <label for="site_key" class="sb-sp-label"><?php esc_html_e('Site Key', 'wpswitchboard'); ?></label>
                    <p class="sb-sp-description"><?php esc_html_e('Public key visible in your site\'s HTML', 'wpswitchboard'); ?></p>
                </div>
                <input type="text" name="site_key" id="site_key" value="<?php echo esc_attr($settings['site_key']); ?>" class="sb-sp-input" placeholder="0x...">
            </div>

            <div class="sb-sp-group sb-sp-field-horizontal" style="margin-bottom: 24px;">
                <div class="sb-sp-field-horizontal-left">
                    <label for="secret_key" class="sb-sp-label"><?php esc_html_e('Secret Key', 'wpswitchboard'); ?></label>
                    <p class="sb-sp-description"><?php esc_html_e('Private key for server-side verification', 'wpswitchboard'); ?></p>
                </div>
                <input type="password" name="secret_key" id="secret_key" value="<?php echo esc_attr($settings['secret_key']); ?>" class="sb-sp-input" placeholder="0x...">
            </div>

            <div class="sb-sp-divider"></div>

            <!-- Appearance -->
            <h3 class="sb-sp-heading">Appearance Mode</h3>
            <p class="sb-sp-description" style="margin-bottom: 16px;"><?php esc_html_e('When the Turnstile widget should be visible to users', 'wpswitchboard'); ?></p>

            <div class="sb-sp-radio-cards" style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 24px;">
                <label class="sb-sp-radio-card" style="display: block; padding: 16px; background: #fff; border: 2px solid var(--gray-200, #e5e7eb); border-radius: 12px; cursor: pointer; transition: all 0.2s ease; position: relative;">
                    <input type="radio" name="appearance" value="always" <?php checked($settings['appearance'], 'always'); ?> style="position: absolute; top: 16px; right: 16px; width: 18px; height: 18px; accent-color: var(--primary-500, #3b82f6);">
                    <div style="padding-right: 30px;">
                        <div style="font-size: 24px; margin-bottom: 8px;"></div>
                        <div style="font-weight: 600; color: var(--gray-900, #111827); margin-bottom: 4px;">Always Visible <span style="color: #16a34a; font-size: 11px; font-weight: 500;">(Recommended)</span></div>
                        <div style="font-size: 13px; color: var(--gray-500, #6b7280);">Widget always shown - most reliable</div>
                    </div>
                </label>

                <label class="sb-sp-radio-card" style="display: block; padding: 16px; background: #fff; border: 2px solid var(--gray-200, #e5e7eb); border-radius: 12px; cursor: pointer; transition: all 0.2s ease; position: relative;">
                    <input type="radio" name="appearance" value="interaction-only" <?php checked($settings['appearance'], 'interaction-only'); ?> style="position: absolute; top: 16px; right: 16px; width: 18px; height: 18px; accent-color: var(--primary-500, #3b82f6);">
                    <div style="padding-right: 30px;">
                        <div style="font-size: 24px; margin-bottom: 8px;"></div>
                        <div style="font-weight: 600; color: var(--gray-900, #111827); margin-bottom: 4px;">On Interaction</div>
                        <div style="font-size: 13px; color: var(--gray-500, #6b7280);">Shows only when user interacts with form</div>
                    </div>
                </label>

                <label class="sb-sp-radio-card" style="display: block; padding: 16px; background: #fff; border: 2px solid var(--gray-200, #e5e7eb); border-radius: 12px; cursor: pointer; transition: all 0.2s ease; position: relative; opacity: 0.7;">
                    <input type="radio" name="appearance" value="execute" <?php checked($settings['appearance'], 'execute'); ?> style="position: absolute; top: 16px; right: 16px; width: 18px; height: 18px; accent-color: var(--primary-500, #3b82f6);">
                    <div style="padding-right: 30px;">
                        <div style="font-size: 24px; margin-bottom: 8px;"></div>
                        <div style="font-weight: 600; color: var(--gray-900, #111827); margin-bottom: 4px;">Invisible <span style="color: #dc2626; font-size: 11px; font-weight: 500;">(Not Recommended)</span></div>
                        <div style="font-size: 13px; color: var(--gray-500, #6b7280);">May cause issues in some environments</div>
                    </div>
                </label>
            </div>

            <div id="invisible-mode-notice" class="sb-sp-notice sb-sp-notice-warning" style="margin-bottom: 24px; padding: 12px 16px; background: #fee2e2; border-left: 4px solid #dc2626; border-radius: 4px; display: <?php echo $settings['appearance'] === 'execute' ? 'block' : 'none'; ?>;">
                <strong style="color: #991b1b;"><?php esc_html_e('Use At Your Own Risk', 'wpswitchboard'); ?></strong>
                <p style="margin: 8px 0 0; font-size: 13px; color: #991b1b;">
                    <?php esc_html_e('Invisible mode may cause verification failures depending on your hosting environment, caching setup, theme, or other plugins. Every environment is different and we cannot guarantee this will work reliably on your site.', 'wpswitchboard'); ?>
                </p>
                <p style="margin: 8px 0 0; font-size: 13px;">
                    <?php esc_html_e('If you must use invisible mode, add the CSS class', 'wpswitchboard'); ?>
                    <code style="background: #fff; padding: 2px 6px; border-radius: 3px;">sb-turnstile-protect</code>
                    <?php esc_html_e('to your submit buttons to help prevent premature submissions.', 'wpswitchboard'); ?>
                </p>
            </div>

            <script>
            document.querySelectorAll('input[name="appearance"]').forEach(function(radio) {
                radio.addEventListener('change', function() {
                    document.getElementById('invisible-mode-notice').style.display = this.value === 'execute' ? 'block' : 'none';
                });
            });
            </script>

            <div class="sb-sp-divider"></div>

            <!-- Form Protection -->
            <h3 class="sb-sp-heading">Form Protection</h3>
            <p class="sb-sp-description" style="margin-bottom: 16px;">Select which forms to protect with Turnstile</p>

            <div class="sb-sp-checkbox-grid" style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;">
                <div class="sb-module-option-item sb-module-toggle-item">
                    <div class="sb-module-option-content">
                        <div class="sb-module-toggle-header">
                            <div class="sb-module-option-info">
                                <strong class="sb-module-option-title">WordPress Login</strong>
                                <p class="sb-module-option-text">Add Turnstile to wp-login.php</p>
                            </div>
                            <label class="sb-module-toggle">
                                <input type="checkbox" name="protect_login" value="1" <?php checked($settings['protect_login']); ?>>
                                <span class="sb-module-toggle-slider"></span>
                            </label>
                        </div>
                    </div>
                </div>

                <div class="sb-module-option-item sb-module-toggle-item">
                    <div class="sb-module-option-content">
                        <div class="sb-module-toggle-header">
                            <div class="sb-module-option-info">
                                <strong class="sb-module-option-title">Registration Form</strong>
                                <p class="sb-module-option-text">Prevent spam registrations</p>
                            </div>
                            <label class="sb-module-toggle">
                                <input type="checkbox" name="protect_registration" value="1" <?php checked($settings['protect_registration']); ?>>
                                <span class="sb-module-toggle-slider"></span>
                            </label>
                        </div>
                    </div>
                </div>

                <div class="sb-module-option-item sb-module-toggle-item">
                    <div class="sb-module-option-content">
                        <div class="sb-module-toggle-header">
                            <div class="sb-module-option-info">
                                <strong class="sb-module-option-title">Password Reset</strong>
                                <p class="sb-module-option-text">Protect lost password requests</p>
                            </div>
                            <label class="sb-module-toggle">
                                <input type="checkbox" name="protect_password_reset" value="1" <?php checked($settings['protect_password_reset']); ?>>
                                <span class="sb-module-toggle-slider"></span>
                            </label>
                        </div>
                    </div>
                </div>

                <div class="sb-module-option-item sb-module-toggle-item">
                    <div class="sb-module-option-content">
                        <div class="sb-module-toggle-header">
                            <div class="sb-module-option-info">
                                <strong class="sb-module-option-title">Comment Form</strong>
                                <p class="sb-module-option-text">Stop spam comments on posts</p>
                            </div>
                            <label class="sb-module-toggle">
                                <input type="checkbox" name="protect_comments" value="1" <?php checked($settings['protect_comments']); ?>>
                                <span class="sb-module-toggle-slider"></span>
                            </label>
                        </div>
                    </div>
                </div>

                <div class="sb-module-option-item sb-module-toggle-item">
                    <div class="sb-module-option-content">
                        <div class="sb-module-toggle-header">
                            <div class="sb-module-option-info">
                                <strong class="sb-module-option-title">Magic Login</strong>
                                <p class="sb-module-option-text">Protect passwordless login</p>
                            </div>
                            <label class="sb-module-toggle">
                                <input type="checkbox" name="protect_magic_login" value="1" <?php checked($settings['protect_magic_login']); ?>>
                                <span class="sb-module-toggle-slider"></span>
                            </label>
                        </div>
                    </div>
                </div>

                <div class="sb-module-option-item sb-module-toggle-item">
                    <div class="sb-module-option-content">
                        <div class="sb-module-toggle-header">
                            <div class="sb-module-option-info">
                                <strong class="sb-module-option-title">OTP Login</strong>
                                <p class="sb-module-option-text">Protect OTP verification forms</p>
                            </div>
                            <label class="sb-module-toggle">
                                <input type="checkbox" name="protect_otp_login" value="1" <?php checked($settings['protect_otp_login']); ?>>
                                <span class="sb-module-toggle-slider"></span>
                            </label>
                        </div>
                    </div>
                </div>

                <div class="sb-module-option-item sb-module-toggle-item">
                    <div class="sb-module-option-content">
                        <div class="sb-module-toggle-header">
                            <div class="sb-module-option-info">
                                <strong class="sb-module-option-title">Simple Forms</strong>
                                <p class="sb-module-option-text">Protect Switchboard forms</p>
                            </div>
                            <label class="sb-module-toggle">
                                <input type="checkbox" name="protect_simple_forms" value="1" <?php checked($settings['protect_simple_forms']); ?>>
                                <span class="sb-module-toggle-slider"></span>
                            </label>
                        </div>
                    </div>
                </div>
            </div>

            <!-- Third-Party Integrations -->
            <h4 class="sb-sp-heading-sm" style="margin-top: 24px;">Third-Party Integrations</h4>
            <div class="sb-sp-checkbox-grid" style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-top: 12px;">
                <?php $cf7_installed = class_exists('WPCF7'); ?>
                <div class="sb-module-option-item sb-module-toggle-item" <?php echo !$cf7_installed ? 'style="opacity: 0.5;"' : ''; ?>>
                    <div class="sb-module-option-content">
                        <div class="sb-module-toggle-header">
                            <div class="sb-module-option-info">
                                <strong class="sb-module-option-title">Contact Form 7</strong>
                                <p class="sb-module-option-text"><?php echo $cf7_installed ? 'Use shortcode tag' : 'Not installed'; ?></p>
                            </div>
                            <label class="sb-module-toggle">
                                <input type="checkbox" name="protect_cf7" value="1" <?php checked($settings['protect_cf7']); ?> <?php echo !$cf7_installed ? 'disabled' : ''; ?>>
                                <span class="sb-module-toggle-slider"></span>
                            </label>
                        </div>
                    </div>
                </div>

                <?php $ff_installed = defined('FLUENTFORM'); ?>
                <div class="sb-module-option-item sb-module-toggle-item" <?php echo !$ff_installed ? 'style="opacity: 0.5;"' : ''; ?>>
                    <div class="sb-module-option-content">
                        <div class="sb-module-toggle-header">
                            <div class="sb-module-option-info">
                                <strong class="sb-module-option-title">Fluent Forms</strong>
                                <p class="sb-module-option-text"><?php echo $ff_installed ? 'Adds "Cloudflare Turnstile" in Advanced fields' : 'Not installed'; ?></p>
                            </div>
                            <label class="sb-module-toggle">
                                <input type="checkbox" name="protect_fluent_forms" value="1" <?php checked($settings['protect_fluent_forms']); ?> <?php echo !$ff_installed ? 'disabled' : ''; ?>>
                                <span class="sb-module-toggle-slider"></span>
                            </label>
                        </div>
                    </div>
                </div>
            </div>

            <div class="sb-sp-divider"></div>

            <!-- Options -->
            <h3 class="sb-sp-heading">Options</h3>

            <div class="sb-module-option-item sb-module-toggle-item">
                <div class="sb-module-option-content">
                    <div class="sb-module-toggle-header">
                        <strong class="sb-module-option-title"><?php esc_html_e('Skip for Logged-in Users', 'wpswitchboard'); ?></strong>
                        <label class="sb-module-toggle">
                            <input type="checkbox" name="whitelist_logged_in" value="1" <?php checked($settings['whitelist_logged_in']); ?>>
                            <span class="sb-module-toggle-slider"></span>
                        </label>
                    </div>
                    <p class="sb-module-option-text"><?php esc_html_e('Logged-in users will not see Turnstile challenges', 'wpswitchboard'); ?></p>
                </div>
            </div>

            <div class="sb-sp-group sb-sp-field-horizontal" style="margin-top: 16px; margin-bottom: 24px;">
                <div class="sb-sp-field-horizontal-left">
                    <label for="error_message" class="sb-sp-label"><?php esc_html_e('Error Message', 'wpswitchboard'); ?></label>
                    <p class="sb-sp-description"><?php esc_html_e('Message shown when Turnstile verification fails', 'wpswitchboard'); ?></p>
                </div>
                <input type="text" name="error_message" id="error_message" value="<?php echo esc_attr($settings['error_message']); ?>" class="sb-sp-input">
            </div>

            <div class="sb-sp-group sb-sp-field-horizontal" style="margin-bottom: 24px;">
                <div class="sb-sp-field-horizontal-left">
                    <label for="whitelist_ips" class="sb-sp-label"><?php esc_html_e('Whitelisted IPs', 'wpswitchboard'); ?></label>
                    <p class="sb-sp-description"><?php esc_html_e('IP addresses that skip Turnstile (one per line or comma-separated)', 'wpswitchboard'); ?></p>
                </div>
                <textarea name="whitelist_ips" id="whitelist_ips" class="sb-sp-textarea" rows="3" placeholder="192.168.1.1&#10;10.0.0.1"><?php echo esc_textarea($settings['whitelist_ips']); ?></textarea>
            </div>

            <div class="sb-sp-divider"></div>

            <?php if (!empty($settings['site_key'])): ?>
            <!-- Test Widget -->
            <h3 class="sb-sp-heading">Test Widget</h3>
            <p class="sb-sp-description" style="margin-bottom: 16px;">Preview how Turnstile will appear on your forms</p>
            <div class="sb-sp-card sb-sp-p-4" style="background: #f8f9fa;">
                <div class="cf-turnstile"
                    data-sitekey="<?php echo esc_attr($settings['site_key']); ?>"
                    data-appearance="<?php echo esc_attr($settings['appearance']); ?>"
                ></div>
                <p class="sb-sp-description" style="margin-top: 12px; font-size: 12px;">If you see the widget above, your site key is working correctly.</p>
            </div>
            <script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=auto" async defer></script>

            <div class="sb-sp-divider"></div>
            <?php endif; ?>

            <!-- Tip Box -->
            <div class="sb-sp-card sb-sp-p-4">
                <h4 class="sb-sp-heading-sm sb-sp-mb-3">Tips</h4>
                <ul class="sb-sp-list">
                    <li class="sb-sp-list-item"><strong>Privacy-First:</strong> Turnstile doesn't track users or require cookies</li>
                    <li class="sb-sp-list-item"><strong>No Puzzles:</strong> Most users pass invisibly without any interaction</li>
                    <li class="sb-sp-list-item"><strong>Free:</strong> Turnstile is free for unlimited use on any website</li>
                    <li class="sb-sp-list-item"><strong>Contact Form 7:</strong> Add <code>[switchboard_turnstile]</code> tag to your forms</li>
                </ul>
            </div>
        </div>
        <?php
    }
}

/**
 * Fluent Forms Custom Field - Cloudflare Turnstile
 * Extends BaseFieldManager to register a proper custom field type
 */
if (class_exists('FluentForm\App\Services\FormBuilder\BaseFieldManager')) {
    class SwitchboardFluentFormsTurnstile extends \FluentForm\App\Services\FormBuilder\BaseFieldManager
    {
        private $turnstile_module;

        public function __construct($turnstile_module)
        {
            $this->turnstile_module = $turnstile_module;

            parent::__construct(
                'switchboard_turnstile',                      // key
                __('Cloudflare Turnstile', 'wpswitchboard'), // title
                ['turnstile', 'captcha', 'security', 'spam', 'cloudflare'], // tags
                'advanced'                                    // section
            );

            // Add validation filter
            add_filter('fluentform/validate_input_item_switchboard_turnstile', [$this, 'validate'], 10, 5);

            // Hide field from entries (captcha responses shouldn't be stored)
            $this->hideFieldFromEntries();
        }

        /**
         * Get component definition for form builder
         */
        public function getComponent()
        {
            return [
                'index'          => 22,
                'element'        => $this->key,
                'attributes'     => [
                    'name'  => $this->key,
                    'class' => '',
                ],
                'settings'       => [
                    'container_class'    => '',
                    'label'              => '',
                    'label_placement'    => '',
                    'help_message'       => '',
                    'error_message'      => __('Turnstile verification failed. Please try again.', 'wpswitchboard'),
                    'conditional_logics' => [],
                ],
                'editor_options' => [
                    'title'      => __('Cloudflare Turnstile', 'wpswitchboard'),
                    'icon_class' => 'ff-edit-shield',
                    'template'   => 'inputHidden',
                ],
            ];
        }

        /**
         * Define which settings appear in General tab
         */
        public function getGeneralEditorElements()
        {
            return [
                'label',
                'label_placement',
            ];
        }

        /**
         * Define custom editor elements
         */
        public function generalEditorElement()
        {
            return [
                'error_message' => [
                    'template' => 'inputText',
                    'label'    => __('Error Message', 'wpswitchboard'),
                ],
            ];
        }

        /**
         * Render the Turnstile field on frontend
         */
        public function render($data, $form)
        {
            $settings = $this->turnstile_module->get_settings();

            // Skip for whitelisted users
            if (method_exists($this->turnstile_module, 'is_whitelisted') && $this->turnstile_module->is_whitelisted()) {
                return '';
            }

            // Check if API keys are configured
            if (empty($settings['site_key']) || empty($settings['secret_key'])) {
                if (current_user_can('manage_options')) {
                    echo '<div class="ff-el-group"><p style="color: #dc2626;">Cloudflare Turnstile: API keys not configured. <a href="' . admin_url('admin.php?page=switchboard') . '">Configure in Switchboard</a></p></div>';
                }
                return '';
            }

            $data_attrs = [
                'data-sitekey' => esc_attr($settings['site_key']),
                'data-appearance' => esc_attr($settings['appearance']),
                'data-retry' => 'auto',
                'data-retry-interval' => '1000',
                'data-refresh-expired' => 'auto',
                'data-action' => 'fluent_forms',
                'data-callback' => 'switchboardTurnstileCallback',
                'data-expired-callback' => 'switchboardTurnstileExpired',
            ];

            $attrs_string = '';
            foreach ($data_attrs as $key => $value) {
                $attrs_string .= sprintf(' %s="%s"', $key, $value);
            }

            // Echo the Turnstile widget directly
            echo '<div class="ff-el-group" style="margin-bottom: 15px;">';
            echo '<div class="cf-turnstile"' . $attrs_string . '></div>';
            echo '</div>';

            // Return empty string - Turnstile widget is self-contained
            return '';
        }

        /**
         * Validate the Turnstile on form submission
         */
        public function validate($errorMessage, $field, $formData, $fields, $form)
        {
            $settings = $this->turnstile_module->get_settings();

            // Skip for whitelisted users
            if (method_exists($this->turnstile_module, 'is_whitelisted') && $this->turnstile_module->is_whitelisted()) {
                return $errorMessage;
            }

            // Skip if keys not configured
            if (empty($settings['site_key']) || empty($settings['secret_key'])) {
                return $errorMessage;
            }

            if (!$this->turnstile_module->verify_response()) {
                $error_msg = isset($field['raw']['settings']['error_message']) ? $field['raw']['settings']['error_message'] : $settings['error_message'];
                return [$error_msg];
            }

            return $errorMessage;
        }

        /**
         * Hide Turnstile field from form entries
         */
        private function hideFieldFromEntries()
        {
            add_filter('fluentform/all_entry_labels', function ($formLabels, $form_id) {
                if (!function_exists('wpFluent')) {
                    return $formLabels;
                }
                $form = wpFluent()->table('fluentform_forms')->find($form_id);
                if (!$form) {
                    return $formLabels;
                }
                $fields = \FluentForm\App\Modules\Form\FormFieldsParser::getInputsByElementTypes($form, [$this->key]);
                if (is_array($fields) && !empty($fields)) {
                    foreach (array_keys($fields) as $key) {
                        unset($formLabels[$key]);
                    }
                }
                return $formLabels;
            }, 10, 2);
        }
    }
}

new SwitchboardCloudflareTurnstile();
