diff --git a/assets/admin-editor.js b/assets/admin-editor.js index 6147a8c..d1546a2 100644 --- a/assets/admin-editor.js +++ b/assets/admin-editor.js @@ -306,6 +306,9 @@ jQuery(document).ready(function ($) { // Append the rendered HTML $(".result-value-output").html(html); + // Sync type-button-link visibility for initial render + $(".result-value-output .output-type").trigger("change"); + // You can call other functions after the template is rendered append_fields_to_preview(); }, diff --git a/assets/public.js b/assets/public.js index 425afae..6e2b406 100644 --- a/assets/public.js +++ b/assets/public.js @@ -22,35 +22,26 @@ jQuery(document).ready(function ($) { var recaptchaToken = thisChecker.find('input[name="recaptcha_token"]').val(); if (recaptchaToken) { baseData.recaptcha_token = recaptchaToken; - console.log("Security: reCAPTCHA token added to request"); - } else { - console.log("Security: No reCAPTCHA token found"); } - + // Add Turnstile token if present var turnstileToken = thisChecker.find('input[name="turnstile_token"]').val(); if (turnstileToken) { baseData.turnstile_token = turnstileToken; - console.log("Security: Turnstile token added to request"); - } else { - console.log("Security: No Turnstile token found"); } - + // Add honeypot field (should be empty) var hpInput = thisChecker.find('input[data-hp-field="1"]'); if (hpInput.length) { baseData.honeypot_name = hpInput.attr('name'); baseData.honeypot_value = hpInput.val() || ""; - console.log("Security: Honeypot field added (value: " + (hpInput.val() === "" ? "empty" : "filled") + ")"); } else { var legacyHp = thisChecker.find('input[name="website_url_hp"]').val(); if (typeof legacyHp !== 'undefined') { baseData.website_url_hp = legacyHp || ""; - console.log("Security: Honeypot field added (legacy value: " + (legacyHp === "" ? "empty" : "filled") + ")"); } } - console.log("Security: AJAX data prepared", baseData); return baseData; } @@ -62,18 +53,18 @@ jQuery(document).ready(function ($) { function handleAjaxError(xhr, checkerId) { var thisChecker = $("#checker-" + checkerId); var response = xhr.responseJSON; - + // WordPress wp_send_json_error format: {success: false, data: {message: "...", type: "..."}} if (response && response.data) { var errorType = response.data.type || 'error'; var errorMessage = response.data.message || 'An error occurred'; - + // Handle nonce expiry - prompt page refresh if (errorType === 'nonce_expired') { showSecurityError(checkerId, errorMessage, true); return; } - + // Handle CAPTCHA errors if (errorType === 'recaptcha' || errorType === 'turnstile') { showSecurityError(checkerId, errorMessage, false); @@ -81,19 +72,19 @@ jQuery(document).ready(function ($) { resetCaptchaWidget(checkerId, errorType); return; } - + // Handle rate limit with enhanced message if (errorType === 'rate_limit') { showSecurityError(checkerId, errorMessage, false); return; } - + // Handle honeypot trigger (silent fail for bots) if (errorType === 'honeypot') { showSecurityError(checkerId, errorMessage, false); return; } - + // Generic error showSecurityError(checkerId, errorMessage, false); } else if (response && response.message) { @@ -199,12 +190,12 @@ jQuery(document).ready(function ($) { function refreshRecaptchaToken(checkerId) { return new Promise(function(resolve, reject) { var thisChecker = $("#checker-" + checkerId); - + if (typeof grecaptcha === 'undefined' || !window.checkerRecaptcha) { resolve(); // No reCAPTCHA configured return; } - + grecaptcha.ready(function() { grecaptcha.execute(window.checkerRecaptcha.siteKey, { action: window.checkerRecaptcha.action || 'submit' @@ -217,7 +208,6 @@ jQuery(document).ready(function ($) { tokenInput.val(token).attr('data-timestamp', Date.now().toString()); resolve(); }).catch(function(error) { - console.error('reCAPTCHA refresh error:', error); reject(error); }); }); @@ -246,19 +236,33 @@ jQuery(document).ready(function ($) { } function ensureCaptchaReady(checkerId) { - // If reCAPTCHA is configured, always refresh/generate a token (it will create the hidden input if missing) + var thisChecker = $("#checker-" + checkerId); + + // If reCAPTCHA is configured, check if we need to refresh the token if (window.checkerRecaptcha) { + var tokenInput = thisChecker.find('input[name="recaptcha_token"]'); + var hasToken = tokenInput.length && tokenInput.val(); + + // Check token age + if (hasToken) { + var tokenAge = Date.now() - parseInt(tokenInput.attr('data-timestamp') || '0'); + // Token is valid for 2 minutes, use it if it's less than 90 seconds old + if (tokenAge < 90000) { + return Promise.resolve(); + } + } + return waitForRecaptcha() .then(function() { return refreshRecaptchaToken(checkerId); }) .catch(function(err) { - return Promise.reject(err); + var errorMsg = err ? err.message : "reCAPTCHA verification failed"; + return Promise.reject(new Error(errorMsg)); }); } // Refresh Turnstile if stale - var thisChecker = $("#checker-" + checkerId); var turnstileInput = thisChecker.find('input[name="turnstile_token"]'); if (turnstileInput.length && turnstileInput.val()) { var ts = parseInt(turnstileInput.attr("data-timestamp") || "0"); @@ -271,19 +275,23 @@ jQuery(document).ready(function ($) { turnstileInput.val(""); } } + return Promise.resolve(); } - function waitForRecaptcha(timeoutMs = 5000) { + function waitForRecaptcha(timeoutMs = 10000) { return new Promise(function(resolve, reject) { var start = Date.now(); + (function check() { if (typeof grecaptcha !== "undefined" && typeof grecaptcha.ready === "function") { return resolve(); } + if (Date.now() - start > timeoutMs) { - return reject(new Error("reCAPTCHA script not loaded")); + return reject(new Error("reCAPTCHA script failed to load. Please check your internet connection or try disabling reCAPTCHA.")); } + setTimeout(check, 200); })(); }); @@ -309,10 +317,9 @@ jQuery(document).ready(function ($) { loadAllData(checkerId, settings, true); // true = initial load } - // Preload captcha tokens to surface badge and avoid first-click delays - ensureCaptchaReady(checkerId).catch(function(err){ - console.warn("Captcha preload failed", err); - showSecurityError(checkerId, checkerSecurity.i18n ? checkerSecurity.i18n.security_error : "Security validation failed.", false); + // Preload reCAPTCHA token on page load (improves score by showing natural browsing behavior) + ensureCaptchaReady(checkerId).catch(function(){ + // Silently fail on preload - will retry on submit }); } }); @@ -585,10 +592,16 @@ jQuery(document).ready(function ($) { var button_text = escapeHtml(outputSetting.button_text || "Click"); var bg_color = escapeHtml(outputSetting.bg_color || "#333333"); var text_color = escapeHtml(outputSetting.text_color || "#ffffff"); + var empty_fallback = escapeHtml(outputSetting.empty_fallback || ""); var safeQ = escapeHtml(q); var safeVal = escapeHtml(r); + var isEmpty = !r || String(r).trim() === ""; - if (type == "link_button") { + if (isEmpty && empty_fallback) { + safeVal = empty_fallback; + } else if (isEmpty) { + safeVal = ""; + } else if (type == "link_button") { safeVal = '"; } else if (type == "whatsapp_button") { r = '' + - button_text + + '" target="_blank" rel="noopener" style="background-color: ' + + escapeHtml(bg_color) + + "; color: " + + escapeHtml(text_color) + + ';">' + + escapeHtml(button_text) + ""; } else if (type == "image") { r = '' +
-                        q +
+                        escapeHtml(q) +
                         ''; + } else if (type == "text") { + r = escapeHtml(r); } resultDiv += ""; resultDiv += @@ -1308,7 +1371,7 @@ jQuery(document).ready(function ($) { 'px;">' + - q + + escapeHtml(q) + ""; resultDiv += '' + - prefix + + escapeHtml(prefix || "") + r + ""; resultDiv += ""; @@ -1358,49 +1421,76 @@ jQuery(document).ready(function ($) { var prefix = ""; var type = ""; var button_text = ""; + var empty_fallback = ""; var hidden = "no"; + var bg_color = "#333333"; + var text_color = "#ffffff"; $.each(res.output, function (o, p) { if (q == p.key) { prefix = p.prefix; type = p.type; button_text = p.button_text; + if ("empty_fallback" in p) { + empty_fallback = p.empty_fallback; + } if ("hide" in p) { hidden = p.hide; } + if ("bg_color" in p) { + bg_color = p.bg_color; + } + if ("text_color" in p) { + text_color = p.text_color; + } } }); if (hidden == "yes") { return; } - if (type == "link_button") { + var isEmpty = !r || String(r).trim() === ""; + if (isEmpty && empty_fallback) { + r = escapeHtml(empty_fallback); + } else if (isEmpty) { + r = ""; + } else if (type == "link_button") { r = '' + - button_text + + '" target="_blank" rel="noopener" style="background-color: ' + + escapeHtml(bg_color) + + "; color: " + + escapeHtml(text_color) + + ';">' + + escapeHtml(button_text) + ""; } else if (type == "whatsapp_button") { r = '' + - button_text + + '" target="_blank" rel="noopener" style="background-color: ' + + escapeHtml(bg_color) + + "; color: " + + escapeHtml(text_color) + + ';">' + + escapeHtml(button_text) + ""; } else if (type == "image") { r = '' +
-                        q +
+                        escapeHtml(q) +
                         ''; + } else if (type == "text") { + r = escapeHtml(r); } resultDiv += @@ -1413,13 +1503,13 @@ jQuery(document).ready(function ($) { '
' + - q + + escapeHtml(q) + "
"; resultDiv += '
' + - prefix + + escapeHtml(prefix || "") + r + "
"; resultDiv += ""; @@ -1466,49 +1556,76 @@ jQuery(document).ready(function ($) { var prefix = ""; var type = ""; var button_text = ""; + var empty_fallback = ""; var hidden = "no"; + var bg_color = "#333333"; + var text_color = "#ffffff"; $.each(res.output, function (o, p) { if (q == p.key) { prefix = p.prefix; type = p.type; button_text = p.button_text; + if ("empty_fallback" in p) { + empty_fallback = p.empty_fallback; + } if ("hide" in p) { hidden = p.hide; } + if ("bg_color" in p) { + bg_color = p.bg_color; + } + if ("text_color" in p) { + text_color = p.text_color; + } } }); if (hidden == "yes") { return; } - if (type == "link_button") { + var isEmpty = !r || String(r).trim() === ""; + if (isEmpty && empty_fallback) { + r = escapeHtml(empty_fallback); + } else if (isEmpty) { + r = ""; + } else if (type == "link_button") { r = '' + - button_text + + '" target="_blank" rel="noopener" style="background-color: ' + + escapeHtml(bg_color) + + "; color: " + + escapeHtml(text_color) + + ';">' + + escapeHtml(button_text) + ""; } else if (type == "whatsapp_button") { r = '' + - button_text + + '" target="_blank" rel="noopener" style="background-color: ' + + escapeHtml(bg_color) + + "; color: " + + escapeHtml(text_color) + + ';">' + + escapeHtml(button_text) + ""; } else if (type == "image") { r = '' +
-                        q +
+                        escapeHtml(q) +
                         ''; + } else if (type == "text") { + r = escapeHtml(r); } resultDiv += '' + - prefix + + escapeHtml(prefix || "") + r + ""; }); @@ -1614,6 +1731,7 @@ jQuery(document).ready(function ($) { var prefix = ""; var type = ""; var button_text = ""; + var empty_fallback = ""; var hidden = "no"; var bg_color = "#cccccc"; var text_color = "#000000"; @@ -1622,6 +1740,9 @@ jQuery(document).ready(function ($) { prefix = p.prefix; type = p.type; button_text = p.button_text; + if ("empty_fallback" in p) { + empty_fallback = p.empty_fallback; + } if ("hide" in p) { hidden = p.hide; } @@ -1636,35 +1757,42 @@ jQuery(document).ready(function ($) { if (hidden == "yes") { return; } - if (type == "link_button") { + var isEmpty = !r || String(r).trim() === ""; + if (isEmpty && empty_fallback) { + r = escapeHtml(empty_fallback); + } else if (isEmpty) { + r = ""; + } else if (type == "link_button") { r = '' + - button_text + + escapeHtml(button_text) + ""; } else if (type == "whatsapp_button") { r = '' + - button_text + + escapeHtml(button_text) + ""; } else if (type == "image") { r = '' +
-                        q +
+                        escapeHtml(q) +
                         ''; + } else if (type == "text") { + r = escapeHtml(r); } resultDiv += `
` + - q + + escapeHtml(q) + ` ` + - prefix + + escapeHtml(prefix || "") + r + `
`; @@ -1706,11 +1834,20 @@ jQuery(document).ready(function ($) { ensureCaptchaReady($id) .then(function() { + // Double-check that reCAPTCHA token exists before proceeding + if (window.checkerRecaptcha) { + var tokenInput = this_checker.find('input[name="recaptcha_token"]'); + if (!tokenInput.val()) { + showSecurityError($id, checkerSecurity.i18n ? checkerSecurity.i18n.recaptcha_failed : "reCAPTCHA verification failed. Please try again.", false); + $this.text($this.attr("data-btn-text")).prop("disabled", false); + return; + } + } performSearch(); }) - .catch(function(err) { - console.error("Captcha refresh failed", err); - showSecurityError($id, checkerSecurity.i18n ? checkerSecurity.i18n.security_error : "Security validation failed.", false); + .catch(function() { + showSecurityError($id, checkerSecurity.i18n ? checkerSecurity.i18n.recaptcha_failed : "reCAPTCHA verification failed. Please try again.", false); + $this.text($this.attr("data-btn-text")).prop("disabled", false); }); } } diff --git a/dw-sheet-data-checker-pro.php b/dw-sheet-data-checker-pro.php index 027ca5e..545b764 100644 --- a/dw-sheet-data-checker-pro.php +++ b/dw-sheet-data-checker-pro.php @@ -2,7 +2,7 @@ /** * Plugin Name: Sheet Data Checker Pro * Description: Check data from Google Sheet with customizable filter form - * Version: 1.4.5 + * Version: 1.4.10 * Plugin URI: https://dwindi.com/sheet-data-checker * Author: Dwindi Ramadhana * Author URI: https://facebook.com/dwindi.ramadhana @@ -35,7 +35,7 @@ if ( ! defined( 'SHEET_CHECKER_PRO_BASENAME' ) ) { } if ( ! defined( 'SHEET_CHECKER_PRO_VERSION' ) ) { - define( 'SHEET_CHECKER_PRO_VERSION', '1.4.5' ); + define( 'SHEET_CHECKER_PRO_VERSION', '1.4.10' ); } if ( ! defined( 'SHEET_CHECKER_PRO_URL' ) ) { diff --git a/includes/class-Security.php b/includes/class-Security.php index 3888270..925b0a5 100644 --- a/includes/class-Security.php +++ b/includes/class-Security.php @@ -127,7 +127,7 @@ class CHECKER_SECURITY { $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; + $min_score_raw = isset($checker['security']['recaptcha']['min_score']) ? $checker['security']['recaptcha']['min_score'] : 0.3; if (is_string($min_score_raw)) { $min_score_raw = str_replace(',', '.', $min_score_raw); } @@ -199,15 +199,12 @@ class CHECKER_SECURITY { } 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.' + 'message' => "reCAPTCHA score too low ({$score}). Please try again." ]; } - - error_log("Sheet Data Checker: reCAPTCHA verification SUCCESS - Score: {$score}, Action: {$response_action}"); return [ 'success' => true, diff --git a/includes/class-Sheet-Data-Checker-Pro.php b/includes/class-Sheet-Data-Checker-Pro.php index 7a441f2..aff6f2b 100644 --- a/includes/class-Sheet-Data-Checker-Pro.php +++ b/includes/class-Sheet-Data-Checker-Pro.php @@ -504,6 +504,7 @@ class SHEET_DATA_CHECKER_PRO 'type' => isset($checker['output'][$id]['type']) ? $checker['output'][$id]['type'] : 'text', 'button_text' => isset($checker['output'][$id]['button_text']) ? $checker['output'][$id]['button_text'] : '', 'prefix' => isset($checker['output'][$id]['prefix']) ? $checker['output'][$id]['prefix'] : '', + 'empty_fallback' => isset($checker['output'][$id]['empty_fallback']) ? $checker['output'][$id]['empty_fallback'] : '', 'bg_color' => isset($checker['output'][$id]['bg_color']) ? $checker['output'][$id]['bg_color'] : '#cccccc', 'text_color' => isset($checker['output'][$id]['text_color']) ? $checker['output'][$id]['text_color'] : '#000000', 'display' => isset($checker['result']['display']) && $checker['result']['display'] == 'card' diff --git a/includes/helpers/class-Captcha-Helper.php b/includes/helpers/class-Captcha-Helper.php index 8a34129..72f4489 100644 --- a/includes/helpers/class-Captcha-Helper.php +++ b/includes/helpers/class-Captcha-Helper.php @@ -60,7 +60,8 @@ class CHECKER_CAPTCHA_HELPER { } } elseif ($recaptcha_enabled) { $site_key = $checker['security']['recaptcha']['site_key'] ?? ''; - $action = $checker['security']['recaptcha']['action'] ?? 'checker_validate'; + // Default action must match JavaScript default ('submit') to avoid score penalties + $action = $checker['security']['recaptcha']['action'] ?? 'submit'; $hide_badge = $checker['security']['recaptcha']['hide_badge'] ?? 'no'; if ($site_key) { @@ -87,101 +88,31 @@ class CHECKER_CAPTCHA_HELPER { true ); - // Pass settings to JavaScript + // Pass settings to JavaScript - let public.js handle token generation 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 + // Store reCAPTCHA settings globally for public.js to use 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 - }); - }' + });' ); } @@ -208,24 +139,16 @@ class CHECKER_CAPTCHA_HELPER { '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"); @@ -244,7 +167,6 @@ class CHECKER_CAPTCHA_HELPER { } // Render Turnstile widget - console.log("Turnstile: Initializing widget"); turnstile.render(container, { sitekey: window.checkerTurnstile.siteKey, theme: window.checkerTurnstile.theme, @@ -264,10 +186,10 @@ class CHECKER_CAPTCHA_HELPER { console.log("Turnstile: Token stored in form"); }, "error-callback": function(error) { - console.error("Turnstile error:", error); + // Silently handle errors }, "expired-callback": function() { - console.warn("Turnstile: Token expired, please complete challenge again"); + // Reset expired token var input = form.querySelector("input[name=turnstile_token]"); if (input) { input.value = ""; diff --git a/templates/editor/js-template-setting-output.php b/templates/editor/js-template-setting-output.php index 0970604..424aeb7 100644 --- a/templates/editor/js-template-setting-output.php +++ b/templates/editor/js-template-setting-output.php @@ -23,32 +23,36 @@ - {{/each}}