639 lines
25 KiB
PHP
639 lines
25 KiB
PHP
<?php
|
|
|
|
class CHECKER_SECURITY {
|
|
|
|
/**
|
|
* Check rate limit for an IP address using improved method
|
|
*
|
|
* @param int $checker_id Checker post ID
|
|
* @param string $ip IP address to check
|
|
* @return array ['allowed' => 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 with defaults
|
|
$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 with checker-specific prefix
|
|
$transient_prefix = 'checker_rate_' . $checker_id . '_' . self::get_ip_hash($ip);
|
|
$transient_key = $transient_prefix . '_attempts';
|
|
$block_key = $transient_prefix . '_blocked';
|
|
|
|
// Check if IP is already blocked
|
|
$blocked_until = get_transient($block_key);
|
|
if ($blocked_until !== false) {
|
|
// Calculate remaining time
|
|
$time_remaining = $blocked_until - time();
|
|
$minutes_remaining = ceil($time_remaining / 60);
|
|
|
|
// Build user-friendly message
|
|
$custom_message = $error_message;
|
|
if ($custom_message === "Too many attempts. Please try again later.") {
|
|
$custom_message = sprintf(
|
|
__('You are temporarily blocked. Please wait %d minutes before trying again.', 'sheet-data-checker-pro'),
|
|
max(1, $minutes_remaining)
|
|
);
|
|
}
|
|
|
|
return [
|
|
'allowed' => false,
|
|
'message' => $custom_message,
|
|
'remaining' => 0,
|
|
'blocked_until' => $blocked_until
|
|
];
|
|
}
|
|
|
|
// 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
|
|
$block_until = time() + ($block_duration * 60);
|
|
set_transient($block_key, $block_until, $block_duration * 60);
|
|
// Reset attempts counter
|
|
delete_transient($transient_key);
|
|
|
|
// Log the rate limit block
|
|
if (class_exists('CHECKER_SECURITY_LOGGER')) {
|
|
CHECKER_SECURITY_LOGGER::log_rate_limit_block($checker_id, $ip, [
|
|
'max_attempts' => $max_attempts,
|
|
'time_window' => $time_window,
|
|
'block_duration' => $block_duration
|
|
]);
|
|
}
|
|
|
|
// Build user-friendly error message
|
|
$minutes_remaining = ceil($block_duration);
|
|
$custom_message = $error_message;
|
|
|
|
// If using default message, enhance it with time info
|
|
if ($custom_message === "Too many attempts. Please try again later.") {
|
|
$custom_message = sprintf(
|
|
__('Too many attempts. Please wait %d minutes before trying again.', 'sheet-data-checker-pro'),
|
|
$minutes_remaining
|
|
);
|
|
}
|
|
|
|
return [
|
|
'allowed' => false,
|
|
'message' => $custom_message,
|
|
'remaining' => 0,
|
|
'blocked_until' => $block_until
|
|
];
|
|
}
|
|
|
|
// Update attempts counter
|
|
set_transient($transient_key, $attempts, $time_window * 60);
|
|
|
|
return [
|
|
'allowed' => true,
|
|
'remaining' => $max_attempts - $attempts
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Verify reCAPTCHA v3 token with improved implementation
|
|
*
|
|
* @param int $checker_id Checker post ID
|
|
* @param string $token reCAPTCHA token from frontend
|
|
* @param string $action Action name for reCAPTCHA (optional)
|
|
* @return array ['success' => bool, 'score' => float, 'message' => string]
|
|
*/
|
|
public static function verify_recaptcha($checker_id, $token, $action = 'submit') {
|
|
$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_raw = isset($checker['security']['recaptcha']['secret_key']) ? $checker['security']['recaptcha']['secret_key'] : '';
|
|
$secret_key = trim((string) $secret_key_raw);
|
|
|
|
$min_score_raw = isset($checker['security']['recaptcha']['min_score']) ? $checker['security']['recaptcha']['min_score'] : 0.5;
|
|
if (is_string($min_score_raw)) {
|
|
$min_score_raw = str_replace(',', '.', $min_score_raw);
|
|
}
|
|
$min_score = (float) $min_score_raw;
|
|
|
|
if (empty($secret_key) || empty($token)) {
|
|
error_log("Sheet Data Checker: reCAPTCHA verification failed - Missing credentials (Checker ID: {$checker_id})");
|
|
return [
|
|
'success' => false,
|
|
'score' => 0,
|
|
'message' => 'reCAPTCHA verification failed: Missing credentials'
|
|
];
|
|
}
|
|
|
|
error_log("Sheet Data Checker: Starting reCAPTCHA verification (Checker ID: {$checker_id}, Min Score: {$min_score})");
|
|
|
|
// Verify with Google using WordPress HTTP API
|
|
$response = wp_remote_post('https://www.google.com/recaptcha/api/siteverify', [
|
|
'timeout' => 10,
|
|
'body' => [
|
|
'secret' => $secret_key,
|
|
'response' => $token,
|
|
'remoteip' => self::get_client_ip()
|
|
]
|
|
]);
|
|
|
|
if (is_wp_error($response)) {
|
|
error_log('Sheet Data Checker: reCAPTCHA verification failed - ' . $response->get_error_message());
|
|
return [
|
|
'success' => false,
|
|
'score' => 0,
|
|
'message' => 'reCAPTCHA verification failed: ' . $response->get_error_message()
|
|
];
|
|
}
|
|
|
|
$body = json_decode(wp_remote_retrieve_body($response), true);
|
|
|
|
$score = isset($body['score']) ? (float)$body['score'] : 0;
|
|
$response_action = isset($body['action']) ? $body['action'] : '';
|
|
|
|
if (!isset($body['success']) || !$body['success']) {
|
|
$error_codes = isset($body['error-codes']) ? $body['error-codes'] : 'unknown';
|
|
error_log("Sheet Data Checker: reCAPTCHA verification failed - Error codes: {$error_codes}");
|
|
|
|
// Log the CAPTCHA failure
|
|
if (class_exists('CHECKER_SECURITY_LOGGER')) {
|
|
CHECKER_SECURITY_LOGGER::log_captcha_failure($checker_id, 'recaptcha', [
|
|
'success' => false,
|
|
'score' => $score,
|
|
'error_codes' => is_array($body['error-codes']) ? $body['error-codes'] : []
|
|
]);
|
|
}
|
|
|
|
return [
|
|
'success' => false,
|
|
'score' => $score,
|
|
'message' => 'reCAPTCHA verification failed'
|
|
];
|
|
}
|
|
|
|
// Verify action matches if specified (reCAPTCHA v3 feature)
|
|
if ($action && $response_action !== $action) {
|
|
error_log("Sheet Data Checker: reCAPTCHA action mismatch - Expected: {$action}, Got: {$response_action}");
|
|
return [
|
|
'success' => false,
|
|
'score' => $score,
|
|
'message' => 'reCAPTCHA action verification failed'
|
|
];
|
|
}
|
|
|
|
if ($score < $min_score) {
|
|
error_log("Sheet Data Checker: reCAPTCHA score too low - Score: {$score}, Min: {$min_score}");
|
|
return [
|
|
'success' => false,
|
|
'score' => $score,
|
|
'message' => 'reCAPTCHA score too low. Please try again.'
|
|
];
|
|
}
|
|
|
|
error_log("Sheet Data Checker: reCAPTCHA verification SUCCESS - Score: {$score}, Action: {$response_action}");
|
|
|
|
return [
|
|
'success' => true,
|
|
'score' => $score
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Verify Cloudflare Turnstile token with improved implementation
|
|
*
|
|
* @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)) {
|
|
error_log("Sheet Data Checker: Turnstile verification failed - Missing credentials (Checker ID: {$checker_id})");
|
|
return [
|
|
'success' => false,
|
|
'message' => 'Turnstile verification failed: Missing credentials'
|
|
];
|
|
}
|
|
|
|
error_log("Sheet Data Checker: Starting Turnstile verification (Checker ID: {$checker_id})");
|
|
|
|
// Verify with Cloudflare using WordPress HTTP API
|
|
$response = wp_remote_post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
|
|
'timeout' => 10,
|
|
'body' => [
|
|
'secret' => $secret_key,
|
|
'response' => $token,
|
|
'remoteip' => self::get_client_ip()
|
|
]
|
|
]);
|
|
|
|
if (is_wp_error($response)) {
|
|
error_log('Sheet Data Checker: Turnstile verification failed - ' . $response->get_error_message());
|
|
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']) {
|
|
$error_codes = isset($body['error-codes']) ? implode(', ', $body['error-codes']) : 'unknown';
|
|
error_log("Sheet Data Checker: Turnstile verification failed - Error codes: {$error_codes}");
|
|
|
|
// Log the CAPTCHA failure
|
|
if (class_exists('CHECKER_SECURITY_LOGGER')) {
|
|
CHECKER_SECURITY_LOGGER::log_captcha_failure($checker_id, 'turnstile', [
|
|
'success' => false,
|
|
'error_codes' => is_array($body['error-codes']) ? $body['error-codes'] : []
|
|
]);
|
|
}
|
|
|
|
return [
|
|
'success' => false,
|
|
'message' => 'Turnstile verification failed'
|
|
];
|
|
}
|
|
|
|
error_log("Sheet Data Checker: Turnstile verification SUCCESS");
|
|
|
|
return ['success' => true];
|
|
}
|
|
|
|
/**
|
|
* Get client IP address with improved proxy detection
|
|
*
|
|
* @return string IP address
|
|
*/
|
|
public static function get_client_ip() {
|
|
// Check for Cloudflare first
|
|
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
|
|
return sanitize_text_field($_SERVER['HTTP_CF_CONNECTING_IP']);
|
|
}
|
|
|
|
// Check various proxy headers
|
|
$ip_headers = [
|
|
'HTTP_X_FORWARDED_FOR',
|
|
'HTTP_X_REAL_IP',
|
|
'HTTP_X_FORWARDED',
|
|
'HTTP_FORWARDED_FOR',
|
|
'HTTP_FORWARDED',
|
|
'REMOTE_ADDR'
|
|
];
|
|
|
|
foreach ($ip_headers as $header) {
|
|
if (!empty($_SERVER[$header])) {
|
|
$ips = explode(',', $_SERVER[$header]);
|
|
$ip = trim($ips[0]);
|
|
|
|
// Validate IP
|
|
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
|
return $ip;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback to REMOTE_ADDR
|
|
return !empty($_SERVER['REMOTE_ADDR']) ? sanitize_text_field($_SERVER['REMOTE_ADDR']) : '0.0.0.0';
|
|
}
|
|
|
|
/**
|
|
* Create a hash of the IP for storage (more secure than storing raw IP)
|
|
*
|
|
* @param string $ip IP address
|
|
* @return string Hashed IP
|
|
*/
|
|
private static function get_ip_hash($ip) {
|
|
return wp_hash($ip . 'sheet_checker_rate_limit');
|
|
}
|
|
|
|
/**
|
|
* Verify nonce for AJAX requests
|
|
*
|
|
* @param string $nonce Nonce value
|
|
* @param string $action Action name
|
|
* @param int $checker_id Optional checker ID for logging
|
|
* @return bool True if valid, false otherwise
|
|
*/
|
|
public static function verify_nonce($nonce, $action, $checker_id = 0) {
|
|
if (!$nonce) {
|
|
return false;
|
|
}
|
|
|
|
$is_valid = wp_verify_nonce($nonce, $action) !== false;
|
|
|
|
// Log nonce failure if checker_id is provided
|
|
if (!$is_valid && $checker_id && class_exists('CHECKER_SECURITY_LOGGER')) {
|
|
CHECKER_SECURITY_LOGGER::log_nonce_failure($checker_id, $nonce);
|
|
}
|
|
|
|
return $is_valid;
|
|
}
|
|
|
|
/**
|
|
* Check if security features are properly configured
|
|
*
|
|
* @param int $checker_id Checker post ID
|
|
* @return array Configuration status
|
|
*/
|
|
public static function check_security_config($checker_id) {
|
|
$checker = get_post_meta($checker_id, 'checker', true);
|
|
$issues = [];
|
|
|
|
// Check rate limiting
|
|
if (isset($checker['security']['rate_limit']['enabled']) && $checker['security']['rate_limit']['enabled'] === 'yes') {
|
|
if (!isset($checker['security']['rate_limit']['max_attempts']) || $checker['security']['rate_limit']['max_attempts'] < 1) {
|
|
$issues[] = 'Rate limiting enabled but max attempts not set or invalid';
|
|
}
|
|
|
|
if (!isset($checker['security']['rate_limit']['time_window']) || $checker['security']['rate_limit']['time_window'] < 1) {
|
|
$issues[] = 'Rate limiting enabled but time window not set or invalid';
|
|
}
|
|
}
|
|
|
|
// Check reCAPTCHA
|
|
if (isset($checker['security']['recaptcha']['enabled']) && $checker['security']['recaptcha']['enabled'] === 'yes') {
|
|
if (empty($checker['security']['recaptcha']['site_key'])) {
|
|
$issues[] = 'reCAPTCHA enabled but site key not set';
|
|
}
|
|
|
|
if (empty($checker['security']['recaptcha']['secret_key'])) {
|
|
$issues[] = 'reCAPTCHA enabled but secret key not set';
|
|
}
|
|
}
|
|
|
|
// Check Turnstile
|
|
if (isset($checker['security']['turnstile']['enabled']) && $checker['security']['turnstile']['enabled'] === 'yes') {
|
|
if (empty($checker['security']['turnstile']['site_key'])) {
|
|
$issues[] = 'Turnstile enabled but site key not set';
|
|
}
|
|
|
|
if (empty($checker['security']['turnstile']['secret_key'])) {
|
|
$issues[] = 'Turnstile enabled but secret key not set';
|
|
}
|
|
}
|
|
|
|
// Check if both CAPTCHAs are enabled
|
|
$recaptcha_enabled = isset($checker['security']['recaptcha']['enabled']) && $checker['security']['recaptcha']['enabled'] === 'yes';
|
|
$turnstile_enabled = isset($checker['security']['turnstile']['enabled']) && $checker['security']['turnstile']['enabled'] === 'yes';
|
|
|
|
if ($recaptcha_enabled && $turnstile_enabled) {
|
|
$issues[] = 'Both reCAPTCHA and Turnstile are enabled - only one should be used';
|
|
}
|
|
|
|
return [
|
|
'configured' => empty($issues),
|
|
'issues' => $issues
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Sanitize and validate user input
|
|
*
|
|
* @param mixed $value Value to sanitize
|
|
* @param string $type Type of value (text, email, url, etc.)
|
|
* @return mixed Sanitized value
|
|
*/
|
|
public static function sanitize_input($value, $type = 'text') {
|
|
if (!is_string($value)) {
|
|
return $value;
|
|
}
|
|
|
|
switch ($type) {
|
|
case 'email':
|
|
return sanitize_email($value);
|
|
case 'url':
|
|
return esc_url_raw($value);
|
|
case 'text':
|
|
default:
|
|
return sanitize_text_field($value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a security feature is enabled for a checker
|
|
*
|
|
* @param array $checker Checker settings array
|
|
* @param string $feature Feature name: 'recaptcha', 'turnstile', 'rate_limit', 'honeypot'
|
|
* @return bool
|
|
*/
|
|
public static function is_enabled($checker, $feature) {
|
|
if (!is_array($checker) || !isset($checker['security'])) {
|
|
return false;
|
|
}
|
|
$enabled = $checker['security'][$feature]['enabled'] ?? false;
|
|
// Accept common truthy flags: 'yes', 'on', true, 1
|
|
return $enabled === 'yes' || $enabled === 'on' || $enabled === true || $enabled === 1 || $enabled === '1';
|
|
}
|
|
|
|
/**
|
|
* Get security setting value with default
|
|
*
|
|
* @param array $checker Checker settings array
|
|
* @param string $feature Feature name
|
|
* @param string $key Setting key
|
|
* @param mixed $default Default value
|
|
* @return mixed
|
|
*/
|
|
public static function get_setting($checker, $feature, $key, $default = '') {
|
|
if (!is_array($checker) || !isset($checker['security'][$feature][$key])) {
|
|
return $default;
|
|
}
|
|
return $checker['security'][$feature][$key];
|
|
}
|
|
|
|
/**
|
|
* Get custom error message with i18n support
|
|
*
|
|
* @param array $checker Checker settings array
|
|
* @param string $feature Feature name
|
|
* @param string $default_key Translation key for default message
|
|
* @return string
|
|
*/
|
|
public static function get_error_message($checker, $feature, $default_key = '') {
|
|
$custom_message = self::get_setting($checker, $feature, 'error_message', '');
|
|
|
|
if (!empty($custom_message)) {
|
|
return $custom_message;
|
|
}
|
|
|
|
// Default translatable messages
|
|
$defaults = [
|
|
'recaptcha' => __('reCAPTCHA verification failed. Please try again.', 'sheet-data-checker-pro'),
|
|
'recaptcha_required' => __('reCAPTCHA verification required.', 'sheet-data-checker-pro'),
|
|
'turnstile' => __('Turnstile verification failed. Please try again.', 'sheet-data-checker-pro'),
|
|
'turnstile_required' => __('Turnstile verification required.', 'sheet-data-checker-pro'),
|
|
'rate_limit' => __('Too many attempts. Please try again later.', 'sheet-data-checker-pro'),
|
|
'honeypot' => __('Security validation failed.', 'sheet-data-checker-pro'),
|
|
'nonce_expired' => __('Session expired. Please refresh the page and try again.', 'sheet-data-checker-pro'),
|
|
];
|
|
|
|
return isset($defaults[$default_key]) ? $defaults[$default_key] : $defaults[$feature] ?? '';
|
|
}
|
|
|
|
/**
|
|
* Unified security verification for all CAPTCHA and security checks
|
|
* Returns error response array if check fails, null if all checks pass
|
|
*
|
|
* @param int $checker_id Checker post ID
|
|
* @param array $checker Checker settings
|
|
* @param array $request Request data ($_REQUEST)
|
|
* @param bool $skip_captcha_for_show_all Whether to skip CAPTCHA for show-all mode initial load
|
|
* @return array|null Error response or null if passed
|
|
*/
|
|
public static function verify_all_security($checker_id, $checker, $request, $skip_captcha_for_show_all = false) {
|
|
$ip = self::get_client_ip();
|
|
|
|
// Check honeypot first (fastest check)
|
|
if (self::is_enabled($checker, 'honeypot')) {
|
|
$honeypot_value = '';
|
|
if (isset($request['honeypot_name'], $request['honeypot_value'])) {
|
|
$honeypot_value = $request['honeypot_value'];
|
|
} elseif (isset($request['website_url_hp'])) {
|
|
$honeypot_value = $request['website_url_hp'];
|
|
}
|
|
if (!empty($honeypot_value)) {
|
|
// Log honeypot trigger
|
|
if (class_exists('CHECKER_SECURITY_LOGGER')) {
|
|
CHECKER_SECURITY_LOGGER::log_security_event($checker_id, 'honeypot_triggered', [
|
|
'ip' => $ip
|
|
]);
|
|
}
|
|
return [
|
|
'success' => false,
|
|
'message' => self::get_error_message($checker, 'honeypot', 'honeypot'),
|
|
'type' => 'honeypot'
|
|
];
|
|
}
|
|
}
|
|
|
|
// Check rate limit
|
|
if (self::is_enabled($checker, 'rate_limit')) {
|
|
$rate_limit = self::check_rate_limit($checker_id, $ip);
|
|
if (!$rate_limit['allowed']) {
|
|
return [
|
|
'success' => false,
|
|
'message' => $rate_limit['message'],
|
|
'type' => 'rate_limit'
|
|
];
|
|
}
|
|
}
|
|
|
|
// Skip CAPTCHA checks for show-all initial load if configured
|
|
if ($skip_captcha_for_show_all) {
|
|
return null;
|
|
}
|
|
|
|
// If both CAPTCHAs are flagged, prefer Turnstile and skip reCAPTCHA to avoid double validation
|
|
$turnstile_enabled = self::is_enabled($checker, 'turnstile');
|
|
$recaptcha_enabled = !$turnstile_enabled && self::is_enabled($checker, 'recaptcha');
|
|
|
|
// Check reCAPTCHA if enabled (and Turnstile not enabled)
|
|
if ($recaptcha_enabled) {
|
|
$token = isset($request['recaptcha_token']) ? $request['recaptcha_token'] : '';
|
|
if (empty($token)) {
|
|
return [
|
|
'success' => false,
|
|
'message' => self::get_error_message($checker, 'recaptcha', 'recaptcha_required'),
|
|
'type' => 'recaptcha'
|
|
];
|
|
}
|
|
$recaptcha_action = isset($checker['security']['recaptcha']['action']) ? $checker['security']['recaptcha']['action'] : 'submit';
|
|
$recaptcha = self::verify_recaptcha($checker_id, $token, $recaptcha_action);
|
|
if (!$recaptcha['success']) {
|
|
return [
|
|
'success' => false,
|
|
'message' => isset($recaptcha['message']) ? $recaptcha['message'] : self::get_error_message($checker, 'recaptcha', 'recaptcha'),
|
|
'type' => 'recaptcha'
|
|
];
|
|
}
|
|
}
|
|
|
|
// Check Turnstile if enabled
|
|
if ($turnstile_enabled) {
|
|
$token = isset($request['turnstile_token']) ? $request['turnstile_token'] : '';
|
|
if (empty($token)) {
|
|
return [
|
|
'success' => false,
|
|
'message' => self::get_error_message($checker, 'turnstile', 'turnstile_required'),
|
|
'type' => 'turnstile'
|
|
];
|
|
}
|
|
$turnstile = self::verify_turnstile($checker_id, $token);
|
|
if (!$turnstile['success']) {
|
|
return [
|
|
'success' => false,
|
|
'message' => isset($turnstile['message']) ? $turnstile['message'] : self::get_error_message($checker, 'turnstile', 'turnstile'),
|
|
'type' => 'turnstile'
|
|
];
|
|
}
|
|
}
|
|
|
|
return null; // All checks passed
|
|
}
|
|
|
|
/**
|
|
* Check if nonce is expired and return appropriate error
|
|
*
|
|
* @param string $nonce Nonce value
|
|
* @param string $action Nonce action
|
|
* @return array ['valid' => bool, 'expired' => bool, 'message' => string]
|
|
*/
|
|
public static function check_nonce_status($nonce, $action = 'checker_ajax_nonce') {
|
|
$verify = wp_verify_nonce($nonce, $action);
|
|
|
|
if ($verify === false) {
|
|
// Nonce is completely invalid or expired
|
|
return [
|
|
'valid' => false,
|
|
'expired' => true,
|
|
'message' => __('Session expired. Please refresh the page and try again.', 'sheet-data-checker-pro')
|
|
];
|
|
}
|
|
|
|
if ($verify === 2) {
|
|
// Nonce is valid but was generated 12-24 hours ago (expiring soon)
|
|
return [
|
|
'valid' => true,
|
|
'expired' => false,
|
|
'expiring_soon' => true,
|
|
'message' => ''
|
|
];
|
|
}
|
|
|
|
// Nonce is fully valid (generated within 12 hours)
|
|
return [
|
|
'valid' => true,
|
|
'expired' => false,
|
|
'expiring_soon' => false,
|
|
'message' => ''
|
|
];
|
|
}
|
|
}
|