Files
dw-sheet-data-checker/assets/public.js

1848 lines
64 KiB
JavaScript

jQuery(document).ready(function ($) {
// Global variables for filter mode
var allDataCache = {};
var currentCheckerId = null;
// CAPTCHA token timestamps for refresh logic
var captchaTokenTimestamps = {};
/**
* Build secure AJAX data object with CAPTCHA tokens and honeypot
* @param {string} checkerId - Checker ID
* @param {object} baseData - Base data object
* @returns {object} - Data object with security fields
*/
function buildSecureAjaxData(checkerId, baseData) {
var thisChecker = $("#checker-" + checkerId);
// Add nonce
baseData.security = checkerSecurity.nonce;
// Add reCAPTCHA token if present
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;
}
/**
* Handle AJAX error responses with proper user feedback
* @param {object} xhr - XHR object
* @param {string} checkerId - Checker ID
*/
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);
// Reset CAPTCHA widget for retry
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) {
// Alternative format: {success: false, message: "..."}
showSecurityError(checkerId, response.message, false);
} else {
showSecurityError(checkerId, "An error occurred. Please try again.", false);
}
}
/**
* Show security-related error message
* @param {string} checkerId - Checker ID
* @param {string} message - Error message
* @param {boolean} requireRefresh - Whether page refresh is required
*/
function showSecurityError(checkerId, message, requireRefresh) {
var thisChecker = $("#checker-" + checkerId);
var errorHtml = '<div class="dw-checker-security-error" style="color: #dc3545; padding: 10px; margin: 10px 0; background: #f8d7da; border-radius: 4px;">';
errorHtml += '<strong>' + message + '</strong>';
if (requireRefresh) {
errorHtml += '<br><button type="button" class="dw-checker-refresh-btn" style="margin-top: 10px; padding: 5px 15px; cursor: pointer;">' +
(checkerSecurity.i18n && checkerSecurity.i18n.refresh_page ? checkerSecurity.i18n.refresh_page : 'Refresh Page') + '</button>';
}
errorHtml += '</div>';
// Remove any existing security error
thisChecker.find('.dw-checker-security-error').remove();
// Insert error before the form
thisChecker.find('.dw-checker-form').prepend(errorHtml);
// Handle refresh button click
if (requireRefresh) {
thisChecker.find('.dw-checker-refresh-btn').on('click', function() {
location.reload();
});
}
}
/**
* Reset CAPTCHA widget after error
* @param {string} checkerId - Checker ID
* @param {string} captchaType - 'recaptcha' or 'turnstile'
*/
function resetCaptchaWidget(checkerId, captchaType) {
var thisChecker = $("#checker-" + checkerId);
if (captchaType === 'turnstile' && typeof turnstile !== 'undefined') {
// Reset Turnstile widget
var turnstileContainer = thisChecker.find('.dw-checker-turnstile-container')[0];
if (turnstileContainer) {
turnstile.reset(turnstileContainer);
}
}
if (captchaType === 'recaptcha') {
// Clear reCAPTCHA token to force regeneration
thisChecker.find('input[name="recaptcha_token"]').val('').removeAttr('data-timestamp');
delete captchaTokenTimestamps[checkerId];
}
}
/**
* Check if CAPTCHA token needs refresh (expired or expiring soon)
* @param {string} checkerId - Checker ID
* @param {string} captchaType - 'recaptcha' or 'turnstile'
* @returns {boolean} - Whether token needs refresh
*/
function needsCaptchaRefresh(checkerId, captchaType) {
var thisChecker = $("#checker-" + checkerId);
var tokenInput = thisChecker.find('input[name="' + captchaType + '_token"]');
if (!tokenInput.length || !tokenInput.val()) {
return true;
}
var timestamp = tokenInput.attr('data-timestamp');
if (!timestamp) {
return true;
}
var tokenAge = Date.now() - parseInt(timestamp);
// reCAPTCHA v3 tokens expire after 2 minutes, refresh at 90 seconds
if (captchaType === 'recaptcha' && tokenAge > 90000) {
return true;
}
// Turnstile tokens expire after 5 minutes, refresh at 4 minutes
if (captchaType === 'turnstile' && tokenAge > 240000) {
return true;
}
return false;
}
/**
* Refresh reCAPTCHA token before submission
* @param {string} checkerId - Checker ID
* @returns {Promise} - Resolves when token is ready
*/
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'
}).then(function(token) {
var tokenInput = thisChecker.find('input[name="recaptcha_token"]');
if (!tokenInput.length) {
tokenInput = $('<input type="hidden" name="recaptcha_token">');
thisChecker.find('form').append(tokenInput);
}
tokenInput.val(token).attr('data-timestamp', Date.now().toString());
resolve();
}).catch(function(error) {
console.error('reCAPTCHA refresh error:', error);
reject(error);
});
});
});
}
/**
* Get AJAX URL from localized variable
* @returns {string} - AJAX URL
*/
function getAjaxUrl() {
return checkerSecurity.ajaxurl || '/wp-admin/admin-ajax.php';
}
// Initialize checker on page load
initializeChecker();
function escapeHtml(value) {
if (value === null || value === undefined) return "";
return String(value)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
function ensureCaptchaReady(checkerId) {
// If reCAPTCHA is configured, always refresh/generate a token (it will create the hidden input if missing)
if (window.checkerRecaptcha) {
return waitForRecaptcha()
.then(function() {
return refreshRecaptchaToken(checkerId);
})
.catch(function(err) {
return Promise.reject(err);
});
}
// 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");
var age = Date.now() - ts;
if (age > 240000 && typeof turnstile !== "undefined") {
var container = thisChecker.find(".dw-checker-turnstile-container")[0];
if (container) {
turnstile.reset(container);
}
turnstileInput.val("");
}
}
return Promise.resolve();
}
function waitForRecaptcha(timeoutMs = 5000) {
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"));
}
setTimeout(check, 200);
})();
});
}
function initializeChecker() {
$(".dw-checker-container").each(function () {
var checkerId = $(this).attr("id").replace("checker-", "");
var settingsEl = $("#checker-settings-" + checkerId);
if (settingsEl.length) {
var settings = JSON.parse(settingsEl.text());
currentCheckerId = checkerId;
// Handle URL parameters
if (settings.url_params_enabled === "yes") {
handleUrlParameters(checkerId, settings);
}
// Handle initial display mode
// Only load data if initial_display is 'all' or 'limited', not 'hidden'
if (settings.initial_display === "all" || settings.initial_display === "limited") {
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);
});
}
});
}
// Get URL parameters
function getUrlParams() {
var params = {};
var queryString = window.location.search.substring(1);
if (!queryString) return params;
var pairs = queryString.split("&");
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i].split("=");
if (pair[0]) {
params[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || "");
}
}
return params;
}
// Handle URL parameters
function handleUrlParameters(checkerId, settings) {
var urlParams = getUrlParams();
if (Object.keys(urlParams).length === 0) return;
var thisChecker = $("#checker-" + checkerId);
var filled = false;
// Fill form fields from URL params
thisChecker.find(".dw-checker-inputs").each(function () {
var fieldName = $(this).data("kolom");
if (urlParams[fieldName]) {
$(this).val(urlParams[fieldName]);
filled = true;
}
});
// Auto-submit if enabled and fields were filled
if (filled && settings.url_params_auto_search === "yes") {
setTimeout(function () {
thisChecker.find(".search-button").trigger("click");
}, 500);
}
}
// Load all data for show all mode
function loadAllData(checkerId, settings, isInitialLoad) {
var limit =
settings.initial_display === "limited" ? 10 : settings.max_records;
var thisChecker = $("#checker-" + checkerId);
// Build secure AJAX data using helper function
var ajaxData = buildSecureAjaxData(checkerId, {
action: "checker_load_all_data",
checker_id: checkerId,
limit: limit,
initial_load: isInitialLoad ? "yes" : "no"
});
$.ajax({
type: "post",
url: getAjaxUrl(),
data: ajaxData,
beforeSend: function () {
showLoadingState(checkerId);
},
success: function (res) {
if (res.success === false) {
// Handle error response from wp_send_json_error
handleAjaxError({ responseJSON: res }, checkerId);
return;
}
if (res.count > 0) {
allDataCache[checkerId] = res;
renderResults(checkerId, res);
// Enable filter mode if configured
if (settings.filter_mode === "filter") {
enableFilterMode(checkerId);
}
} else {
showEmptyState(checkerId);
}
},
error: function (xhr) {
handleAjaxError(xhr, checkerId);
},
});
}
// Enable real-time filter mode
function enableFilterMode(checkerId) {
var thisChecker = $("#checker-" + checkerId);
var allData = allDataCache[checkerId];
if (!allData) return;
// Listen to input changes
thisChecker.find(".dw-checker-inputs").on("input", function () {
var filters = {};
var hasFilters = false;
// Collect all filter values
thisChecker.find(".dw-checker-inputs").each(function () {
var field = $(this).data("kolom");
var value = $(this).val();
if (value) {
filters[field] = value.toLowerCase();
hasFilters = true;
}
});
// If no filters, show all data
if (!hasFilters) {
renderResults(checkerId, allData);
return;
}
// Filter data client-side
var filtered = allData.rows.filter(function (row) {
var match = true;
for (var field in filters) {
var rowValue = (row[field] || "").toLowerCase();
var filterValue = filters[field];
// Check if contains (you can make this configurable)
if (rowValue.indexOf(filterValue) === -1) {
match = false;
break;
}
}
return match;
});
// Update display with filtered results
var filteredRes = {
count: filtered.length,
rows: filtered,
settings: allData.settings,
output: allData.output,
};
if (filtered.length > 0) {
renderResults(checkerId, filteredRes);
} else {
showEmptyState(checkerId, "No results match your filters");
}
});
// Hide search button in filter mode
thisChecker.find(".search-button").hide();
}
// Show loading state
function showLoadingState(checkerId) {
var thisChecker = $("#checker-" + checkerId);
thisChecker.find(".dw-checker-results").html(`
<div class="dw-checker-loading" style="text-align: center; padding: 2rem;">
<div class="spinner-border" role="status" style="width: 3rem; height: 3rem; border-width: 0.3rem;">
<span class="visually-hidden">Loading...</span>
</div>
<p style="margin-top: 1rem; color: #666;">Loading data...</p>
</div>
`);
}
// Show empty state
function showEmptyState(checkerId, message) {
message = message || "No results found";
var thisChecker = $("#checker-" + checkerId);
thisChecker.find(".dw-checker-results").html(
`
<div class="dw-checker-empty-state" style="text-align: center; padding: 3rem 1rem;">
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-bottom: 1rem; color: #ccc;">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
<h3 style="color: #666; margin-bottom: 0.5rem;">` +
message +
`</h3>
<p style="color: #999;">Try adjusting your search criteria</p>
</div>
`,
);
}
// Show error state
function showErrorState(checkerId, message) {
var thisChecker = $("#checker-" + checkerId);
thisChecker.find(".dw-checker-results").html(
`
<div class="dw-checker-error-state" style="text-align: center; padding: 2rem; color: #dc3545;">
<p><strong>Error:</strong> ` +
message +
`</p>
</div>
`,
);
}
// Render results (unified function for all display types)
function renderResults(checkerId, res) {
var thisChecker = $("#checker-" + checkerId);
var displayType = res.settings.display;
// Show result container
thisChecker.find(".dw-checker-result").show();
thisChecker.find(".dw-checker-form").hide();
// Render based on display type
if (displayType === "vertical-table") {
renderVerticalTable(checkerId, res);
} else if (displayType === "standard-table") {
renderStandardTable(checkerId, res);
} else if (displayType === "div") {
renderDivDisplay(checkerId, res);
} else if (displayType === "cards") {
renderCardDisplay(checkerId, res);
}
}
// Helper function to get output setting by key
function getOutputSetting(output, key) {
// Convert output object to array if needed
if (Array.isArray(output)) {
return output.find(function (o) {
return o.key === key;
});
} else {
// output is an object, find by matching key property
for (var prop in output) {
if (output[prop].key === key) {
return output[prop];
}
}
}
return null;
}
// Render vertical table display
function renderVerticalTable(checkerId, res) {
var thisChecker = $("#checker-" + checkerId);
var resultDiv = "";
var perPage = 1; // One record per page for vertical table
var totalPages = Math.ceil(res.count / perPage);
$.each(res.rows, function (index, row) {
var isFirst = index === 0;
resultDiv +=
'<table class="dw-checker-result-container" data-pagination="' +
index +
'" style="' +
(isFirst ? "" : "display: none;") +
"border-color: " +
res.settings.divider +
"; border-width: " +
res.settings.divider_width +
'px;"><tbody>';
$.each(row, function (q, r) {
var id = q.replace(/\s/g, "_").replace(/\./g, "_").toLowerCase();
var outputSetting = getOutputSetting(res.output, q);
if (!outputSetting || outputSetting.hide === "yes") return;
var prefix = escapeHtml(outputSetting.prefix || "");
var type = outputSetting.type || "text";
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 safeQ = escapeHtml(q);
var safeVal = escapeHtml(r);
if (type == "link_button") {
safeVal =
'<a href="' +
safeVal +
'" class="button dw-checker-value-button dw-checker-btn-' +
id +
'" target="_blank" rel="noopener" style="background-color: ' +
bg_color +
"; color: " +
text_color +
';">' +
button_text +
"</a>";
} else if (type == "whatsapp_button") {
safeVal =
'<a href="https://wa.me/' +
safeVal +
'" class="button dw-checker-value-button dw-checker-btn-' +
id +
'" target="_blank" rel="noopener" style="background-color: ' +
bg_color +
"; color: " +
text_color +
';">' +
button_text +
"</a>";
} else if (type == "image") {
safeVal =
'<img src="' +
safeVal +
'" class="dw-checker-image dw-checker-img-' +
id +
'" style="max-width: 150px; height: auto; cursor: pointer;" onclick="openImageLightbox(this)" data-fullsize="' +
safeVal +
'" alt="' +
safeQ +
'" />';
}
resultDiv += "<tr>";
resultDiv +=
'<th style="border-color: ' +
res.settings.divider +
"; border-width: " +
res.settings.divider_width +
'px;"><span class="dw-checker-result-header" style="color:' +
res.settings.header +
'">' +
safeQ +
"</span></th>";
resultDiv +=
'<td style="border-color: ' +
res.settings.divider +
"; border-width: " +
res.settings.divider_width +
'px;"><span class="dw-checker-result-value" style="color:' +
res.settings.value +
'">' +
prefix +
safeVal +
"</span></td>";
resultDiv += "</tr>";
});
resultDiv += "</tbody></table>";
});
// Add enhanced pagination
if (totalPages > 1) {
resultDiv += createEnhancedPagination(totalPages, 1);
}
thisChecker.find(".dw-checker-results").html(resultDiv);
}
// Render standard table display
function renderStandardTable(checkerId, res) {
var thisChecker = $("#checker-" + checkerId);
var resultDiv =
'<table class="dw-checker-result-container display" style="width:100%"><thead><tr>';
// Headers
if (res.rows.length > 0) {
$.each(res.rows[0], function (q, r) {
var outputSetting = getOutputSetting(res.output, q);
if (!outputSetting || outputSetting.hide === "yes") return;
resultDiv +=
'<th style="color:' + res.settings.header + '">' + escapeHtml(q) + "</th>";
});
}
resultDiv += "</tr></thead><tbody>";
// Rows
$.each(res.rows, function (index, row) {
resultDiv += "<tr>";
$.each(row, function (q, r) {
var id = q.replace(/\s/g, "_").replace(/\./g, "_").toLowerCase();
var outputSetting = getOutputSetting(res.output, q);
if (!outputSetting || outputSetting.hide === "yes") return;
var prefix = escapeHtml(outputSetting.prefix || "");
var type = outputSetting.type || "text";
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 safeVal = escapeHtml(r);
if (type == "link_button") {
safeVal =
'<a href="' +
safeVal +
'" class="button dw-checker-value-button dw-checker-btn-' +
id +
'" target="_blank" rel="noopener" style="background-color: ' +
bg_color +
"; color: " +
text_color +
';">' +
button_text +
"</a>";
} else if (type == "whatsapp_button") {
safeVal =
'<a href="https://wa.me/' +
safeVal +
'" class="button dw-checker-value-button dw-checker-btn-' +
id +
'" target="_blank" rel="noopener" style="background-color: ' +
bg_color +
"; color: " +
text_color +
';">' +
button_text +
"</a>";
} else if (type == "image") {
safeVal =
'<img src="' +
safeVal +
'" class="dw-checker-image dw-checker-img-' +
id +
'" style="max-width: 150px; height: auto; cursor: pointer;" onclick="openImageLightbox(this)" data-fullsize="' +
safeVal +
'" alt="' +
escapeHtml(q) +
'" />';
}
resultDiv +=
'<td style="color:' +
res.settings.value +
'">' +
prefix +
safeVal +
"</td>";
});
resultDiv += "</tr>";
});
resultDiv += "</tbody></table>";
thisChecker.find(".dw-checker-results").html(resultDiv);
// Initialize DataTable
setTimeout(function () {
var tbl = thisChecker.find(".dw-checker-result-container");
if (!tbl.length || typeof tbl.DataTable !== "function") {
return;
}
var dt = tbl.DataTable({
paging: true,
pageLength: 10,
searching: false, // Hide search input
info: false, // Hide "Showing X of Y entries"
scrollX: false, // Disable horizontal scrolling by default
responsive: true,
autoWidth: true,
deferRender: true,
columnDefs: [
{
targets: "th",
className: "dt-left",
width: "auto",
},
{
targets: "td",
className: "dt-left",
width: "auto",
},
],
drawCallback: function (settings) {
var api = this.api();
var tableObj = api.table();
if (!tableObj) {
return;
}
var containerEl = tableObj.container();
var tableWidth = containerEl.clientWidth || containerEl.getBoundingClientRect().width;
var contentWidth = tableObj.node().scrollWidth;
if (contentWidth > tableWidth) {
containerEl.style.overflowX = "auto";
} else {
containerEl.style.overflowX = "hidden";
}
},
});
// Adjust columns once
dt.columns.adjust();
}, 100);
}
// Render div display
function renderDivDisplay(checkerId, res) {
var thisChecker = $("#checker-" + checkerId);
var resultDiv = "";
var perPage = 1;
var totalPages = Math.ceil(res.count / perPage);
$.each(res.rows, function (index, row) {
var isFirst = index === 0;
resultDiv +=
'<div class="dw-checker-result-container" data-pagination="' +
index +
'" style="' +
(isFirst ? "" : "display: none;") +
'">';
$.each(row, function (q, r) {
var id = q.replace(/\s/g, "_").replace(/\./g, "_").toLowerCase();
var outputSetting = getOutputSetting(res.output, q);
if (!outputSetting || outputSetting.hide === "yes") return;
var prefix = escapeHtml(outputSetting.prefix || "");
var type = outputSetting.type || "text";
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 safeQ = escapeHtml(q);
var safeVal = escapeHtml(r);
if (type == "link_button") {
safeVal =
'<a href="' +
safeVal +
'" class="button dw-checker-value-button dw-checker-btn-' +
id +
'" target="_blank" rel="noopener" style="background-color: ' +
bg_color +
"; color: " +
text_color +
';">' +
button_text +
"</a>";
} else if (type == "whatsapp_button") {
safeVal =
'<a href="https://wa.me/' +
safeVal +
'" class="button dw-checker-value-button dw-checker-btn-' +
id +
'" target="_blank" rel="noopener" style="background-color: ' +
bg_color +
"; color: " +
text_color +
';">' +
button_text +
"</a>";
} else if (type == "image") {
safeVal =
'<img src="' +
safeVal +
'" class="dw-checker-image dw-checker-img-' +
id +
'" style="max-width: 150px; height: auto; cursor: pointer;" onclick="openImageLightbox(this)" data-fullsize="' +
safeVal +
'" alt="' +
safeQ +
'" />';
}
resultDiv +=
'<div class="dw-checker-result-div" style="border-bottom-color: ' +
res.settings.divider +
"; border-bottom-width: " +
res.settings.divider_width +
'px;">';
resultDiv +=
'<div class="result-header"><span class="dw-checker-result-header" style="color:' +
res.settings.header +
';">' +
safeQ +
"</span></div>";
resultDiv +=
'<div class="result-value"><span class="dw-checker-result-value" style="color:' +
res.settings.value +
';">' +
prefix +
safeVal +
"</span></div>";
resultDiv += "</div>";
});
resultDiv += "</div>";
});
// Add enhanced pagination
if (totalPages > 1) {
resultDiv += createEnhancedPagination(totalPages, 1);
}
thisChecker.find(".dw-checker-results").html(resultDiv);
}
// Render card display
function renderCardDisplay(checkerId, res) {
var thisChecker = $("#checker-" + checkerId);
var resultDiv = "";
var perPage = 1;
var totalPages = Math.ceil(res.count / perPage);
$.each(res.rows, function (index, row) {
var isFirst = index === 0;
resultDiv +=
'<div class="dw-checker-result-container dw-checker-card-container" data-pagination="' +
index +
'" style="' +
(isFirst ? "" : "display: none;") +
'">';
$.each(row, function (q, r) {
var id = q.replace(/\s/g, "_").replace(/\./g, "_").toLowerCase();
var outputSetting = getOutputSetting(res.output, q);
if (!outputSetting || outputSetting.hide === "yes") return;
var prefix = escapeHtml(outputSetting.prefix || "");
var type = outputSetting.type || "text";
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 safeQ = escapeHtml(q);
var safeVal = escapeHtml(r);
if (type == "link_button") {
safeVal =
'<a href="' +
safeVal +
'" class="button dw-checker-value-button dw-checker-btn-' +
id +
'" target="_blank" rel="noopener" style="background-color: ' +
bg_color +
"; color: " +
text_color +
';">' +
button_text +
"</a>";
} else if (type == "whatsapp_button") {
safeVal =
'<a href="https://wa.me/' +
safeVal +
'" class="button dw-checker-value-button dw-checker-btn-' +
id +
'" target="_blank" rel="noopener" style="background-color: ' +
bg_color +
"; color: " +
text_color +
';">' +
button_text +
"</a>";
} else if (type == "image") {
safeVal =
'<img src="' +
safeVal +
'" class="dw-checker-image dw-checker-img-' +
id +
'" style="max-width: 100%; height: auto; cursor: pointer;" onclick="openImageLightbox(this)" data-fullsize="' +
safeVal +
'" alt="' +
safeQ +
'" />';
}
resultDiv +=
'<div class="dw-checker-single-card" style="background-color: ' +
bg_color +
"; color: " +
text_color +
"; border-color: " +
res.settings.divider +
"; border-width: " +
res.settings.divider_width +
'px;">';
resultDiv +=
'<span class="dw-checker-card-header" style="color:' +
text_color +
'">' +
safeQ +
"</span>";
resultDiv +=
'<span class="dw-checker-card-value" style="color:' +
text_color +
'">' +
prefix +
safeVal +
"</span>";
resultDiv += "</div>";
});
resultDiv += "</div>";
});
// Add enhanced pagination
if (totalPages > 1) {
resultDiv += createEnhancedPagination(totalPages, 1);
}
thisChecker.find(".dw-checker-results").html(resultDiv);
}
// Create enhanced pagination with Previous/Next buttons
function createEnhancedPagination(totalPages, currentPage) {
var html =
'<div class="dw-checker-pagination" style="margin-top: 1rem; display: flex; gap: 0.5rem; justify-content: center; align-items: center; flex-wrap: wrap;">';
// Previous button
html +=
'<button class="pagination-btn pagination-prev" data-page="' +
(currentPage - 1) +
'" ' +
(currentPage === 1 ? "disabled" : "") +
' style="padding: 0.5rem 1rem; border: 1px solid #ddd; background: #fff; cursor: pointer;">Previous</button>';
// Page numbers (show max 5 pages)
var startPage = Math.max(1, currentPage - 2);
var endPage = Math.min(totalPages, startPage + 4);
if (endPage - startPage < 4) {
startPage = Math.max(1, endPage - 4);
}
for (var i = startPage; i <= endPage; i++) {
var active =
i === currentPage ? ' style="background: #333; color: #fff;"' : "";
html +=
'<button class="pagination-btn pagination-page" data-page="' +
i +
'"' +
active +
' style="padding: 0.5rem 1rem; border: 1px solid #ddd; background: #fff; cursor: pointer;">' +
i +
"</button>";
}
// Next button
html +=
'<button class="pagination-btn pagination-next" data-page="' +
(currentPage + 1) +
'" ' +
(currentPage === totalPages ? "disabled" : "") +
' style="padding: 0.5rem 1rem; border: 1px solid #ddd; background: #fff; cursor: pointer;">Next</button>';
html += "</div>";
return html;
}
// Handle pagination clicks (delegated event)
$(document).on("click", ".pagination-btn", function () {
if ($(this).prop("disabled")) return;
var page = parseInt($(this).data("page"));
var containers = $(this)
.closest(".dw-checker-results")
.find(".dw-checker-result-container");
// Hide all containers
containers.hide();
// Show selected page (0-indexed)
containers.eq(page - 1).show();
// Update pagination buttons
var totalPages = containers.length;
var newPagination = createEnhancedPagination(totalPages, page);
$(this).closest(".dw-checker-pagination").replaceWith(newPagination);
});
$(".dw-checker-inputs").on("input change blur", function () {
$(this).siblings(".dw-checker-input-validator").remove();
if ($(this).val().length == 0) {
$(this)
.parents(".dw-checker-field")
.append(
'<div class="dw-checker-input-validator">' +
$(this).data("kolom") +
" is required!</div>",
);
}
});
$(".search-button").on("click", function (e) {
e.preventDefault();
var $this = $(this);
var $id = $this.data("checker");
var this_checker = $("#checker-" + $id);
var inputs = this_checker.find(".dw-checker-inputs");
var inputs_count = inputs.length;
var validator = [];
var submission = [];
if (inputs.length > 0) {
$.each(inputs, function (m, n) {
if ($(n).val().length == 0) {
$(n)
.parents(".dw-checker-field")
.append(
'<div class="dw-checker-input-validator">' +
$(n).data("kolom") +
" is required!</div>",
);
$(n).addClass("dw-checker-input-validator-border");
return false;
}
validator.push({
kolom: $(n).data("kolom"),
value: $(n).val(),
});
submission.push($(n).val());
});
var validator_count = validator.length;
if (validator_count == inputs_count) {
// Remove any existing security errors
this_checker.find('.dw-checker-security-error').remove();
// Function to perform the actual AJAX request
var performSearch = function() {
// Build secure AJAX data using helper function
var ajaxData = buildSecureAjaxData($id, {
action: "checker_public_validation",
checker_id: $id,
validate: validator,
});
$.ajax({
type: "post",
url: getAjaxUrl(),
data: ajaxData,
beforeSend: function () {
$this.attr("data-text", $(this).text());
$this.text("Searching...").prop("disabled", true);
this_checker
.find(".dw-checker-result")
.find(".dw-checker-title")
.html("");
this_checker
.find(".dw-checker-result")
.find(".dw-checker-description")
.html("");
this_checker
.find(".dw-checker-result")
.find(".dw-checker-results")
.html("");
this_checker
.find(".dw-checker-result")
.find(".dw-checker-bottom-results")
.html("");
},
success: function (res) {
console.log(res);
$this.text($this.attr("data-btn-text")).prop("disabled", false);
// Handle error response from wp_send_json_error
if (res.success === false) {
handleAjaxError({ responseJSON: res }, $id);
return;
}
var title = "";
var desc = "";
if (res.count == 0) {
title = "Not Found!";
desc = "Recheck your request and click search again";
} else {
var records = "record";
if (res.count > 1) {
records = "records";
}
title = res.count + " " + records + " found";
desc =
"You got " +
res.count +
" " +
records +
" matching <b>" +
submission.join(" - ") +
"</b>";
}
$this.text($(this).attr("data-text")).prop("disabled", false);
this_checker.find(".dw-checker-form").hide();
this_checker
.find(".dw-checker-result")
.find(".dw-checker-title")
.html(title);
this_checker
.find(".dw-checker-result")
.find(".dw-checker-description")
.html(desc);
this_checker.find(".dw-checker-result").show();
if (res.rows.length > 0) {
var resultDiv = "";
var pagination = "";
if (res.rows.length > 1) {
resultDiv += '<div class="dw-checker-result-pagination">';
var list = [];
for (var i = 1; i <= res.rows.length; i++) {
list.push(i);
}
$.each(list, function (r, o) {
var active = "";
if (r == 0) {
active = " active";
}
resultDiv +=
'<button class="dw-checker-result-pagination-button' +
active +
'" data-target-pagination="' +
o +
'">' +
o +
"</button>";
});
resultDiv += "</div>";
}
if (res.settings.display == "vertical-table") {
$.each(res.rows, function (index, item) {
resultData = item;
if (index == 0) {
resultDiv +=
'<table class="dw-checker-result-container" data-pagination="' +
index +
'" style="border-color: ' +
res.settings.divider +
"; border-width: " +
res.settings.divider_width +
'px;"><tbody>';
} else {
resultDiv +=
'<table class="dw-checker-result-container" data-pagination="' +
index +
'" style="display: none; border-color: ' +
res.settings.divider +
"; border-width: " +
res.settings.divider_width +
'px;"><tbody>';
}
var header_color = res.settings.header;
var value_color = res.settings.value;
$.each(item, function (q, r) {
var id = q
.replace(" ", "_")
.replace(".", "_")
.toLowerCase();
var prefix = "";
var type = "";
var button_text = "";
var hidden = "no";
$.each(res.output, function (o, p) {
if (q == p.key) {
prefix = p.prefix;
type = p.type;
button_text = p.button_text;
if ("hide" in p) {
hidden = p.hide;
}
}
});
if (hidden == "yes") {
return;
}
if (type == "link_button") {
r =
'<a href="' +
r +
'" class="button dw-checker-value-button dw-checker-btn-' +
id +
'" target="_blank" rel="noopener">' +
button_text +
"</a>";
} else if (type == "whatsapp_button") {
r =
'<a href="https://wa.me/' +
r +
'" class="button dw-checker-value-button dw-checker-btn-' +
id +
'" target="_blank" rel="noopener">' +
button_text +
"</a>";
} else if (type == "image") {
r =
'<img src="' +
r +
'" class="dw-checker-image dw-checker-img-' +
id +
'" style="max-width: 150px; height: auto; cursor: pointer;" onclick="openImageLightbox(this)" data-fullsize="' +
r +
'" alt="' +
q +
'" />';
}
resultDiv += "<tr>";
resultDiv +=
'<th style="border-color: ' +
res.settings.divider +
"; border-width: " +
res.settings.divider_width +
'px;"><span class="dw-checker-result-header" style="color:' +
header_color +
'">' +
q +
"</span></th>";
resultDiv +=
'<td style="border-color: ' +
res.settings.divider +
"; border-width: " +
res.settings.divider_width +
'px;"><span class="dw-checker-result-value" style="color:' +
value_color +
'">' +
prefix +
r +
"</span></td>";
resultDiv += "</tr>";
});
resultDiv += "</tbody></table>";
});
this_checker
.find(".dw-checker-result")
.find(".dw-checker-results")
.html(resultDiv);
} else if (res.settings.display == "div") {
$.each(res.rows, function (index, item) {
resultData = item;
var header_color = res.settings.header;
var value_color = res.settings.value;
// Create container div for this row
if (index == 0) {
resultDiv +=
'<div class="dw-checker-result-container" data-pagination="' +
index +
'">';
} else {
resultDiv +=
'<div class="dw-checker-result-container" data-pagination="' +
index +
'" style="display: none;">';
}
// Loop through each field in the row
$.each(item, function (q, r) {
var id = q
.replace(" ", "_")
.replace(".", "_")
.toLowerCase();
var prefix = "";
var type = "";
var button_text = "";
var hidden = "no";
$.each(res.output, function (o, p) {
if (q == p.key) {
prefix = p.prefix;
type = p.type;
button_text = p.button_text;
if ("hide" in p) {
hidden = p.hide;
}
}
});
if (hidden == "yes") {
return;
}
if (type == "link_button") {
r =
'<a href="' +
r +
'" class="button dw-checker-value-button dw-checker-btn-' +
id +
'" target="_blank" rel="noopener">' +
button_text +
"</a>";
} else if (type == "whatsapp_button") {
r =
'<a href="https://wa.me/' +
r +
'" class="button dw-checker-value-button dw-checker-btn-' +
id +
'" target="_blank" rel="noopener">' +
button_text +
"</a>";
} else if (type == "image") {
r =
'<img src="' +
r +
'" class="dw-checker-image dw-checker-img-' +
id +
'" style="max-width: 150px; height: auto; cursor: pointer;" onclick="openImageLightbox(this)" data-fullsize="' +
r +
'" alt="' +
q +
'" />';
}
resultDiv +=
'<div class="dw-checker-result-div" style="border-bottom-color: ' +
res.settings.divider +
"; border-bottom-width: " +
res.settings.divider_width +
'px;">';
resultDiv +=
'<div class="result-header"><span class="dw-checker-result-header" style="color:' +
header_color +
';">' +
q +
"</span></div>";
resultDiv +=
'<div class="result-value"><span class="dw-checker-result-value" style="color:' +
value_color +
';">' +
prefix +
r +
"</span></div>";
resultDiv += "</div>";
});
// Close container div for this row
resultDiv += "</div>";
});
this_checker
.find(".dw-checker-result")
.find(".dw-checker-results")
.html(resultDiv);
} else if (res.settings.display == "standard-table") {
this_checker.find(".dw-checker-divider:nth-child(3)").hide();
resultDiv =
'<table class="dw-checker-result-container display nowrap">';
var header_color = res.settings.header;
var value_color = res.settings.value;
resultDiv += "<thead><tr>";
$.each(res.output, function (header, value) {
var hidden = "no";
if ("hide" in value) {
hidden = value.hide;
}
if (hidden !== "yes") {
resultDiv += "<th>" + value.key + "</th>";
}
});
resultDiv += "</tr><thead>";
resultDiv += "<tbody>";
$.each(res.rows, function (index, item) {
resultData = item;
resultDiv += "<tr>";
$.each(item, function (q, r) {
var id = q
.replace(" ", "_")
.replace(".", "_")
.toLowerCase();
var prefix = "";
var type = "";
var button_text = "";
var hidden = "no";
$.each(res.output, function (o, p) {
if (q == p.key) {
prefix = p.prefix;
type = p.type;
button_text = p.button_text;
if ("hide" in p) {
hidden = p.hide;
}
}
});
if (hidden == "yes") {
return;
}
if (type == "link_button") {
r =
'<a href="' +
r +
'" class="button dw-checker-value-button dw-checker-btn-' +
id +
'" target="_blank" rel="noopener">' +
button_text +
"</a>";
} else if (type == "whatsapp_button") {
r =
'<a href="https://wa.me/' +
r +
'" class="button dw-checker-value-button dw-checker-btn-' +
id +
'" target="_blank" rel="noopener">' +
button_text +
"</a>";
} else if (type == "image") {
r =
'<img src="' +
r +
'" class="dw-checker-image dw-checker-img-' +
id +
'" style="max-width: 150px; height: auto; cursor: pointer;" onclick="openImageLightbox(this)" data-fullsize="' +
r +
'" alt="' +
q +
'" />';
}
resultDiv +=
'<td style="border-color: ' +
res.settings.divider +
"; border-width: " +
res.settings.divider_width +
'px;"><span class="dw-checker-result-value" style="color:' +
value_color +
'">' +
prefix +
r +
"</span></td>";
});
resultDiv += "</tr>";
});
resultDiv += "</tbody>";
resultDiv += "</table>";
this_checker.next(".dw-checker-bottom-results").html(resultDiv);
var stdTable = this_checker
.next(".dw-checker-bottom-results")
.find("table.dw-checker-result-container");
if (stdTable.length && typeof stdTable.DataTable === "function") {
stdTable.DataTable({
paging: true,
pageLength: 10,
searching: false, // Hide search input
info: false, // Hide "Showing X of Y entries"
scrollX: false, // Disable horizontal scrolling by default
responsive: true,
autoWidth: true,
deferRender: true,
columnDefs: [
{
targets: "th",
className: "dt-left",
width: "auto",
},
{
targets: "td",
className: "dt-left",
width: "auto",
},
],
drawCallback: function () {
var api = this.api();
var tableObj = api.table();
if (!tableObj) {
return;
}
var containerEl = tableObj.container();
var tableWidth =
containerEl.clientWidth ||
containerEl.getBoundingClientRect().width;
var contentWidth = tableObj.node().scrollWidth;
if (contentWidth > tableWidth) {
containerEl.style.overflowX = "auto";
} else {
containerEl.style.overflowX = "hidden";
}
},
}).columns.adjust();
}
} else if (res.settings.display == "card") {
this_checker.find(".dw-checker-divider:nth-child(3)").hide();
$.each(res.rows, function (index, item) {
resultData = item;
resultDiv +=
`
<style>
.dw-checker-card-container {
grid-template-columns: repeat(` +
res.settings.column +
`, 1fr);
}
@media (max-width: 1280px){
.dw-checker-card-container {
grid-template-columns: repeat(3, 1fr);
}
}
@media (max-width: 820px){
.dw-checker-card-container {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 482px){
.dw-checker-card-container {
grid-template-columns: repeat(1, 1fr);
}
}
</style>
`;
resultDiv +=
'<div class="dw-checker-result-container dw-checker-card-container" data-pagination="' +
index +
'" style="display:none;">';
$.each(item, function (q, r) {
var id = q
.replace(" ", "_")
.replace(".", "_")
.toLowerCase();
var prefix = "";
var type = "";
var button_text = "";
var hidden = "no";
var bg_color = "#cccccc";
var text_color = "#000000";
$.each(res.output, function (o, p) {
if (q == p.key) {
prefix = p.prefix;
type = p.type;
button_text = p.button_text;
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") {
r =
'<a href="' +
r +
'" class="button dw-checker-value-button dw-checker-btn-' +
id +
'" target="_blank" rel="noopener">' +
button_text +
"</a>";
} else if (type == "whatsapp_button") {
r =
'<a href="https://wa.me/' +
r +
'" class="button dw-checker-value-button dw-checker-btn-' +
id +
'" target="_blank" rel="noopener">' +
button_text +
"</a>";
} else if (type == "image") {
r =
'<img src="' +
r +
'" class="dw-checker-image dw-checker-img-' +
id +
'" style="max-width: 100%; height: auto; cursor: pointer;" onclick="openImageLightbox(this)" data-fullsize="' +
r +
'" alt="' +
q +
'" />';
}
resultDiv +=
`<div class="dw-checker-single-card" style="background-color: ` +
bg_color +
`; color: ` +
text_color +
`; border-color: ` +
res.settings.divider +
`; border-width: ` +
res.settings.divider_width +
`px;">
<span class="dw-checker-card-header" style="color:` +
text_color +
`">` +
q +
`</span>
<span class="dw-checker-card-value" style="color:` +
text_color +
`">` +
prefix +
r +
`</span>
</div>`;
});
resultDiv += "</div>";
this_checker
.next(".dw-checker-bottom-results")
.html(resultDiv);
});
}
}
},
error: function (xhr) {
$this.text($this.attr("data-btn-text")).prop("disabled", false);
handleAjaxError(xhr, $id);
}
});
}; // End of performSearch function
ensureCaptchaReady($id)
.then(function() {
performSearch();
})
.catch(function(err) {
console.error("Captcha refresh failed", err);
showSecurityError($id, checkerSecurity.i18n ? checkerSecurity.i18n.security_error : "Security validation failed.", false);
});
}
}
});
$(document).on("click", ".dw-checker-result-pagination-button", function (e) {
e.preventDefault();
var container = $(this).data("target-pagination");
var page = container - 1;
$(".dw-checker-result-pagination-button").removeClass("active");
$(this).addClass("active");
$(".dw-checker-result-container").hide();
$("[data-pagination=" + page + "]").show();
});
$(".back-button").on("click", function () {
var checker_id = $(this).data("checker");
$("#checker-" + checker_id)
.find(".dw-checker-result")
.hide();
$("#checker-" + checker_id)
.find(".dw-checker-result")
.find(".dw-checker-title")
.html("");
$("#checker-" + checker_id)
.find(".dw-checker-result")
.find(".dw-checker-description")
.html("");
$("#checker-" + checker_id)
.find(".dw-checker-result")
.find(".dw-checker-results")
.html("");
$("#checker-" + checker_id)
.find(".dw-checker-inputs")
.val("");
$("#checker-" + checker_id)
.find(".dw-checker-form")
.show();
$("#checker-" + checker_id)
.find(".dw-checker-divider")
.show();
});
// Frontend clear cache button handler
$(document).on("click", ".dw-checker-clear-cache-btn", function () {
var btn = $(this);
var checkerId = btn.data("checker");
var originalText = btn.html();
btn.prop("disabled", true).html('<span class="dashicons dashicons-update" style="animation: spin 1s linear infinite; vertical-align: middle;"></span> ' +
(checkerSecurity.i18n && checkerSecurity.i18n.refreshing ? checkerSecurity.i18n.refreshing : 'Refreshing...'));
$.ajax({
url: getAjaxUrl(),
type: "POST",
data: {
action: "checker_clear_cache",
checker_id: checkerId,
security: checkerSecurity.nonce
},
success: function (response) {
if (response.success) {
// Show success message briefly
btn.html('<span class="dashicons dashicons-yes" style="vertical-align: middle;"></span> ' +
(response.data.message || 'Refreshed!'));
// Reset button after 2 seconds
setTimeout(function () {
btn.prop("disabled", false).html(originalText);
}, 2000);
} else {
btn.html('<span class="dashicons dashicons-warning" style="vertical-align: middle;"></span> Failed');
setTimeout(function () {
btn.prop("disabled", false).html(originalText);
}, 2000);
}
},
error: function () {
btn.html('<span class="dashicons dashicons-warning" style="vertical-align: middle;"></span> Error');
setTimeout(function () {
btn.prop("disabled", false).html(originalText);
}, 2000);
}
});
});
// Add CSS animation for spinning icon
if (!document.getElementById('dw-checker-spin-animation')) {
var style = document.createElement('style');
style.id = 'dw-checker-spin-animation';
style.textContent = '@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }';
document.head.appendChild(style);
}
});
// Image Lightbox Function
function openImageLightbox(img) {
var fullsizeUrl = img.getAttribute("data-fullsize");
var alt = img.getAttribute("alt");
// Create lightbox overlay
var lightbox = document.createElement("div");
lightbox.id = "dw-checker-lightbox";
lightbox.style.cssText =
"position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);z-index:99999;display:flex;align-items:center;justify-content:center;cursor:pointer;";
// Create image element
var lightboxImg = document.createElement("img");
lightboxImg.src = fullsizeUrl;
lightboxImg.alt = alt;
lightboxImg.style.cssText =
"max-width:90%;max-height:90%;object-fit:contain;";
// Create close button
var closeBtn = document.createElement("span");
closeBtn.innerHTML = "&times;";
closeBtn.style.cssText =
"position:absolute;top:20px;right:40px;color:#fff;font-size:40px;font-weight:bold;cursor:pointer;";
// Append elements
lightbox.appendChild(lightboxImg);
lightbox.appendChild(closeBtn);
document.body.appendChild(lightbox);
// Close on click
lightbox.onclick = function () {
document.body.removeChild(lightbox);
};
// Prevent image click from closing
lightboxImg.onclick = function (e) {
e.stopPropagation();
};
}