bool, 'message' => string, 'remaining' => int] */ public static function check_rate_limit($checker_id, $ip) { $checker = get_post_meta($checker_id, 'checker', true); // Check if rate limiting is enabled if (!isset($checker['security']['rate_limit']['enabled']) || $checker['security']['rate_limit']['enabled'] !== 'yes') { return ['allowed' => true, 'remaining' => 999]; } // Get settings $max_attempts = isset($checker['security']['rate_limit']['max_attempts']) ? (int)$checker['security']['rate_limit']['max_attempts'] : 5; $time_window = isset($checker['security']['rate_limit']['time_window']) ? (int)$checker['security']['rate_limit']['time_window'] : 15; $block_duration = isset($checker['security']['rate_limit']['block_duration']) ? (int)$checker['security']['rate_limit']['block_duration'] : 60; $error_message = isset($checker['security']['rate_limit']['error_message']) ? $checker['security']['rate_limit']['error_message'] : 'Too many attempts. Please try again later.'; // Create transient keys $transient_key = 'checker_rate_' . $checker_id . '_' . md5($ip); $block_key = 'checker_block_' . $checker_id . '_' . md5($ip); // Check if IP is blocked $blocked_until = get_transient($block_key); if ($blocked_until !== false) { $remaining_time = ceil(($blocked_until - time()) / 60); return [ 'allowed' => false, 'message' => $error_message . ' (' . $remaining_time . ' minutes remaining)', 'remaining' => 0 ]; } // Get current attempts $attempts = get_transient($transient_key); if ($attempts === false) { $attempts = 0; } // Increment attempts $attempts++; // Check if exceeded limit if ($attempts > $max_attempts) { // Block the IP set_transient($block_key, time() + ($block_duration * 60), $block_duration * 60); // Reset attempts counter delete_transient($transient_key); return [ 'allowed' => false, 'message' => $error_message, 'remaining' => 0 ]; } // Update attempts counter set_transient($transient_key, $attempts, $time_window * 60); return [ 'allowed' => true, 'remaining' => $max_attempts - $attempts ]; } /** * Verify reCAPTCHA v3 token * * @param int $checker_id Checker post ID * @param string $token reCAPTCHA token from frontend * @return array ['success' => bool, 'score' => float, 'message' => string] */ public static function verify_recaptcha($checker_id, $token) { $checker = get_post_meta($checker_id, 'checker', true); // Check if reCAPTCHA is enabled if (!isset($checker['security']['recaptcha']['enabled']) || $checker['security']['recaptcha']['enabled'] !== 'yes') { return ['success' => true, 'score' => 1.0]; } // Get settings $secret_key = isset($checker['security']['recaptcha']['secret_key']) ? $checker['security']['recaptcha']['secret_key'] : ''; $min_score = isset($checker['security']['recaptcha']['min_score']) ? (float)$checker['security']['recaptcha']['min_score'] : 0.5; if (empty($secret_key) || empty($token)) { return [ 'success' => false, 'score' => 0, 'message' => 'reCAPTCHA verification failed: Missing credentials' ]; } // Verify with Google $response = wp_remote_post('https://www.google.com/recaptcha/api/siteverify', [ 'body' => [ 'secret' => $secret_key, 'response' => $token ] ]); if (is_wp_error($response)) { return [ 'success' => false, 'score' => 0, 'message' => 'reCAPTCHA verification failed: ' . $response->get_error_message() ]; } $body = json_decode(wp_remote_retrieve_body($response), true); if (!isset($body['success']) || !$body['success']) { return [ 'success' => false, 'score' => 0, 'message' => 'reCAPTCHA verification failed' ]; } $score = isset($body['score']) ? (float)$body['score'] : 0; if ($score < $min_score) { return [ 'success' => false, 'score' => $score, 'message' => 'reCAPTCHA score too low. Please try again.' ]; } return [ 'success' => true, 'score' => $score ]; } /** * Verify Cloudflare Turnstile token * * @param int $checker_id Checker post ID * @param string $token Turnstile token from frontend * @return array ['success' => bool, 'message' => string] */ public static function verify_turnstile($checker_id, $token) { $checker = get_post_meta($checker_id, 'checker', true); // Check if Turnstile is enabled if (!isset($checker['security']['turnstile']['enabled']) || $checker['security']['turnstile']['enabled'] !== 'yes') { return ['success' => true]; } // Get settings $secret_key = isset($checker['security']['turnstile']['secret_key']) ? $checker['security']['turnstile']['secret_key'] : ''; if (empty($secret_key) || empty($token)) { return [ 'success' => false, 'message' => 'Turnstile verification failed: Missing credentials' ]; } // Verify with Cloudflare $response = wp_remote_post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [ 'body' => [ 'secret' => $secret_key, 'response' => $token ] ]); if (is_wp_error($response)) { return [ 'success' => false, 'message' => 'Turnstile verification failed: ' . $response->get_error_message() ]; } $body = json_decode(wp_remote_retrieve_body($response), true); if (!isset($body['success']) || !$body['success']) { return [ 'success' => false, 'message' => 'Turnstile verification failed' ]; } return ['success' => true]; } /** * Get client IP address * * @return string IP address */ public static function get_client_ip() { $ip = ''; if (!empty($_SERVER['HTTP_CLIENT_IP'])) { $ip = $_SERVER['HTTP_CLIENT_IP']; } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; } else { $ip = $_SERVER['REMOTE_ADDR']; } // If multiple IPs, get the first one if (strpos($ip, ',') !== false) { $ip = trim(explode(',', $ip)[0]); } return $ip; } }