clean build for version 1.4.5 with fixes of security funtionalities, logic branches, etc. Already tested and working fine
This commit is contained in:
431
includes/helpers/class-Captcha-Helper.php
Normal file
431
includes/helpers/class-Captcha-Helper.php
Normal file
@@ -0,0 +1,431 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* CAPTCHA Helper Class
|
||||
*
|
||||
* Handles integration with reCAPTCHA v3 and Cloudflare Turnstile
|
||||
* Provides methods for script loading, token generation, and verification
|
||||
*
|
||||
* @since 1.5.0
|
||||
*/
|
||||
|
||||
class CHECKER_CAPTCHA_HELPER {
|
||||
|
||||
/**
|
||||
* Load CAPTCHA scripts and initialize on page
|
||||
*
|
||||
* @param int $checker_id Checker post ID
|
||||
* @return void
|
||||
*/
|
||||
public static function load_captcha_scripts($checker_id) {
|
||||
$checker = get_post_meta($checker_id, 'checker', true);
|
||||
|
||||
if (!$checker) {
|
||||
error_log("CAPTCHA Helper: No checker meta found for ID: {$checker_id}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if Turnstile is enabled
|
||||
$turnstile_flag = $checker['security']['turnstile']['enabled'] ?? false;
|
||||
$turnstile_enabled = ($turnstile_flag === 'yes' || $turnstile_flag === 'on' || $turnstile_flag === true || $turnstile_flag === 1 || $turnstile_flag === '1');
|
||||
|
||||
// If Turnstile is enabled, prefer it and skip loading reCAPTCHA to avoid dual-widgets
|
||||
$recaptcha_flag = $checker['security']['recaptcha']['enabled'] ?? false;
|
||||
$recaptcha_enabled = !$turnstile_enabled && ($recaptcha_flag === 'yes' || $recaptcha_flag === 'on' || $recaptcha_flag === true || $recaptcha_flag === 1 || $recaptcha_flag === '1');
|
||||
|
||||
if ($turnstile_enabled) {
|
||||
// Make sure any previously enqueued reCAPTCHA is disabled to avoid dual widgets
|
||||
if (wp_script_is('google-recaptcha-v3', 'enqueued') || wp_script_is('google-recaptcha-v3', 'to_enqueue')) {
|
||||
wp_dequeue_script('google-recaptcha-v3');
|
||||
wp_deregister_script('google-recaptcha-v3');
|
||||
}
|
||||
// Also dequeue any generic recaptcha handle some themes/plugins may use
|
||||
if (wp_script_is('recaptcha', 'enqueued') || wp_script_is('recaptcha', 'to_enqueue')) {
|
||||
wp_dequeue_script('recaptcha');
|
||||
wp_deregister_script('recaptcha');
|
||||
}
|
||||
// Ensure the main frontend bundle ignores reCAPTCHA when Turnstile is enabled and hide any badge
|
||||
wp_add_inline_script(
|
||||
'checker-pro',
|
||||
'window.checkerRecaptcha = null; (function(){var b=document.querySelector(".grecaptcha-badge"); if(b){b.style.display=\"none\";}})();',
|
||||
'before'
|
||||
);
|
||||
|
||||
$site_key = $checker['security']['turnstile']['site_key'] ?? '';
|
||||
$theme = $checker['security']['turnstile']['theme'] ?? 'auto';
|
||||
$size = $checker['security']['turnstile']['size'] ?? 'normal';
|
||||
|
||||
if ($site_key) {
|
||||
self::load_turnstile_script($site_key, $theme, $size);
|
||||
}
|
||||
} elseif ($recaptcha_enabled) {
|
||||
$site_key = $checker['security']['recaptcha']['site_key'] ?? '';
|
||||
$action = $checker['security']['recaptcha']['action'] ?? 'checker_validate';
|
||||
$hide_badge = $checker['security']['recaptcha']['hide_badge'] ?? 'no';
|
||||
|
||||
if ($site_key) {
|
||||
self::load_recaptcha_v3_script($site_key, $action, $hide_badge === 'yes');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load reCAPTCHA v3 script and initialize
|
||||
*
|
||||
* @param string $site_key reCAPTCHA site key
|
||||
* @param string $action Action name for verification
|
||||
* @param bool $hide_badge Whether to hide the reCAPTCHA badge
|
||||
* @return void
|
||||
*/
|
||||
private static function load_recaptcha_v3_script($site_key, $action, $hide_badge = false) {
|
||||
// Enqueue reCAPTCHA v3 script
|
||||
wp_enqueue_script(
|
||||
'google-recaptcha-v3',
|
||||
'https://www.google.com/recaptcha/api.js?render=' . $site_key,
|
||||
[],
|
||||
'3.0',
|
||||
true
|
||||
);
|
||||
|
||||
// Pass settings to JavaScript
|
||||
wp_add_inline_script(
|
||||
'google-recaptcha-v3',
|
||||
'
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
console.log("[CAPTCHA Debug] DOMContentLoaded - checking for grecaptcha...");
|
||||
console.log("[CAPTCHA Debug] grecaptcha available:", typeof grecaptcha !== "undefined");
|
||||
|
||||
if (typeof grecaptcha !== "undefined") {
|
||||
console.log("[CAPTCHA Debug] reCAPTCHA v3 detected, initializing...");
|
||||
|
||||
// Store reCAPTCHA settings globally
|
||||
window.checkerRecaptcha = {
|
||||
siteKey: "' . esc_js($site_key) . '",
|
||||
action: "' . esc_js($action) . '"
|
||||
};
|
||||
|
||||
console.log("[CAPTCHA Debug] reCAPTCHA settings:", window.checkerRecaptcha);
|
||||
|
||||
' . ($hide_badge ? '
|
||||
// Hide reCAPTCHA badge
|
||||
var style = document.createElement("style");
|
||||
style.innerHTML = ".grecaptcha-badge { display: none !important; }";
|
||||
document.head.appendChild(style);
|
||||
console.log("[CAPTCHA Debug] reCAPTCHA badge hidden");
|
||||
' : '
|
||||
// Ensure badge is visible
|
||||
var style = document.createElement("style");
|
||||
style.innerHTML = ".grecaptcha-badge { visibility: visible !important; opacity: 1 !important; display: block !important; }";
|
||||
document.head.appendChild(style);
|
||||
') . '
|
||||
|
||||
// Initialize reCAPTCHA for all checker forms
|
||||
initRecaptchaForForms();
|
||||
console.log("[CAPTCHA Debug] reCAPTCHA initialization complete");
|
||||
} else {
|
||||
console.error("[CAPTCHA Debug] grecaptcha NOT available - script may not have loaded");
|
||||
}
|
||||
});
|
||||
|
||||
function initRecaptchaForForms() {
|
||||
var forms = document.querySelectorAll(".dw-checker-container form");
|
||||
forms.forEach(function(form) {
|
||||
var searchButton = form.querySelector(".search-button");
|
||||
if (!searchButton) return;
|
||||
|
||||
// Generate token when search button is clicked
|
||||
searchButton.addEventListener("click", function(e) {
|
||||
var tokenInput = form.querySelector("input[name=recaptcha_token]");
|
||||
|
||||
// If token already exists and is recent (less than 2 minutes old), don\'t regenerate
|
||||
if (tokenInput && tokenInput.value && tokenInput.dataset.timestamp) {
|
||||
var tokenAge = Date.now() - parseInt(tokenInput.dataset.timestamp);
|
||||
if (tokenAge < 120000) { // 2 minutes
|
||||
console.log("reCAPTCHA: Using existing token (age: " + Math.floor(tokenAge/1000) + "s)");
|
||||
return; // Let the click proceed with existing token
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent default to wait for token generation
|
||||
var hasToken = tokenInput && tokenInput.value;
|
||||
if (!hasToken) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
console.log("reCAPTCHA: Generating new token...");
|
||||
|
||||
// Generate new token
|
||||
grecaptcha.ready(function() {
|
||||
grecaptcha.execute(
|
||||
window.checkerRecaptcha.siteKey,
|
||||
{action: window.checkerRecaptcha.action}
|
||||
).then(function(token) {
|
||||
console.log("reCAPTCHA: Token generated successfully");
|
||||
// Add or update token in hidden input
|
||||
if (!tokenInput) {
|
||||
tokenInput = document.createElement("input");
|
||||
tokenInput.type = "hidden";
|
||||
tokenInput.name = "recaptcha_token";
|
||||
form.appendChild(tokenInput);
|
||||
}
|
||||
tokenInput.value = token;
|
||||
tokenInput.dataset.timestamp = Date.now().toString();
|
||||
|
||||
// Trigger the search button click again
|
||||
searchButton.click();
|
||||
}).catch(function(error) {
|
||||
console.error("reCAPTCHA error:", error);
|
||||
alert("reCAPTCHA verification failed. Please refresh the page.");
|
||||
});
|
||||
});
|
||||
}
|
||||
}, true); // Use capture phase to run before other click handlers
|
||||
});
|
||||
}'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Cloudflare Turnstile script and initialize
|
||||
*
|
||||
* @param string $site_key Turnstile site key
|
||||
* @param string $theme Theme (light, dark, auto)
|
||||
* @param string $size Size (normal, compact)
|
||||
* @return void
|
||||
*/
|
||||
private static function load_turnstile_script($site_key, $theme = 'auto', $size = 'normal') {
|
||||
// Enqueue Turnstile script
|
||||
wp_enqueue_script(
|
||||
'cloudflare-turnstile',
|
||||
'https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback',
|
||||
[],
|
||||
'1.0',
|
||||
true
|
||||
);
|
||||
|
||||
// Pass settings to JavaScript
|
||||
wp_add_inline_script(
|
||||
'cloudflare-turnstile',
|
||||
'
|
||||
window.onloadTurnstileCallback = function() {
|
||||
console.log("[CAPTCHA Debug] Turnstile callback triggered");
|
||||
|
||||
// Store Turnstile settings globally
|
||||
window.checkerTurnstile = {
|
||||
siteKey: "' . esc_js($site_key) . '",
|
||||
theme: "' . esc_js($theme) . '",
|
||||
size: "' . esc_js($size) . '"
|
||||
};
|
||||
|
||||
console.log("[CAPTCHA Debug] Turnstile settings:", window.checkerTurnstile);
|
||||
|
||||
// Initialize Turnstile for all checker forms
|
||||
initTurnstileForForms();
|
||||
console.log("[CAPTCHA Debug] Turnstile initialization complete");
|
||||
};
|
||||
|
||||
// Also log when script loads
|
||||
console.log("[CAPTCHA Debug] Turnstile script loading...");
|
||||
|
||||
function initTurnstileForForms() {
|
||||
var forms = document.querySelectorAll(".dw-checker-container form");
|
||||
forms.forEach(function(form) {
|
||||
var submitBtn = form.querySelector(".search-button");
|
||||
|
||||
// Insert Turnstile widget before submit button
|
||||
var container = document.createElement("div");
|
||||
container.className = "dw-checker-turnstile-container";
|
||||
container.style.marginBottom = "1rem";
|
||||
|
||||
if (submitBtn) {
|
||||
submitBtn.parentNode.insertBefore(container, submitBtn);
|
||||
} else {
|
||||
form.appendChild(container);
|
||||
}
|
||||
|
||||
// Render Turnstile widget
|
||||
console.log("Turnstile: Initializing widget");
|
||||
turnstile.render(container, {
|
||||
sitekey: window.checkerTurnstile.siteKey,
|
||||
theme: window.checkerTurnstile.theme,
|
||||
size: window.checkerTurnstile.size,
|
||||
callback: function(token) {
|
||||
console.log("Turnstile: Token generated successfully");
|
||||
// Add token to hidden input
|
||||
var input = form.querySelector("input[name=turnstile_token]");
|
||||
if (!input) {
|
||||
input = document.createElement("input");
|
||||
input.type = "hidden";
|
||||
input.name = "turnstile_token";
|
||||
form.appendChild(input);
|
||||
}
|
||||
input.value = token;
|
||||
input.dataset.timestamp = Date.now().toString();
|
||||
console.log("Turnstile: Token stored in form");
|
||||
},
|
||||
"error-callback": function(error) {
|
||||
console.error("Turnstile error:", error);
|
||||
},
|
||||
"expired-callback": function() {
|
||||
console.warn("Turnstile: Token expired, please complete challenge again");
|
||||
var input = form.querySelector("input[name=turnstile_token]");
|
||||
if (input) {
|
||||
input.value = "";
|
||||
input.dataset.timestamp = "";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Modify form submit to ensure token is available
|
||||
form.addEventListener("submit", function(e) {
|
||||
var tokenInput = form.querySelector("input[name=turnstile_token]");
|
||||
if (!tokenInput || !tokenInput.value) {
|
||||
e.preventDefault();
|
||||
alert("Please wait for the security check to complete.");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CAPTCHA configuration for a checker
|
||||
*
|
||||
* @param int $checker_id Checker post ID
|
||||
* @return array Configuration data
|
||||
*/
|
||||
public static function get_captcha_config($checker_id) {
|
||||
$checker = get_post_meta($checker_id, 'checker', true);
|
||||
|
||||
if (!$checker) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$config = [
|
||||
'recaptcha' => [
|
||||
'enabled' => false,
|
||||
'site_key' => '',
|
||||
'action' => 'checker_validate',
|
||||
'hide_badge' => false
|
||||
],
|
||||
'turnstile' => [
|
||||
'enabled' => false,
|
||||
'site_key' => '',
|
||||
'theme' => 'auto',
|
||||
'size' => 'normal'
|
||||
]
|
||||
];
|
||||
|
||||
// Get reCAPTCHA settings
|
||||
if (isset($checker['security']['recaptcha']['enabled']) && $checker['security']['recaptcha']['enabled'] === 'yes') {
|
||||
$config['recaptcha'] = [
|
||||
'enabled' => true,
|
||||
'site_key' => $checker['security']['recaptcha']['site_key'] ?? '',
|
||||
'action' => $checker['security']['recaptcha']['action'] ?? 'checker_validate',
|
||||
'hide_badge' => $checker['security']['recaptcha']['hide_badge'] === 'yes'
|
||||
];
|
||||
}
|
||||
|
||||
// Get Turnstile settings
|
||||
if (isset($checker['security']['turnstile']['enabled']) && $checker['security']['turnstile']['enabled'] === 'yes') {
|
||||
$config['turnstile'] = [
|
||||
'enabled' => true,
|
||||
'site_key' => $checker['security']['turnstile']['site_key'] ?? '',
|
||||
'theme' => $checker['security']['turnstile']['theme'] ?? 'auto',
|
||||
'size' => $checker['security']['turnstile']['size'] ?? 'normal'
|
||||
];
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CAPTCHA field HTML for form
|
||||
*
|
||||
* @param int $checker_id Checker post ID
|
||||
* @return string HTML for CAPTCHA fields
|
||||
*/
|
||||
public static function get_captcha_fields($checker_id) {
|
||||
$config = self::get_captcha_config($checker_id);
|
||||
// Prefer Turnstile over reCAPTCHA when both are flagged (should not happen in UI)
|
||||
if ($config['turnstile']['enabled']) {
|
||||
$config['recaptcha']['enabled'] = false;
|
||||
}
|
||||
$html = '';
|
||||
|
||||
if ($config['recaptcha']['enabled'] || $config['turnstile']['enabled']) {
|
||||
// Add container for CAPTCHA
|
||||
$html .= '<div class="dw-checker-captcha-wrapper">';
|
||||
|
||||
if ($config['recaptcha']['enabled']) {
|
||||
// reCAPTCHA v3 doesn't need visible fields
|
||||
$html .= '<input type="hidden" name="recaptcha_token" id="recaptcha-token-' . esc_attr($checker_id) . '">';
|
||||
}
|
||||
|
||||
if ($config['turnstile']['enabled']) {
|
||||
// Turnstile container will be added dynamically
|
||||
$html .= '<div id="turnstile-container-' . esc_attr($checker_id) . '" class="dw-checker-turnstile-container"></div>';
|
||||
$html .= '<input type="hidden" name="turnstile_token" id="turnstile-token-' . esc_attr($checker_id) . '">';
|
||||
}
|
||||
|
||||
$html .= '</div>';
|
||||
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if CAPTCHA is configured and valid
|
||||
*
|
||||
* @param int $checker_id Checker post ID
|
||||
* @return array Validity check result
|
||||
*/
|
||||
public static function validate_captcha_config($checker_id) {
|
||||
$config = self::get_captcha_config($checker_id);
|
||||
$result = [
|
||||
'valid' => false,
|
||||
'type' => null,
|
||||
'issues' => []
|
||||
];
|
||||
|
||||
// Check reCAPTCHA
|
||||
if ($config['recaptcha']['enabled']) {
|
||||
if (empty($config['recaptcha']['site_key'])) {
|
||||
$result['issues'][] = 'reCAPTCHA site key is missing';
|
||||
} elseif (!preg_match('/^6Lc[a-zA-Z0-9_-]{38}$/', $config['recaptcha']['site_key'])) {
|
||||
$result['issues'][] = 'reCAPTCHA site key appears to be invalid';
|
||||
}
|
||||
|
||||
if (count($result['issues']) === 0) {
|
||||
$result['valid'] = true;
|
||||
$result['type'] = 'recaptcha';
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
// Check Turnstile
|
||||
if ($config['turnstile']['enabled']) {
|
||||
if (empty($config['turnstile']['site_key'])) {
|
||||
$result['issues'][] = 'Turnstile site key is missing';
|
||||
} elseif (!preg_match('/^0x4AAA[a-zA-Z0-9_-]{33}$/', $config['turnstile']['site_key'])) {
|
||||
$result['issues'][] = 'Turnstile site key appears to be invalid';
|
||||
}
|
||||
|
||||
if (count($result['issues']) === 0) {
|
||||
$result['valid'] = true;
|
||||
$result['type'] = 'turnstile';
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
// If both are enabled, it's an issue
|
||||
if ($config['recaptcha']['enabled'] && $config['turnstile']['enabled']) {
|
||||
$result['issues'][] = 'Both reCAPTCHA and Turnstile are enabled (only one should be used)';
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user