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:
213
templates/editor/common/handlebars-templates.php
Normal file
213
templates/editor/common/handlebars-templates.php
Normal file
@@ -0,0 +1,213 @@
|
||||
<!-- Handlebars Template for Fields -->
|
||||
<script id="fields-template" type="text/x-handlebars-template">
|
||||
{{#each fields}}
|
||||
<div class="dw-checker-field">
|
||||
<label for="{{fieldId}}" style="color: {{fieldLabelColor}}; display: {{fieldDisplayLabel}};">{{fieldLabel}}</label>
|
||||
{{#if isTextField}}
|
||||
<input name="{{fieldId}}" placeholder="{{fieldPlaceholder}}"/>
|
||||
{{else if isSelectField}}
|
||||
<select name="{{fieldId}}" placeholder="{{fieldPlaceholder}}">
|
||||
<option value="" selected disabled>-- {{fieldPlaceholder}} --</option>
|
||||
{{#each uniqueValues}}
|
||||
<option value="{{this}}">{{this}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</script>
|
||||
|
||||
<!-- Vertical Table Template -->
|
||||
<script id="vertical-table-template" type="text/x-handlebars-template">
|
||||
<div class="dw-checker-results-container">
|
||||
{{#each results}}
|
||||
<div class="result-page" data-page="{{@index}}">
|
||||
<table class="dw-checker-result-table" {{{getStyle ../resultDivider ../resultDividerWidth}}}>
|
||||
<tbody>
|
||||
{{#each this}}
|
||||
{{#unless (getColumnSetting @key 'hide')}}
|
||||
<tr>
|
||||
<th {{{getStyleHeader ../resultDivider ../resultDividerWidth ../headerColor}}}>
|
||||
<span class="dw-checker-result-header">{{@key}}</span>
|
||||
</th>
|
||||
<td {{{getStyleValue ../resultDivider ../resultDividerWidth ../valueColor}}}>
|
||||
{{#if (eq (getColumnSetting @key 'type') 'link_button')}}
|
||||
<span class="dw-checker-result-value">{{getColumnSetting @key 'prefix' this}} <a href="{{getValueWithPrefix @key}}" class="btn btn-primary">{{getColumnSetting @key 'button_text'}}</a></span>
|
||||
{{else if (eq (getColumnSetting @key 'type') 'whatsapp_button')}}
|
||||
<span class="dw-checker-result-value">{{getColumnSetting @key 'prefix' this}} <a href="https://wa.me/{{getValueWithPrefix @key}}" class="btn btn-success">{{getColumnSetting @key 'button_text'}}</a></span>
|
||||
{{else if (eq (getColumnSetting @key 'type') 'text')}}
|
||||
<!-- Raw value for text type -->
|
||||
<span class="dw-checker-result-value">{{getColumnSetting @key 'prefix' this}} {{formatValue this}}</span>
|
||||
{{else}}
|
||||
<!-- Default behavior: raw value -->
|
||||
<span class="dw-checker-result-value">{{getColumnSetting @key 'prefix' this}} {{formatValue this}}</span>
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/unless}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class="pagination-controls">
|
||||
<button type="button" class="prev-page">Previous</button>
|
||||
<span class="current-page">Data 1</span>
|
||||
<button type="button" class="next-page">Next</button>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<!-- Div Template -->
|
||||
<script id="div-template" type="text/x-handlebars-template">
|
||||
<div class="dw-checker-results-container">
|
||||
{{#each results}}
|
||||
<div class="result-page" data-page="{{@index}}">
|
||||
<div class="dw-checker-result-div" {{{getStyle ../resultDivider ../resultDividerWidth}}}>
|
||||
{{#each this}}
|
||||
{{#unless (getColumnSetting @key 'hide')}}
|
||||
<div class="dw-checker-result-div-item" {{{getStyleHeader ../resultDivider ../resultDividerWidth ../headerColor}}}>
|
||||
<div class="result-header" {{{getStyleHeader ../resultDivider ../resultDividerWidth ../headerColor}}}>
|
||||
<span class="dw-checker-result-header">{{@key}}</span>
|
||||
</div>
|
||||
<div class="result-value" {{{getStyleValue ../resultDivider ../resultDividerWidth ../valueColor}}}>
|
||||
{{#if (eq (getColumnSetting @key 'type') 'link_button')}}
|
||||
<span class="dw-checker-result-value">{{getColumnSetting @key 'prefix' this}} <a href="{{getValueWithPrefix @key}}" class="btn btn-primary">{{getColumnSetting @key 'button_text'}}</a></span>
|
||||
{{else if (eq (getColumnSetting @key 'type') 'whatsapp_button')}}
|
||||
<span class="dw-checker-result-value">{{getColumnSetting @key 'prefix' this}} <a href="https://wa.me/{{getValueWithPrefix @key}}" class="btn btn-success">{{getColumnSetting @key 'button_text'}}</a></span>
|
||||
{{else if (eq (getColumnSetting @key 'type') 'text')}}
|
||||
<!-- Raw value for text type -->
|
||||
<span class="dw-checker-result-value">{{getColumnSetting @key 'prefix' this}} {{formatValue this}}</span>
|
||||
{{else}}
|
||||
<!-- Default behavior: raw value -->
|
||||
<span class="dw-checker-result-value">{{getColumnSetting @key 'prefix' this}} {{formatValue this}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class="pagination-controls">
|
||||
<button type="button" class="prev-page">Previous</button>
|
||||
<span class="current-page">Data 1</span>
|
||||
<button type="button" class="next-page">Next</button>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<!-- Card Template -->
|
||||
<script id="cards-template" type="text/x-handlebars-template">
|
||||
<div class="dw-cards-container">
|
||||
{{#each results}}
|
||||
<div class="result-page" data-page="{{@index}}" style="display: none;">
|
||||
{{#each this}}
|
||||
<div class="dw-card">
|
||||
<div class="dw-card-item">
|
||||
<h6 class="dw-card-title">{{@key}}</h6>
|
||||
<p class="dw-card-value">
|
||||
{{#if (eq (getColumnSetting @key 'type' this) 'link_button')}}
|
||||
<span class="dw-checker-result-value">{{getColumnSetting @key 'prefix' this}} <a href="{{getValueWithPrefix @key}}" class="btn btn-primary">{{getColumnSetting @key 'button_text'}}</a></span>
|
||||
{{else if (eq (getColumnSetting @key 'type' this) 'whatsapp_button')}}
|
||||
<span class="dw-checker-result-value">{{getColumnSetting @key 'prefix' this}} <a href="https://wa.me/{{getValueWithPrefix @key}}" class="btn btn-success">{{getColumnSetting @key 'button_text' this}}</a></span>
|
||||
{{else if (eq (getColumnSetting @key 'type' this) 'text')}}
|
||||
<!-- Raw value for text type -->
|
||||
<span class="dw-checker-result-value">{{getColumnSetting @key 'prefix' this}} {{formatValue this}}</span>
|
||||
{{else}}
|
||||
<!-- Default behavior: raw value -->
|
||||
<span class="dw-checker-result-value">{{getColumnSetting @key 'prefix' this}} {{formatValue this}}</span>
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class="pagination-controls">
|
||||
<button type="button" class="prev-page">Previous</button>
|
||||
<span class="current-page">Data 1</span>
|
||||
<button type="button" class="next-page">Next</button>
|
||||
</div>
|
||||
</script>
|
||||
<!-- General Table Template -->
|
||||
<script id="standard-table-template" type="text/x-handlebars-template">
|
||||
<table class="dw-standard-table table">
|
||||
<thead>
|
||||
<tr>
|
||||
{{#each columnHeaders}}
|
||||
<th>{{this}}</th>
|
||||
{{/each}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each results}}
|
||||
<tr>
|
||||
{{#each this}}
|
||||
<td>{{formatValue this}}</td>
|
||||
{{/each}}
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</script>
|
||||
|
||||
<script id="repeater-template" type="text/x-handlebars-template">
|
||||
{{#each fields}}
|
||||
<div class="card shadow repeater-card gap-2 position-relative">
|
||||
<div class="card-body">
|
||||
<div class="row mb-2">
|
||||
<div class="col-3"><label class="form-label fw-bold mb-0">Field ID</label></div>
|
||||
<div class="col-9">
|
||||
<input class="form-control field-id" value="{{@key}}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-3"><label class="form-label fw-bold mb-0">Kolom</label></div>
|
||||
<div class="col-9">
|
||||
<select name="checker[fields][{{@key}}][kolom]" class="form-select form-control border select-kolom">
|
||||
{{#each kolom}}
|
||||
<option value="{{this}}" {{#ifCond ../selected_kolom '==' this}}selected{{/ifCond}}>{{this}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-3"><label class="form-label fw-bold mb-0">Tipe</label></div>
|
||||
<div class="col-9">
|
||||
<select name="checker[fields][{{@key}}][type]" class="form-select form-control border select-field-type">
|
||||
<option value="text" {{#ifCond type '==' 'text'}}selected{{/ifCond}}>Text</option>
|
||||
<option value="select" {{#ifCond type '==' 'select'}}selected{{/ifCond}}>Select</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-3"><label class="form-label fw-bold mb-0">Label</label></div>
|
||||
<div class="col-9">
|
||||
<input name="checker[fields][{{@key}}][label]" class="form-control field-label" value="{{label}}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-3"><label class="form-label fw-bold mb-0">Placeholder</label></div>
|
||||
<div class="col-9">
|
||||
<input name="checker[fields][{{@key}}][placeholder]" class="form-control field-placeholder" value="{{placeholder}}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-3"><label class="form-label fw-bold mb-0">Value Matcher</label></div>
|
||||
<div class="col-9">
|
||||
<select name="checker[fields][{{@key}}][match]" class="form-select form-control border select-match-type">
|
||||
<option value="match" {{#ifCond match '==' 'match'}}selected{{/ifCond}}>Match</option>
|
||||
<option value="contain" {{#ifCond match '==' 'contain'}}selected{{/ifCond}}>Contain</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-buttons d-flex gap-2 flex-column position-absolute">
|
||||
<button type="button" class="btn btn-secondary py-1 px-2 move-card"><i class="bi bi-arrow-down-up"></i></button>
|
||||
<button type="button" class="btn btn-danger py-1 px-2 delete-form-card"><i class="bi bi-x"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</script>
|
||||
@@ -168,7 +168,7 @@
|
||||
<div class="col-9">
|
||||
<select name="checker[fields][{{@key}}][kolom]" class="form-select form-control border select-kolom">
|
||||
{{#each kolom}}
|
||||
<option value="{{this}}">{{this}}</option>
|
||||
<option value="{{this}}" {{#ifCond ../selected_kolom '==' this}}selected{{/ifCond}}>{{this}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -27,12 +27,125 @@
|
||||
<div class="dw-checker-container" id="dw-checker-outside-results" style="display: none;">
|
||||
<div class="dw-checker-wrapper"></div>
|
||||
</div>
|
||||
<input type="hidden" id="post_id" value="<?= (isset($_GET['post']) && isset($_GET['action']) && $_GET['action'] == 'edit') ? $_GET['post'] : '' ?>">
|
||||
<input type="hidden" id="post_id" value="<?= isset($_GET["post"]) &&
|
||||
isset($_GET["action"]) &&
|
||||
$_GET["action"] == "edit"
|
||||
? $_GET["post"]
|
||||
: "" ?>">
|
||||
</div>
|
||||
<hr>
|
||||
<div class="input-group mt-3">
|
||||
<span class="input-group-text" id="basic-addon2">Reset Preview Interval</span>
|
||||
<input type="number" id="preview-interval" class="form-control border text-end pe-2" aria-describedby="basic-addon2" value="10">
|
||||
<span class="input-group-text border" id="basic-addon2">seconds</span>
|
||||
<button class="btn btn-primary border-primary set-preview"><i class="bi bi-arrow-clockwise me-1"></i>Refresh</button>
|
||||
</div>
|
||||
|
||||
<!-- Essential Handlebars templates for preview functionality -->
|
||||
|
||||
<!-- Handlebars Template for Fields -->
|
||||
<script id="fields-template" type="text/x-handlebars-template">
|
||||
{{#each fields}}
|
||||
<div class="dw-checker-field">
|
||||
<label for="{{fieldId}}" style="color: {{fieldLabelColor}}; display: {{fieldDisplayLabel}};">{{fieldLabel}}</label>
|
||||
{{#if isTextField}}
|
||||
<input name="{{fieldId}}" placeholder="{{fieldPlaceholder}}"/>
|
||||
{{else if isSelectField}}
|
||||
<select name="{{fieldId}}" placeholder="{{fieldPlaceholder}}">
|
||||
<option value="" selected disabled>-- {{fieldPlaceholder}} --</option>
|
||||
{{#each uniqueValues}}
|
||||
<option value="{{this}}">{{this}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</script>
|
||||
|
||||
<script id="vertical-table-template" type="text/x-handlebars-template">
|
||||
<div class="dw-checker-results-container">
|
||||
{{#each results}}
|
||||
<div class="result-page" data-page="{{@index}}">
|
||||
<table class="dw-checker-result-table">
|
||||
<tbody>
|
||||
{{#each this}}
|
||||
<tr>
|
||||
<th><span class="dw-checker-result-header">{{@key}}</span></th>
|
||||
<td><span class="dw-checker-result-value">{{this}}</span></td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script id="div-template" type="text/x-handlebars-template">
|
||||
<div class="dw-checker-results-container">
|
||||
{{#each results}}
|
||||
<div class="result-page" data-page="{{@index}}">
|
||||
<div class="dw-checker-result-container" data-pagination="{{@index}}">
|
||||
{{#each this}}
|
||||
<div class="dw-checker-result-div">
|
||||
<div class="result-header">
|
||||
<span class="dw-checker-result-header">{{@key}}</span>
|
||||
</div>
|
||||
<div class="result-value">
|
||||
<span class="dw-checker-result-value">{{this}}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script id="standard-table-template" type="text/x-handlebars-template">
|
||||
<div class="dw-checker-results-container">
|
||||
<table class="dw-checker-result-table">
|
||||
<thead>
|
||||
{{#if results.[0]}}
|
||||
<tr>
|
||||
{{#each results.[0]}}
|
||||
<th><span class="dw-checker-result-header">{{@key}}</span></th>
|
||||
{{/each}}
|
||||
</tr>
|
||||
{{/if}}
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each results}}
|
||||
<tr>
|
||||
{{#each this}}
|
||||
<td><span class="dw-checker-result-value">{{this}}</span></td>
|
||||
{{/each}}
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<!-- Card Template -->
|
||||
<script id="cards-template" type="text/x-handlebars-template">
|
||||
<div class="dw-cards-container">
|
||||
{{#each results}}
|
||||
<div class="result-page" data-page="{{@index}}" style="display: none;">
|
||||
{{#each this}}
|
||||
<div class="dw-card">
|
||||
<div class="dw-card-item">
|
||||
<h6 class="dw-card-title">{{@key}}</h6>
|
||||
<p class="dw-card-value">{{this}}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class="pagination-controls">
|
||||
<button type="button" class="prev-page">Previous</button>
|
||||
<span class="current-page">Data 1</span>
|
||||
<button type="button" class="next-page">Next</button>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="input-group mt-3">
|
||||
<span class="input-group-text" id="basic-addon2">Reset Preview Interval</span>
|
||||
<input type="number" id="preview-interval" class="form-control border text-end pe-2" aria-describedby="basic-addon2" value="10">
|
||||
<span class="input-group-text border" id="basic-addon2">seconds</span>
|
||||
<button class="btn btn-primary border-primary set-preview"><i class="bi bi-arrow-clockwise me-1"></i>Refresh</button>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
@@ -31,6 +31,16 @@
|
||||
<small class="text-muted">Maximum records to display (performance limit)</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-3"><label class="form-label fw-bold mb-0">Cache Control</label></div>
|
||||
<div class="col-9">
|
||||
<button type="button" class="btn btn-sm btn-outline-warning" id="clear-checker-cache" data-checker-id="<?= $post_id ?>">
|
||||
<i class="bi bi-arrow-clockwise"></i> Clear Cache & Refresh Data
|
||||
</button>
|
||||
<small class="text-muted d-block mt-1">Clear cached data to fetch fresh data from Google Sheet (cached for 5 minutes)</small>
|
||||
<div id="cache-clear-message" class="mt-2" style="display:none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="has-link" style="display: none;">
|
||||
@@ -150,4 +160,49 @@
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
jQuery(document).ready(function($) {
|
||||
$('#clear-checker-cache').on('click', function() {
|
||||
var btn = $(this);
|
||||
var checkerId = btn.data('checkerId');
|
||||
var messageDiv = $('#cache-clear-message');
|
||||
|
||||
btn.prop('disabled', true).html('<i class="bi bi-arrow-clockwise spinner-border spinner-border-sm"></i> Clearing...');
|
||||
|
||||
$.ajax({
|
||||
url: ajaxurl,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'checker_clear_cache',
|
||||
checker_id: checkerId,
|
||||
security: '<?php echo wp_create_nonce("checker_ajax_nonce"); ?>'
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
messageDiv.removeClass('alert-danger').addClass('alert alert-success')
|
||||
.html('<i class="bi bi-check-circle"></i> ' + response.data.message)
|
||||
.fadeIn();
|
||||
} else {
|
||||
messageDiv.removeClass('alert-success').addClass('alert alert-danger')
|
||||
.html('<i class="bi bi-exclamation-circle"></i> ' + response.data.message)
|
||||
.fadeIn();
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
messageDiv.fadeOut();
|
||||
}, 3000);
|
||||
},
|
||||
error: function() {
|
||||
messageDiv.removeClass('alert-success').addClass('alert alert-danger')
|
||||
.html('<i class="bi bi-exclamation-circle"></i> Failed to clear cache')
|
||||
.fadeIn();
|
||||
},
|
||||
complete: function() {
|
||||
btn.prop('disabled', false).html('<i class="bi bi-arrow-clockwise"></i> Clear Cache & Refresh Data');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -3,120 +3,407 @@
|
||||
<tr class="has-link" style="display: none;">
|
||||
<th>Rate Limiting</th>
|
||||
<td>
|
||||
<p class="text-muted small mb-3">Limit the number of searches per IP address to prevent abuse</p>
|
||||
|
||||
<p class="text-muted small mb-3">Limit the number of searches per IP address to prevent abuse and bot attacks</p>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" value="yes" id="security-rate-limit-enabled" name="checker[security][rate_limit][enabled]" <?= isset($checker['security']['rate_limit']['enabled']) && $checker['security']['rate_limit']['enabled'] == 'yes' ? 'checked' : '' ?>>
|
||||
<input class="form-check-input" type="checkbox" value="yes" id="security-rate-limit-enabled" name="checker[security][rate_limit][enabled]" <?= isset(
|
||||
$checker["security"]["rate_limit"]["enabled"],
|
||||
) && $checker["security"]["rate_limit"]["enabled"] == "yes"
|
||||
? "checked"
|
||||
: "" ?>>
|
||||
<label class="form-check-label fw-bold" for="security-rate-limit-enabled">
|
||||
Enable Rate Limiting
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="rate-limit-settings" style="<?= isset($checker['security']['rate_limit']['enabled']) && $checker['security']['rate_limit']['enabled'] == 'yes' ? '' : 'display:none;' ?>">
|
||||
|
||||
<div class="rate-limit-settings" style="<?= isset(
|
||||
$checker["security"]["rate_limit"]["enabled"],
|
||||
) && $checker["security"]["rate_limit"]["enabled"] == "yes"
|
||||
? ""
|
||||
: "display:none;" ?>">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Max Attempts</label>
|
||||
<input type="number" name="checker[security][rate_limit][max_attempts]" value="<?= $checker['security']['rate_limit']['max_attempts'] ?? 5 ?>" class="form-control" min="1" max="100">
|
||||
<small class="text-muted">Maximum searches allowed per time window</small>
|
||||
<input type="number" name="checker[security][rate_limit][max_attempts]" value="<?= $checker[
|
||||
"security"
|
||||
]["rate_limit"]["max_attempts"] ??
|
||||
5 ?>" class="form-control" min="1" max="100">
|
||||
<small class="text-muted">Maximum searches allowed per time window (1-100)</small>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Time Window (minutes)</label>
|
||||
<input type="number" name="checker[security][rate_limit][time_window]" value="<?= $checker['security']['rate_limit']['time_window'] ?? 15 ?>" class="form-control" min="1" max="1440">
|
||||
<small class="text-muted">Reset attempts after this duration</small>
|
||||
<input type="number" name="checker[security][rate_limit][time_window]" value="<?= $checker[
|
||||
"security"
|
||||
]["rate_limit"]["time_window"] ??
|
||||
15 ?>" class="form-control" min="1" max="1440">
|
||||
<small class="text-muted">Reset attempts after this duration (1-1440 minutes)</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Block Duration (minutes)</label>
|
||||
<input type="number" name="checker[security][rate_limit][block_duration]" value="<?= $checker['security']['rate_limit']['block_duration'] ?? 60 ?>" class="form-control" min="1" max="10080">
|
||||
<small class="text-muted">How long to block after exceeding limit</small>
|
||||
<input type="number" name="checker[security][rate_limit][block_duration]" value="<?= $checker[
|
||||
"security"
|
||||
]["rate_limit"]["block_duration"] ??
|
||||
60 ?>" class="form-control" min="1" max="10080">
|
||||
<small class="text-muted">How long to block after exceeding limit (1-10080 minutes)</small>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Error Message</label>
|
||||
<input type="text" name="checker[security][rate_limit][error_message]" value="<?= $checker['security']['rate_limit']['error_message'] ?? 'Too many attempts. Please try again later.' ?>" class="form-control">
|
||||
<input type="text" name="checker[security][rate_limit][error_message]" value="<?= $checker[
|
||||
"security"
|
||||
]["rate_limit"]["error_message"] ??
|
||||
"Too many attempts. Please try again later." ?>" class="form-control">
|
||||
<small class="text-muted">Message shown when blocked</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" value="yes" id="security-rate-limit-whitelist" name="checker[security][rate_limit][whitelist_enabled]" <?= isset(
|
||||
$checker["security"]["rate_limit"][
|
||||
"whitelist_enabled"
|
||||
],
|
||||
) &&
|
||||
$checker["security"]["rate_limit"][
|
||||
"whitelist_enabled"
|
||||
] == "yes"
|
||||
? "checked"
|
||||
: "" ?>>
|
||||
<label class="form-check-label" for="security-rate-limit-whitelist">
|
||||
Enable IP Whitelist
|
||||
</label>
|
||||
</div>
|
||||
<div id="whitelist-ips" style="<?= isset(
|
||||
$checker["security"]["rate_limit"][
|
||||
"whitelist_enabled"
|
||||
],
|
||||
) &&
|
||||
$checker["security"]["rate_limit"][
|
||||
"whitelist_enabled"
|
||||
] == "yes"
|
||||
? ""
|
||||
: "display:none;" ?>" class="mt-3">
|
||||
<label class="form-label">Whitelisted IPs (one per line)</label>
|
||||
<textarea name="checker[security][rate_limit][whitelist_ips]" class="form-control" rows="3"><?= $checker[
|
||||
"security"
|
||||
]["rate_limit"]["whitelist_ips"] ??
|
||||
"" ?></textarea>
|
||||
<small class="text-muted">IPs that bypass rate limiting (supports CIDR notation like 192.168.1.0/24)</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="has-link" style="display: none;">
|
||||
<th>Google reCAPTCHA v3</th>
|
||||
<td>
|
||||
<p class="text-muted small mb-3">Invisible CAPTCHA protection. <a href="https://www.google.com/recaptcha/admin" target="_blank">Get keys here</a></p>
|
||||
|
||||
<div class="alert alert-info small mb-3">
|
||||
<strong>How to get keys:</strong><br>
|
||||
1. Go to <a href="https://www.google.com/recaptcha/admin/create" target="_blank" rel="noopener">reCAPTCHA Admin Console</a><br>
|
||||
2. Create a new site with <strong>Score based (v3)</strong> type<br>
|
||||
3. Add your domain (e.g., dwindi.com)<br>
|
||||
4. Copy the <strong>Site Key</strong> and <strong>Secret Key</strong> shown after creation<br>
|
||||
<em class="text-muted">Note: If using Google Cloud Console, click "Use Legacy Key" under Integration tab to get the secret key</em>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" value="yes" id="security-recaptcha-enabled" name="checker[security][recaptcha][enabled]" <?= isset($checker['security']['recaptcha']['enabled']) && $checker['security']['recaptcha']['enabled'] == 'yes' ? 'checked' : '' ?>>
|
||||
<input class="form-check-input" type="checkbox" value="yes" id="security-recaptcha-enabled" name="checker[security][recaptcha][enabled]" <?= isset(
|
||||
$checker["security"]["recaptcha"]["enabled"],
|
||||
) && $checker["security"]["recaptcha"]["enabled"] == "yes"
|
||||
? "checked"
|
||||
: "" ?>>
|
||||
<label class="form-check-label fw-bold" for="security-recaptcha-enabled">
|
||||
Enable reCAPTCHA v3
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="recaptcha-settings" style="<?= isset($checker['security']['recaptcha']['enabled']) && $checker['security']['recaptcha']['enabled'] == 'yes' ? '' : 'display:none;' ?>">
|
||||
|
||||
<div class="recaptcha-settings" style="<?= isset(
|
||||
$checker["security"]["recaptcha"]["enabled"],
|
||||
) && $checker["security"]["recaptcha"]["enabled"] == "yes"
|
||||
? ""
|
||||
: "display:none;" ?>">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Site Key</label>
|
||||
<input type="text" name="checker[security][recaptcha][site_key]" value="<?= $checker['security']['recaptcha']['site_key'] ?? '' ?>" class="form-control" placeholder="6Lc...">
|
||||
<small class="text-muted">Public key for frontend</small>
|
||||
<label class="form-label">Site Key <span class="text-danger">*</span></label>
|
||||
<input type="text" name="checker[security][recaptcha][site_key]" value="<?= esc_attr($checker["security"]["recaptcha"]["site_key"] ?? "") ?>" class="form-control" placeholder="6Lc...">
|
||||
<small class="text-muted">Public key for frontend - shown after creating reCAPTCHA site</small>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Secret Key</label>
|
||||
<input type="text" name="checker[security][recaptcha][secret_key]" value="<?= $checker['security']['recaptcha']['secret_key'] ?? '' ?>" class="form-control" placeholder="6Lc...">
|
||||
<small class="text-muted">Private key for backend verification</small>
|
||||
<label class="form-label">Secret Key (Server) <span class="text-danger">*</span></label>
|
||||
<input type="text" name="checker[security][recaptcha][secret_key]" value="<?= esc_attr($checker["security"]["recaptcha"]["secret_key"] ?? "") ?>" class="form-control" placeholder="Secret key from Google admin console">
|
||||
<small class="text-muted">Server-side secret from Google reCAPTCHA admin console (required for verification)</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Minimum Score</label>
|
||||
<input type="number" name="checker[security][recaptcha][min_score]" value="<?= $checker['security']['recaptcha']['min_score'] ?? 0.5 ?>" class="form-control" min="0" max="1" step="0.1">
|
||||
<small class="text-muted">0.0 (bot) to 1.0 (human). Recommended: 0.5</small>
|
||||
<input type="number" name="checker[security][recaptcha][min_score]" value="<?= $checker[
|
||||
"security"
|
||||
]["recaptcha"]["min_score"] ??
|
||||
0.5 ?>" class="form-control" min="0" max="1" step="0.1" <?= isset(
|
||||
$checker["security"]["recaptcha"]["enabled"],
|
||||
) && $checker["security"]["recaptcha"]["enabled"] == "yes"
|
||||
? "required"
|
||||
: "" ?>>
|
||||
<small class="text-muted">0.0 (likely bot) to 1.0 (likely human). Recommended: 0.5</small>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Action Name</label>
|
||||
<input type="text" name="checker[security][recaptcha][action]" value="<?= $checker[
|
||||
"security"
|
||||
]["recaptcha"]["action"] ??
|
||||
"checker_validate" ?>" class="form-control" <?= isset(
|
||||
$checker["security"]["recaptcha"]["enabled"],
|
||||
) && $checker["security"]["recaptcha"]["enabled"] == "yes"
|
||||
? "required"
|
||||
: "" ?>>
|
||||
<small class="text-muted">Action name for reCAPTCHA tracking (letters only)</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" value="yes" id="security-recaptcha-hide-badge" name="checker[security][recaptcha][hide_badge]" <?= isset(
|
||||
$checker["security"]["recaptcha"][
|
||||
"hide_badge"
|
||||
],
|
||||
) &&
|
||||
$checker["security"]["recaptcha"][
|
||||
"hide_badge"
|
||||
] == "yes"
|
||||
? "checked"
|
||||
: "" ?>>
|
||||
<label class="form-check-label" for="security-recaptcha-hide-badge">
|
||||
Hide reCAPTCHA Badge
|
||||
</label>
|
||||
</div>
|
||||
<small class="text-muted">Hides the "protected by reCAPTCHA" badge. You must add attribution elsewhere on the page.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label">Custom Error Message</label>
|
||||
<input type="text" name="checker[security][recaptcha][error_message]" value="<?= esc_attr($checker["security"]["recaptcha"]["error_message"] ?? "") ?>" class="form-control" placeholder="<?= esc_attr__('Leave empty for default message', 'sheet-data-checker-pro') ?>">
|
||||
<small class="text-muted"><?= esc_html__('Custom message shown when reCAPTCHA verification fails (leave empty for default)', 'sheet-data-checker-pro') ?></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="has-link" style="display: none;">
|
||||
<th>Cloudflare Turnstile</th>
|
||||
<td>
|
||||
<p class="text-muted small mb-3">Privacy-friendly CAPTCHA alternative. <a href="https://dash.cloudflare.com/?to=/:account/turnstile" target="_blank">Get keys here</a></p>
|
||||
|
||||
<div class="alert alert-info small mb-3">
|
||||
<strong>How to get keys:</strong><br>
|
||||
1. Go to <a href="https://dash.cloudflare.com/?to=/:account/turnstile" target="_blank" rel="noopener">Cloudflare Turnstile Dashboard</a><br>
|
||||
2. Click "Add Widget" and enter your site name<br>
|
||||
3. Add your domain (e.g., dwindi.com)<br>
|
||||
4. Choose Widget Mode: <strong>Managed</strong> (recommended) or Non-interactive<br>
|
||||
5. Copy the <strong>Site Key</strong> and <strong>Secret Key</strong> shown after creation
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" value="yes" id="security-turnstile-enabled" name="checker[security][turnstile][enabled]" <?= isset($checker['security']['turnstile']['enabled']) && $checker['security']['turnstile']['enabled'] == 'yes' ? 'checked' : '' ?>>
|
||||
<input class="form-check-input" type="checkbox" value="yes" id="security-turnstile-enabled" name="checker[security][turnstile][enabled]" <?= isset(
|
||||
$checker["security"]["turnstile"]["enabled"],
|
||||
) && $checker["security"]["turnstile"]["enabled"] == "yes"
|
||||
? "checked"
|
||||
: "" ?>>
|
||||
<label class="form-check-label fw-bold" for="security-turnstile-enabled">
|
||||
Enable Cloudflare Turnstile
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="turnstile-settings" style="<?= isset($checker['security']['turnstile']['enabled']) && $checker['security']['turnstile']['enabled'] == 'yes' ? '' : 'display:none;' ?>">
|
||||
|
||||
<div class="turnstile-settings" style="<?= isset(
|
||||
$checker["security"]["turnstile"]["enabled"],
|
||||
) && $checker["security"]["turnstile"]["enabled"] == "yes"
|
||||
? ""
|
||||
: "display:none;" ?>">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Site Key</label>
|
||||
<input type="text" name="checker[security][turnstile][site_key]" value="<?= $checker['security']['turnstile']['site_key'] ?? '' ?>" class="form-control" placeholder="0x4AAA...">
|
||||
<small class="text-muted">Public key for frontend</small>
|
||||
<label class="form-label">Site Key <span class="text-danger">*</span></label>
|
||||
<input type="text" name="checker[security][turnstile][site_key]" value="<?= esc_attr($checker["security"]["turnstile"]["site_key"] ?? "") ?>" class="form-control" placeholder="0x4AAA...">
|
||||
<small class="text-muted">Public key for frontend (starts with 0x4AAA...)</small>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Secret Key</label>
|
||||
<input type="text" name="checker[security][turnstile][secret_key]" value="<?= $checker['security']['turnstile']['secret_key'] ?? '' ?>" class="form-control" placeholder="0x4AAA...">
|
||||
<small class="text-muted">Private key for backend verification</small>
|
||||
<label class="form-label">Secret Key <span class="text-danger">*</span></label>
|
||||
<input type="text" name="checker[security][turnstile][secret_key]" value="<?= esc_attr($checker["security"]["turnstile"]["secret_key"] ?? "") ?>" class="form-control" placeholder="0x4AAA...">
|
||||
<small class="text-muted">Private key for backend verification (starts with 0x4AAA...)</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Theme</label>
|
||||
<select name="checker[security][turnstile][theme]" class="form-select">
|
||||
<option value="light" <?= isset($checker['security']['turnstile']['theme']) && $checker['security']['turnstile']['theme'] == 'light' ? 'selected' : '' ?>>Light</option>
|
||||
<option value="dark" <?= isset($checker['security']['turnstile']['theme']) && $checker['security']['turnstile']['theme'] == 'dark' ? 'selected' : '' ?>>Dark</option>
|
||||
<option value="auto" <?= isset($checker['security']['turnstile']['theme']) && $checker['security']['turnstile']['theme'] == 'auto' ? 'selected' : '' ?>>Auto</option>
|
||||
<option value="light" <?= isset(
|
||||
$checker["security"]["turnstile"]["theme"],
|
||||
) &&
|
||||
$checker["security"]["turnstile"]["theme"] ==
|
||||
"light"
|
||||
? "selected"
|
||||
: "" ?>>Light</option>
|
||||
<option value="dark" <?= isset(
|
||||
$checker["security"]["turnstile"]["theme"],
|
||||
) &&
|
||||
$checker["security"]["turnstile"]["theme"] ==
|
||||
"dark"
|
||||
? "selected"
|
||||
: "" ?>>Dark</option>
|
||||
<option value="auto" <?= isset(
|
||||
$checker["security"]["turnstile"]["theme"],
|
||||
) &&
|
||||
$checker["security"]["turnstile"]["theme"] ==
|
||||
"auto"
|
||||
? "selected"
|
||||
: "" ?>>Auto</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Size</label>
|
||||
<select name="checker[security][turnstile][size]" class="form-select">
|
||||
<option value="normal" <?= isset(
|
||||
$checker["security"]["turnstile"]["size"],
|
||||
) &&
|
||||
$checker["security"]["turnstile"]["size"] ==
|
||||
"normal"
|
||||
? "selected"
|
||||
: "" ?>>Normal</option>
|
||||
<option value="compact" <?= isset(
|
||||
$checker["security"]["turnstile"]["size"],
|
||||
) &&
|
||||
$checker["security"]["turnstile"]["size"] ==
|
||||
"compact"
|
||||
? "selected"
|
||||
: "" ?>>Compact</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label">Custom Error Message</label>
|
||||
<input type="text" name="checker[security][turnstile][error_message]" value="<?= esc_attr($checker["security"]["turnstile"]["error_message"] ?? "") ?>" class="form-control" placeholder="<?= esc_attr__('Leave empty for default message', 'sheet-data-checker-pro') ?>">
|
||||
<small class="text-muted"><?= esc_html__('Custom message shown when Turnstile verification fails (leave empty for default)', 'sheet-data-checker-pro') ?></small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="has-link" style="display: none;">
|
||||
<th>Honeypot Protection</th>
|
||||
<td>
|
||||
<p class="text-muted small mb-3"><?= esc_html__('Invisible spam protection that catches automated bots without affecting real users', 'sheet-data-checker-pro') ?></p>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" value="yes" id="security-honeypot-enabled" name="checker[security][honeypot][enabled]" <?= isset($checker["security"]["honeypot"]["enabled"]) && $checker["security"]["honeypot"]["enabled"] == "yes" ? "checked" : "" ?>>
|
||||
<label class="form-check-label fw-bold" for="security-honeypot-enabled">
|
||||
<?= esc_html__('Enable Honeypot Field', 'sheet-data-checker-pro') ?>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="honeypot-settings" style="<?= isset($checker["security"]["honeypot"]["enabled"]) && $checker["security"]["honeypot"]["enabled"] == "yes" ? "" : "display:none;" ?>">
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label"><?= esc_html__('Custom Error Message', 'sheet-data-checker-pro') ?></label>
|
||||
<input type="text" name="checker[security][honeypot][error_message]" value="<?= esc_attr($checker["security"]["honeypot"]["error_message"] ?? "") ?>" class="form-control" placeholder="<?= esc_attr__('Leave empty for default message', 'sheet-data-checker-pro') ?>">
|
||||
<small class="text-muted"><?= esc_html__('Message shown when honeypot is triggered (leave empty for default)', 'sheet-data-checker-pro') ?></small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info small">
|
||||
<i class="bi bi-info-circle"></i> <?= esc_html__('Honeypot adds an invisible field that bots will fill out, allowing easy detection without user interaction.', 'sheet-data-checker-pro') ?>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="has-link" style="display: none;">
|
||||
<th>IP Detection Method</th>
|
||||
<td>
|
||||
<p class="text-muted small mb-3">Configure how to detect visitor IP addresses</p>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label">IP Detection Priority</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" id="ip-auto" name="checker[security][ip_detection]" value="auto" <?= !isset(
|
||||
$checker["security"]["ip_detection"],
|
||||
) || $checker["security"]["ip_detection"] == "auto"
|
||||
? "checked"
|
||||
: "" ?>>
|
||||
<label class="form-check-label" for="ip-auto">
|
||||
Automatic (Recommended)
|
||||
</label>
|
||||
<small class="d-block text-muted">Automatically detect IP through Cloudflare, proxies, and standard headers</small>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" id="ip-remote-addr" name="checker[security][ip_detection]" value="remote_addr" <?= isset(
|
||||
$checker["security"]["ip_detection"],
|
||||
) &&
|
||||
$checker["security"]["ip_detection"] ==
|
||||
"remote_addr"
|
||||
? "checked"
|
||||
: "" ?>>
|
||||
<label class="form-check-label" for="ip-remote-addr">
|
||||
REMOTE_ADDR Only
|
||||
</label>
|
||||
<small class="d-block text-muted">Only use REMOTE_ADDR (less accurate but more predictable)</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="has-link" style="display: none;">
|
||||
<th>Nonce Verification</th>
|
||||
<td>
|
||||
<p class="text-muted small mb-3">WordPress security token to prevent CSRF attacks</p>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" value="yes" id="security-nonce-enabled" name="checker[security][nonce_enabled]" value="yes" checked disabled>
|
||||
<label class="form-check-label" for="security-nonce-enabled">
|
||||
Enable Nonce Verification (Always Active)
|
||||
</label>
|
||||
</div>
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
Nonce verification is always enabled for security. This protects against Cross-Site Request Forgery (CSRF) attacks.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="has-link" style="display: none;">
|
||||
<th>Security Status</th>
|
||||
<td>
|
||||
<div id="security-status" class="alert alert-warning mb-3">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<strong>Security Check:</strong> Please configure at least one protection method (Rate Limiting, reCAPTCHA, or Turnstile).
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" id="test-security-btn">
|
||||
<i class="fas fa-shield-alt"></i> Test Security Settings
|
||||
</button>
|
||||
|
||||
<div id="security-test-results" style="display:none;" class="mt-3"></div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="has-link" style="display: none;">
|
||||
<th colspan="2">
|
||||
<div class="alert alert-info mb-0">
|
||||
<strong>Note:</strong> Only enable ONE CAPTCHA solution at a time. reCAPTCHA and Turnstile cannot be used together.
|
||||
<strong>Security Recommendations:</strong>
|
||||
<ul class="mb-0 mt-2">
|
||||
<li>Enable at least one protection method (Rate Limiting, reCAPTCHA, or Turnstile)</li>
|
||||
<li>Only enable ONE CAPTCHA solution at a time (reCAPTCHA or Turnstile)</li>
|
||||
<li>For high-traffic sites, use Rate Limiting with reCAPTCHA v3</li>
|
||||
<li>Regularly review rate limiting logs for suspicious activity</li>
|
||||
</ul>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
@@ -133,33 +420,178 @@ jQuery(document).ready(function($){
|
||||
$('.rate-limit-settings').slideUp();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Toggle IP whitelist
|
||||
$('#security-rate-limit-whitelist').on('change', function(){
|
||||
if($(this).is(':checked')){
|
||||
$('#whitelist-ips').slideDown();
|
||||
}else{
|
||||
$('#whitelist-ips').slideUp();
|
||||
}
|
||||
});
|
||||
|
||||
// Toggle reCAPTCHA settings
|
||||
$('#security-recaptcha-enabled').on('change', function(){
|
||||
if($(this).is(':checked')){
|
||||
$('.recaptcha-settings').slideDown();
|
||||
// Disable Turnstile if reCAPTCHA is enabled
|
||||
if($('#security-turnstile-enabled').is(':checked')){
|
||||
$('#security-turnstile-enabled').prop('checked', false).trigger('change');
|
||||
alert('reCAPTCHA enabled. Turnstile has been disabled.');
|
||||
if(confirm('reCAPTCHA will be enabled. Do you want to disable Turnstile?')){
|
||||
$('#security-turnstile-enabled').prop('checked', false).trigger('change');
|
||||
} else {
|
||||
$(this).prop('checked', false);
|
||||
alert('Only one CAPTCHA solution can be active at a time.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
$('.recaptcha-settings').slideUp();
|
||||
}
|
||||
updateSecurityStatus();
|
||||
});
|
||||
|
||||
|
||||
// Toggle Turnstile settings
|
||||
$('#security-turnstile-enabled').on('change', function(){
|
||||
if($(this).is(':checked')){
|
||||
$('.turnstile-settings').slideDown();
|
||||
// Disable reCAPTCHA if Turnstile is enabled
|
||||
if($('#security-recaptcha-enabled').is(':checked')){
|
||||
$('#security-recaptcha-enabled').prop('checked', false).trigger('change');
|
||||
alert('Turnstile enabled. reCAPTCHA has been disabled.');
|
||||
if(confirm('Turnstile will be enabled. Do you want to disable reCAPTCHA?')){
|
||||
$('#security-recaptcha-enabled').prop('checked', false).trigger('change');
|
||||
} else {
|
||||
$(this).prop('checked', false);
|
||||
alert('Only one CAPTCHA solution can be active at a time.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
$('.turnstile-settings').slideUp();
|
||||
}
|
||||
updateSecurityStatus();
|
||||
});
|
||||
|
||||
// Honeypot toggle
|
||||
$('#security-honeypot-enabled').on('change', function(){
|
||||
if($(this).is(':checked')){
|
||||
$('.honeypot-settings').slideDown();
|
||||
}else{
|
||||
$('.honeypot-settings').slideUp();
|
||||
}
|
||||
updateSecurityStatus();
|
||||
});
|
||||
|
||||
// Update security status when any setting changes
|
||||
$('input[type="checkbox"]').on('change', function(){
|
||||
updateSecurityStatus();
|
||||
});
|
||||
|
||||
function updateSecurityStatus() {
|
||||
var rateLimitEnabled = $('#security-rate-limit-enabled').is(':checked');
|
||||
var recaptchaEnabled = $('#security-recaptcha-enabled').is(':checked');
|
||||
var turnstileEnabled = $('#security-turnstile-enabled').is(':checked');
|
||||
var honeypotEnabled = $('#security-honeypot-enabled').is(':checked');
|
||||
|
||||
var statusEl = $('#security-status');
|
||||
var protectionCount = 0;
|
||||
var protections = [];
|
||||
|
||||
if(rateLimitEnabled) { protectionCount++; protections.push('Rate Limiting'); }
|
||||
if(recaptchaEnabled) { protectionCount++; protections.push('reCAPTCHA'); }
|
||||
if(turnstileEnabled) { protectionCount++; protections.push('Turnstile'); }
|
||||
if(honeypotEnabled) { protectionCount++; protections.push('Honeypot'); }
|
||||
|
||||
if(protectionCount > 0) {
|
||||
statusEl.removeClass('alert-warning alert-danger').addClass('alert-success');
|
||||
statusEl.html('<i class="fas fa-check-circle"></i> <strong>Security Status:</strong> ' + protectionCount + ' protection(s) active: ' + protections.join(', '));
|
||||
} else {
|
||||
statusEl.removeClass('alert-success alert-danger').addClass('alert-warning');
|
||||
statusEl.html('<i class="fas fa-exclamation-triangle"></i> <strong>Security Status:</strong> No protection enabled. Please configure at least one protection method.');
|
||||
}
|
||||
|
||||
// Check for CAPTCHA conflict
|
||||
if(recaptchaEnabled && turnstileEnabled) {
|
||||
statusEl.removeClass('alert-success alert-warning').addClass('alert-danger');
|
||||
statusEl.html('<i class="fas fa-exclamation-circle"></i> <strong>Security Warning:</strong> Both reCAPTCHA and Turnstile are enabled. Please disable one of them.');
|
||||
}
|
||||
}
|
||||
|
||||
// Test security settings
|
||||
$('#test-security-btn').on('click', function(){
|
||||
var btn = $(this);
|
||||
var resultsEl = $('#security-test-results');
|
||||
|
||||
btn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> Testing...');
|
||||
|
||||
resultsEl.removeClass('alert-success alert-danger alert-warning').addClass('alert-info')
|
||||
.html('<i class="fas fa-info-circle"></i> Running security tests...').show();
|
||||
|
||||
// Simulate security test
|
||||
setTimeout(function(){
|
||||
var issues = [];
|
||||
|
||||
// Check rate limiting
|
||||
if(!$('#security-rate-limit-enabled').is(':checked')) {
|
||||
issues.push('Rate limiting is not enabled');
|
||||
}
|
||||
|
||||
// Check CAPTCHA
|
||||
if(!$('#security-recaptcha-enabled').is(':checked') && !$('#security-turnstile-enabled').is(':checked')) {
|
||||
issues.push('No CAPTCHA solution is enabled');
|
||||
}
|
||||
|
||||
// Check for CAPTCHA conflict
|
||||
if($('#security-recaptcha-enabled').is(':checked') && $('#security-turnstile-enabled').is(':checked')) {
|
||||
issues.push('Both reCAPTCHA and Turnstile are enabled (conflict)');
|
||||
}
|
||||
|
||||
// Check reCAPTCHA keys
|
||||
if($('#security-recaptcha-enabled').is(':checked')) {
|
||||
var siteKey = $('input[name="checker[security][recaptcha][site_key]"]').val();
|
||||
var secretKey = $('input[name="checker[security][recaptcha][secret_key]"]').val();
|
||||
|
||||
if(!siteKey || !siteKey.startsWith('6Lc')) {
|
||||
issues.push('reCAPTCHA site key is missing or invalid');
|
||||
}
|
||||
|
||||
if(!secretKey || !secretKey.startsWith('6Lc')) {
|
||||
issues.push('reCAPTCHA secret key is missing or invalid');
|
||||
}
|
||||
}
|
||||
|
||||
// Check Turnstile keys
|
||||
if($('#security-turnstile-enabled').is(':checked')) {
|
||||
var siteKey = $('input[name="checker[security][turnstile][site_key]"]').val();
|
||||
var secretKey = $('input[name="checker[security][turnstile][secret_key]"]').val();
|
||||
|
||||
if(!siteKey || !siteKey.startsWith('0x4AAA')) {
|
||||
issues.push('Turnstile site key is missing or invalid');
|
||||
}
|
||||
|
||||
if(!secretKey || !secretKey.startsWith('0x4AAA')) {
|
||||
issues.push('Turnstile secret key is missing or invalid');
|
||||
}
|
||||
}
|
||||
|
||||
// Display results
|
||||
btn.prop('disabled', false).html('<i class="fas fa-shield-alt"></i> Test Security Settings');
|
||||
|
||||
if(issues.length === 0) {
|
||||
resultsEl.removeClass('alert-info alert-danger alert-warning').addClass('alert-success')
|
||||
.html('<i class="fas fa-check-circle"></i> <strong>All security checks passed!</strong> Your configuration is secure.');
|
||||
} else {
|
||||
var issuesHtml = '<ul class="mb-0">';
|
||||
issues.forEach(function(issue) {
|
||||
issuesHtml += '<li>' + issue + '</li>';
|
||||
});
|
||||
issuesHtml += '</ul>';
|
||||
|
||||
resultsEl.removeClass('alert-info alert-success alert-warning').addClass('alert-danger')
|
||||
.html('<i class="fas fa-exclamation-circle"></i> <strong>Security issues found:</strong> ' + issuesHtml);
|
||||
}
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
// Initial security status update
|
||||
updateSecurityStatus();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -8,11 +8,61 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<?php require_once SHEET_CHECKER_PRO_PATH . 'templates/editor/setting-table-card.php'; ?>
|
||||
<?php require_once SHEET_CHECKER_PRO_PATH . 'templates/editor/setting-table-form.php'; ?>
|
||||
<?php require_once SHEET_CHECKER_PRO_PATH . 'templates/editor/setting-table-result.php'; ?>
|
||||
<?php require_once SHEET_CHECKER_PRO_PATH . 'templates/editor/setting-table-security.php'; ?>
|
||||
<?php require_once SHEET_CHECKER_PRO_PATH . 'templates/editor/js-template-repeater-card.php'; ?>
|
||||
<?php require_once SHEET_CHECKER_PRO_PATH . 'templates/editor/js-template-setting-output.php'; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php require_once SHEET_CHECKER_PRO_PATH .
|
||||
"templates/editor/setting-table-card.php"; ?>
|
||||
<?php require_once SHEET_CHECKER_PRO_PATH .
|
||||
"templates/editor/setting-table-form.php"; ?>
|
||||
<?php require_once SHEET_CHECKER_PRO_PATH .
|
||||
"templates/editor/setting-table-result.php"; ?>
|
||||
<?php require_once SHEET_CHECKER_PRO_PATH .
|
||||
"templates/editor/setting-table-security.php"; ?>
|
||||
<?php require_once SHEET_CHECKER_PRO_PATH .
|
||||
"templates/editor/js-template-repeater-card.php"; ?>
|
||||
<?php require_once SHEET_CHECKER_PRO_PATH .
|
||||
"templates/editor/js-template-setting-output.php"; ?>
|
||||
|
||||
<!-- Templates for preview functionality -->
|
||||
<script id="vertical-table-template" type="text/x-handlebars-template">
|
||||
<div class="dw-checker-results-container">
|
||||
{{#each results}}
|
||||
<div class="result-page" data-page="{{@index}}">
|
||||
<table class="dw-checker-result-table" {{{getStyle ../resultDivider ../resultDividerWidth}}}>
|
||||
<tbody>
|
||||
{{#each this}}
|
||||
{{#unless (getColumnSetting @key 'hide')}}
|
||||
<tr>
|
||||
<th {{{getStyleHeader ../resultDivider ../resultDividerWidth ../headerColor}}}}>
|
||||
<span class="dw-checker-result-header">{{@key}}</span>
|
||||
</th>
|
||||
<td {{{getStyleValue ../resultDivider ../resultDividerWidth ../valueColor}}}>
|
||||
{{#if (eq (getColumnSetting @key 'type') 'link_button')}}
|
||||
<span class="dw-checker-result-value">{{getColumnSetting @key 'prefix' this}} <a href="{{getValueWithPrefix @key}}" class="btn btn-primary">{{getColumnSetting @key 'button_text'}}</a></span>
|
||||
{{else if (eq (getColorSetting @key 'type') 'whatsapp_button')}}
|
||||
<span class="dw-checker-result-value">{{getColumnSetting @key 'prefix' this}} <a href="https://wa.me/{{getValueWithPrefix @key}}" class="btn btn-success">{{getColumnSetting @key 'button_text'}}</a></span>
|
||||
{{else}}
|
||||
<span class="dw-checker-result-value">{{getColumnSetting @key 'prefix' this}} {{formatValue this}}</span>
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/unless}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script id="div-template" type="text/x-handlebars-template">
|
||||
<div class="dw-checker-results-container">
|
||||
{{#each results}}
|
||||
<div class="result-page" data-page="{{@index}}">
|
||||
<div class="dw-checker-result-container" data-pagination="{{@index}}">
|
||||
{{#each this}}
|
||||
<div class="dw-checker-result-div">
|
||||
<div class="result-header">
|
||||
<span class="dw-checker-result-header">{{@key}}</span>
|
||||
</div>
|
||||
<div class="result-value">
|
||||
<span class="dw-checker-result-value">{{#ifCond (eq (getColumnSetting @key 'type') 'text')}}{{formatValue this}}{{else}}{{#ifCond (eq (getColumnSetting @key 'type') 'link_button')}}{{getColumnSetting @key 'prefix' this}} <a href="{{this}}" class="btn btn-primary">{{getColumnSetting @key 'button_text'}}</a>{{else if (eq (getColumnSetting @key 'type') 'whatsapp_button')}}{{getColumnSetting @key 'prefix' this}} <a href="https://wa.me/{{this}}" class="btn btn-success">{{getColumnSetting @key 'button_text'}}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user