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:
dwindown
2026-01-07 15:10:47 +07:00
parent 31b3398c2f
commit 0ba62b435a
26 changed files with 8962 additions and 2804 deletions

View File

@@ -0,0 +1,487 @@
<?php
/**
* Security Dashboard for Sheet Data Checker Pro
*
* Provides an admin dashboard to monitor and configure security settings
* for all checker forms on the site
*
* @since 1.5.0
*/
class CHECKER_SECURITY_DASHBOARD {
/**
* Initialize the dashboard
*/
public static function init() {
add_action('admin_menu', [__CLASS__, 'add_admin_menu']);
add_action('admin_enqueue_scripts', [__CLASS__, 'enqueue_scripts']);
add_action('wp_ajax_checker_security_dashboard', [__CLASS__, 'ajax_handler']);
// Load Turnstile test page
require_once __DIR__ . '/test-turnstile.php';
}
/**
* Add admin menu item
*/
public static function add_admin_menu() {
add_submenu_page(
'edit.php?post_type=checker',
'Security Dashboard',
'Security',
'manage_options',
'checker-security',
[__CLASS__, 'render_dashboard']
);
}
/**
* Enqueue admin scripts
*/
public static function enqueue_scripts($hook) {
if ('checker_page_checker-security' !== $hook) {
return;
}
wp_enqueue_style('bootstrap', 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css');
wp_enqueue_script('chartjs', 'https://cdn.jsdelivr.net/npm/chart.js', [], '3.7.1');
}
/**
* Render the dashboard
*/
public static function render_dashboard() {
$checkers = self::get_all_checkers();
$security_status = self::get_security_overview($checkers);
?>
<div class="wrap">
<h1 class="wp-heading-inline">Security Dashboard</h1>
<hr class="wp-header-end">
<div class="dashboard-widgets-wrap">
<!-- Security Overview -->
<div class="metabox-holder">
<div class="postbox-container" style="width: 100%;">
<div class="postbox">
<h2 class="hndle ui-sortable-handle">Security Overview</h2>
<div class="inside">
<div class="row">
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title">Total Checkers</h5>
<h2 class="text-primary"><?php echo count($checkers); ?></h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title">Rate Limited</h5>
<h2 class="text-success"><?php echo $security_status['rate_limited']; ?></h2>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title">CAPTCHA Protected</h5>
<h2 class="text-info"><?php echo $security_status['captcha_protected']; ?></h2>
<small class="text-muted">
reCAPTCHA: <?php echo $security_status['recaptcha_count']; ?> |
Turnstile: <?php echo $security_status['turnstile_count']; ?>
</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title">Unprotected</h5>
<h2 class="text-danger"><?php echo $security_status['unprotected']; ?></h2>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Security Status Chart -->
<div class="metabox-holder">
<div class="postbox-container" style="width: 100%;">
<div class="postbox">
<h2 class="hndle ui-sortable-handle">Security Status Distribution</h2>
<div class="inside">
<canvas id="securityChart" width="400" height="150"></canvas>
</div>
</div>
</div>
</div>
<!-- Rate Limit Logs -->
<div class="metabox-holder">
<div class="postbox-container" style="width: 100%;">
<div class="postbox">
<h2 class="hndle ui-sortable-handle">
Recent Rate Limit Blocks
<button type="button" class="button button-secondary refresh-logs" style="float: right;">
<span class="dashicons dashicons-update"></span> Refresh
</button>
</h2>
<div class="inside">
<div id="rate-limit-logs">
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>IP Address</th>
<th>Checker</th>
<th>Time</th>
<th>Reason</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="4">Loading...</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Individual Checker Status -->
<div class="metabox-holder">
<div class="postbox-container" style="width: 100%;">
<div class="postbox">
<h2 class="hndle ui-sortable-handle">Individual Checker Security Status</h2>
<div class="inside">
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>Checker</th>
<th>Rate Limit</th>
<th>reCAPTCHA</th>
<th>Turnstile</th>
<th>Honeypot</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($checkers as $checker): ?>
<tr>
<td>
<strong>
<a href="<?php echo get_edit_post_link($checker->ID); ?>">
<?php echo get_the_title($checker->ID); ?>
</a>
</strong>
</td>
<td>
<?php
$rate_limit = get_post_meta($checker->ID, 'checker', true)['security']['rate_limit']['enabled'] ?? 'no';
if ($rate_limit === 'yes') {
$max_attempts = get_post_meta($checker->ID, 'checker', true)['security']['rate_limit']['max_attempts'] ?? 5;
echo '<span class="dashicons dashicons-shield-alt text-success"></span> ' . $max_attempts . ' per ';
echo get_post_meta($checker->ID, 'checker', true)['security']['rate_limit']['time_window'] ?? 15 . ' min';
} else {
echo '<span class="dashicons dashicons-shield-no text-danger"></span> Disabled';
}
?>
</td>
<td>
<?php
$recaptcha = get_post_meta($checker->ID, 'checker', true)['security']['recaptcha']['enabled'] ?? 'no';
if ($recaptcha === 'yes') {
$min_score = get_post_meta($checker->ID, 'checker', true)['security']['recaptcha']['min_score'] ?? 0.5;
echo '<span class="dashicons dashicons-yes-alt text-success"></span> Score ' . $min_score;
} else {
echo '<span class="dashicons dashicons-no-alt text-danger"></span> Disabled';
}
?>
</td>
<td>
<?php
$checker_data = get_post_meta($checker->ID, 'checker', true);
$turnstile = isset($checker_data['security']['turnstile']['enabled']) ? $checker_data['security']['turnstile']['enabled'] : 'no';
// Debug: Check if turnstile data exists
if (!isset($checker_data['security'])) {
echo '<small class="text-muted">No security data</small>';
} elseif (!isset($checker_data['security']['turnstile'])) {
echo '<small class="text-muted">No turnstile data</small>';
} else {
if ($turnstile === 'yes') {
echo '<span class="dashicons dashicons-yes-alt text-success"></span> Enabled';
} else {
echo '<span class="dashicons dashicons-no-alt text-danger"></span> Disabled';
}
}
?>
</td>
<td>
<?php
$honeypot = isset($checker_data['security']['honeypot']['enabled']) ? $checker_data['security']['honeypot']['enabled'] : 'no';
if ($honeypot === 'yes') {
echo '<span class="dashicons dashicons-hidden text-success"></span> Enabled';
} else {
echo '<span class="dashicons dashicons-visibility text-muted"></span> Disabled';
}
?>
</td>
<td>
<?php
$is_protected = ($rate_limit === 'yes' || $recaptcha === 'yes' || $turnstile === 'yes' || $honeypot === 'yes');
if ($is_protected) {
echo '<span class="badge bg-success">Protected</span>';
} else {
echo '<span class="badge bg-danger">Unprotected</span>';
}
?>
</td>
<td>
<button type="button" class="button button-small view-checker-security" data-checker-id="<?php echo $checker->ID; ?>">
<span class="dashicons dashicons-visibility"></span> View
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
jQuery(document).ready(function($) {
// Security Chart
var ctx = document.getElementById('securityChart').getContext('2d');
var securityChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['Rate Limited', 'CAPTCHA Protected', 'Unprotected'],
datasets: [{
data: [
<?php echo $security_status['rate_limited']; ?>,
<?php echo $security_status['captcha_protected']; ?>,
<?php echo $security_status['unprotected']; ?>
],
backgroundColor: [
'#28a745',
'#17a2b8',
'#dc3545'
]
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
// Refresh rate limit logs
$('.refresh-logs').on('click', function() {
loadRateLimitLogs();
});
// View checker security details
$('.view-checker-security').on('click', function() {
var checkerId = $(this).data('checker-id');
window.location.href = '<?php echo admin_url('post.php?action=edit&post='); ?>' + checkerId + '#checker-security';
});
// Load rate limit logs on page load
loadRateLimitLogs();
function loadRateLimitLogs() {
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'checker_security_dashboard',
security_action: 'get_rate_limit_logs'
},
success: function(response) {
if (response.success) {
var logsHtml = '';
if (response.data.logs && response.data.logs.length > 0) {
response.data.logs.forEach(function(log) {
logsHtml += '<tr>';
logsHtml += '<td>' + log.ip + '</td>';
logsHtml += '<td>' + log.checker + '</td>';
logsHtml += '<td>' + log.time + '</td>';
logsHtml += '<td>' + log.reason + '</td>';
logsHtml += '</tr>';
});
} else {
logsHtml = '<tr><td colspan="4">No recent rate limit blocks</td></tr>';
}
$('#rate-limit-logs tbody').html(logsHtml);
}
}
});
}
});
</script>
<?php
}
/**
* Get all checker posts
*/
private static function get_all_checkers() {
return get_posts([
'post_type' => 'checker',
'post_status' => 'publish',
'numberposts' => -1
]);
}
/**
* Get security overview
*/
private static function get_security_overview($checkers) {
$rate_limited = 0;
$captcha_protected = 0;
$honeypot_enabled = 0;
$unprotected = 0;
$recaptcha_count = 0;
$turnstile_count = 0;
foreach ($checkers as $checker) {
$checker_data = get_post_meta($checker->ID, 'checker', true);
$has_rate_limit = isset($checker_data['security']['rate_limit']['enabled']) && $checker_data['security']['rate_limit']['enabled'] === 'yes';
$has_recaptcha = isset($checker_data['security']['recaptcha']['enabled']) && $checker_data['security']['recaptcha']['enabled'] === 'yes';
$has_turnstile = isset($checker_data['security']['turnstile']['enabled']) && $checker_data['security']['turnstile']['enabled'] === 'yes';
$has_honeypot = isset($checker_data['security']['honeypot']['enabled']) && $checker_data['security']['honeypot']['enabled'] === 'yes';
if ($has_rate_limit) {
$rate_limited++;
}
if ($has_recaptcha) {
$recaptcha_count++;
}
if ($has_turnstile) {
$turnstile_count++;
}
if ($has_honeypot) {
$honeypot_enabled++;
}
if ($has_recaptcha || $has_turnstile) {
$captcha_protected++;
}
if (!$has_rate_limit && !$has_recaptcha && !$has_turnstile && !$has_honeypot) {
$unprotected++;
}
}
return [
'rate_limited' => $rate_limited,
'captcha_protected' => $captcha_protected,
'honeypot_enabled' => $honeypot_enabled,
'unprotected' => $unprotected,
'recaptcha_count' => $recaptcha_count,
'turnstile_count' => $turnstile_count
];
}
/**
* AJAX handler for dashboard actions
*/
public static function ajax_handler() {
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$security_action = $_POST['security_action'] ?? '';
switch ($security_action) {
case 'get_rate_limit_logs':
self::get_rate_limit_logs();
break;
}
wp_die();
}
/**
* Get rate limit logs
*/
private static function get_rate_limit_logs() {
global $wpdb;
// This is a simplified version - in a real implementation,
// you might want to store rate limit blocks in a custom table
$logs = [];
// Get recent transients that indicate rate limit blocks
$transients = $wpdb->get_results(
"SELECT option_name, option_value
FROM {$wpdb->options}
WHERE option_name LIKE '%_transient_checker_block_%'
ORDER BY option_name DESC
LIMIT 10"
);
foreach ($transients as $transient) {
// Extract checker ID from transient name
if (preg_match('/_transient_checker_block_(\d+)_/', $transient->option_name, $matches)) {
$checker_id = $matches[1];
$checker = get_post($checker_id);
$ip_hash = substr($transient->option_name, strrpos($transient->option_name, '_') + 1);
$blocked_until = $transient->option_value;
$logs[] = [
'ip' => self::mask_ip(self::decode_ip_from_hash($ip_hash)),
'checker' => $checker ? $checker->post_title : 'Unknown',
'time' => date('Y-m-d H:i:s', $blocked_until),
'reason' => 'Rate limit exceeded'
];
}
}
wp_send_json_success(['logs' => $logs]);
}
/**
* Mask IP address for privacy
*/
private static function mask_ip($ip) {
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$parts = explode('.', $ip);
return $parts[0] . '.' . $parts[1] . '.***.***';
} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
$parts = explode(':', $ip);
return $parts[0] . ':' . $parts[1] . '::***';
}
return $ip;
}
/**
* Decode IP from hash (simplified version)
*/
private static function decode_ip_from_hash($hash) {
// This is a simplified version - in reality, you can't easily reverse a hash
// For demonstration purposes, we'll return a placeholder
return '192.168.1.***';
}
}
// Initialize the dashboard
CHECKER_SECURITY_DASHBOARD::init();