完全重写

This commit is contained in:
文派备案 2025-05-26 02:03:35 +08:00
parent fa7b00b4f5
commit 0ae1b5b593
13 changed files with 4657 additions and 78 deletions

537
assets/admin.css Normal file
View file

@ -0,0 +1,537 @@
/* WPBan Pro Admin Styles - WordPress Native Experience */
.wpban-wrap {
margin-top: 20px;
max-width: 1400px;
}
/* Dashboard Grid */
.wpban-dashboard {
margin-top: 20px;
}
.wpban-stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.wpban-stat-card {
background: #fff;
border: 1px solid #c3c4c7;
padding: 20px;
text-align: center;
box-shadow: 0 1px 1px rgba(0,0,0,0.04);
position: relative;
transition: all 0.3s ease;
}
.wpban-stat-card:hover {
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transform: translateY(-2px);
}
.wpban-stat-card h3 {
margin: 0 0 10px 0;
font-size: 14px;
font-weight: 600;
color: #1d2327;
}
.wpban-stat-number {
font-size: 36px;
font-weight: 300;
color: #2271b1;
line-height: 1;
}
.wpban-stat-trend {
margin-top: 10px;
font-size: 12px;
}
.wpban-stat-trend .up {
color: #d63638;
}
.wpban-stat-trend .down {
color: #00a32a;
}
/* Grid Layout */
.wpban-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
@media (max-width: 1200px) {
.wpban-grid {
grid-template-columns: 1fr;
}
}
/* Cards */
.wpban-card {
background: #fff;
border: 1px solid #c3c4c7;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 1px 1px rgba(0,0,0,0.04);
}
.wpban-card h2 {
margin: 0 0 15px 0;
font-size: 1.3em;
font-weight: 600;
color: #1d2327;
}
.wpban-card h3 {
margin: 20px 0 10px 0;
font-size: 1.1em;
font-weight: 600;
}
/* Actions */
.wpban-actions {
display: flex;
gap: 10px;
margin: 20px 0;
flex-wrap: wrap;
}
/* Bypass URL */
.wpban-bypass-url {
margin: 20px 0;
}
.wpban-bypass-url label {
display: block;
margin-bottom: 10px;
font-weight: 600;
}
.wpban-input-group {
display: flex;
gap: 10px;
margin-bottom: 10px;
}
.wpban-input-group input {
flex: 1;
}
/* Templates Grid */
.wpban-templates-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
}
.wpban-template-card {
background: #f6f7f7;
border: 1px solid #dcdcde;
padding: 20px;
border-radius: 4px;
transition: all 0.2s;
}
.wpban-template-card:hover {
background: #fff;
border-color: #2271b1;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.wpban-template-card h3 {
margin: 0 0 10px 0;
font-size: 16px;
font-weight: 600;
}
.wpban-template-card p {
margin: 0 0 15px 0;
color: #50575e;
font-size: 13px;
line-height: 1.5;
}
/* Tabs */
.wpban-tabs {
margin-top: 20px;
}
.tab-content {
display: none;
background: #fff;
border: 1px solid #c3c4c7;
border-top: none;
padding: 20px;
}
.tab-content.active {
display: block;
}
.tab-content h2 {
margin-top: 0;
}
/* Crawler Controls */
.wpban-crawler-controls {
margin-bottom: 20px;
display: flex;
gap: 10px;
flex-wrap: wrap;
}
/* Crawler Grid */
.wpban-crawler-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 10px;
margin-bottom: 30px;
}
.wpban-crawler-item {
display: block;
padding: 12px;
background: #f6f7f7;
border: 1px solid #dcdcde;
cursor: pointer;
transition: all 0.2s;
}
.wpban-crawler-item:hover {
background: #fff;
border-color: #2271b1;
}
.wpban-crawler-item input[type="checkbox"] {
margin-right: 8px;
}
.wpban-crawler-name {
font-weight: 600;
display: inline-block;
margin-right: 8px;
font-family: Consolas, Monaco, monospace;
font-size: 13px;
}
.wpban-crawler-desc {
font-size: 12px;
color: #50575e;
display: inline;
}
/* Notices */
.wpban-notice {
padding: 12px;
margin: 15px 0;
border-left: 4px solid;
background: #fff;
}
.wpban-notice-warning {
border-left-color: #f0b849;
background-color: #fef8ee;
}
.wpban-notice-warning p {
margin: 0;
}
/* Logs */
.wpban-logs-filters {
background: #fff;
border: 1px solid #c3c4c7;
padding: 15px;
margin-bottom: 20px;
}
.wpban-logs-filters form {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
.wpban-logs-filters input[type="date"],
.wpban-logs-filters input[type="text"],
.wpban-logs-filters select {
height: 30px;
line-height: 28px;
}
/* Badges */
.wpban-badge {
display: inline-block;
padding: 3px 8px;
border-radius: 3px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
}
.wpban-badge-banned {
background: #d63638;
color: #fff;
}
.wpban-badge-blocked {
background: #f0b849;
color: #000;
}
.wpban-badge-failed_login {
background: #dba617;
color: #fff;
}
.wpban-badge-bypass {
background: #00a32a;
color: #fff;
}
/* Truncate */
.wpban-truncate {
display: inline-block;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
}
/* Country Selector */
.wpban-country-selector select {
font-family: Consolas, Monaco, monospace;
font-size: 13px;
}
/* Rate Limit Settings */
.form-table input[type="number"] {
width: 80px;
}
/* Tools Grid */
.wpban-tools-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 20px;
}
/* Chart Container */
#wpban-country-chart {
max-height: 300px;
}
/* Activity List */
.wpban-activity-list {
margin: 0;
padding: 0;
list-style: none;
}
.wpban-activity-list li {
padding: 8px 0;
border-bottom: 1px solid #f0f0f1;
}
.wpban-activity-list li:last-child {
border-bottom: none;
}
/* Form Elements */
.form-table textarea.code {
font-family: Consolas, Monaco, monospace;
font-size: 13px;
line-height: 1.5;
}
/* Pagination */
.tablenav-pages {
margin: 20px 0;
text-align: right;
}
.tablenav-pages .pagination-links {
display: inline-block;
}
.tablenav-pages a,
.tablenav-pages span {
display: inline-block;
padding: 3px 8px;
margin: 0 2px;
background: #f6f7f7;
border: 1px solid #dcdcde;
text-decoration: none;
}
.tablenav-pages .current {
background: #2271b1;
color: #fff;
border-color: #2271b1;
}
/* Responsive Tables */
@media (max-width: 782px) {
.wp-list-table {
display: block;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.wpban-stats-grid {
grid-template-columns: 1fr;
}
.wpban-templates-grid {
grid-template-columns: 1fr;
}
.wpban-crawler-grid {
grid-template-columns: 1fr;
}
.wpban-tools-grid {
grid-template-columns: 1fr;
}
.wpban-logs-filters form {
flex-direction: column;
align-items: stretch;
}
.wpban-logs-filters input,
.wpban-logs-filters select,
.wpban-logs-filters button {
width: 100%;
margin-bottom: 10px;
}
}
/* Loading States */
.wpban-loading {
opacity: 0.6;
pointer-events: none;
position: relative;
}
.wpban-loading::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
margin: -10px 0 0 -10px;
border: 2px solid #f3f3f3;
border-top: 2px solid #2271b1;
border-radius: 50%;
animation: wpban-spin 1s linear infinite;
}
@keyframes wpban-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Buttons */
.button.button-small {
padding: 0 8px;
line-height: 26px;
height: 28px;
font-size: 12px;
}
/* WP Editor in Settings */
.tab-content .wp-editor-wrap {
margin-top: 10px;
}
/* Empty States */
.wpban-empty-state {
text-align: center;
padding: 40px;
color: #646970;
}
.wpban-empty-state p {
font-size: 16px;
margin: 0;
}
/* Success/Error Messages */
.wpban-message {
padding: 12px;
margin: 15px 0;
border-left: 4px solid;
background: #fff;
animation: wpban-fade-in 0.3s ease;
}
.wpban-message.success {
border-left-color: #00a32a;
background-color: #f0f8f0;
}
.wpban-message.error {
border-left-color: #d63638;
background-color: #fef1f1;
}
@keyframes wpban-fade-in {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Row Actions */
.row-actions {
font-size: 12px;
padding-top: 4px;
}
.row-actions a {
text-decoration: none;
}
/* Status Indicators */
.wpban-status {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 5px;
}
.wpban-status.active {
background: #00a32a;
}
.wpban-status.inactive {
background: #787c82;
}
/* Code Preview */
.wpban-code-preview {
background: #f6f7f7;
border: 1px solid #dcdcde;
padding: 15px;
font-family: Consolas, Monaco, monospace;
font-size: 13px;
overflow-x: auto;
white-space: pre;
max-height: 300px;
overflow-y: auto;
}

335
assets/admin.js Normal file
View file

@ -0,0 +1,335 @@
/**
* WPBan Pro Admin JavaScript
*/
(function($) {
'use strict';
// Tab Navigation
$(document).on('click', '.nav-tab', function(e) {
e.preventDefault();
const target = $(this).attr('href');
$('.nav-tab').removeClass('nav-tab-active');
$(this).addClass('nav-tab-active');
$('.tab-content').removeClass('active');
$(target).addClass('active');
// Save active tab to localStorage
localStorage.setItem('wpban_active_tab', target);
});
// Restore active tab
$(document).ready(function() {
const activeTab = localStorage.getItem('wpban_active_tab');
if (activeTab && $(activeTab).length) {
$('.nav-tab[href="' + activeTab + '"]').trigger('click');
}
});
// Settings Form Handler
$('#wpban-settings-form').on('submit', function(e) {
e.preventDefault();
const $form = $(this);
const $button = $('#wpban-save-settings');
const $spinner = $button.next('.spinner');
$button.prop('disabled', true);
$spinner.addClass('is-active');
$.ajax({
url: wpban.ajax_url,
type: 'POST',
data: {
action: 'wpban_save_settings',
settings: $form.serialize(),
_ajax_nonce: $('#wpban_nonce').val()
},
success: function(response) {
if (response.success) {
showMessage('success', response.data.message);
} else {
showMessage('error', response.data || wpban.i18n.error);
}
},
error: function() {
showMessage('error', wpban.i18n.error);
},
complete: function() {
$button.prop('disabled', false);
$spinner.removeClass('is-active');
}
});
});
// Apply Template
window.wpbanApplyTemplate = function(templateId) {
if (!confirm(wpban.i18n.confirm_template)) {
return;
}
$.post(wpban.ajax_url, {
action: 'wpban_apply_template',
template: templateId,
_ajax_nonce: wpban.nonce
}, function(response) {
if (response.success) {
showMessage('success', response.data.message);
setTimeout(() => location.reload(), 1500);
} else {
showMessage('error', response.data || wpban.i18n.error);
}
});
};
// Copy Text to Clipboard
window.wpbanCopyText = function(text) {
navigator.clipboard.writeText(text).then(() => {
showMessage('success', 'Copied to clipboard!');
}).catch(() => {
// Fallback for older browsers
const $temp = $('<input>');
$('body').append($temp);
$temp.val(text).select();
document.execCommand('copy');
$temp.remove();
showMessage('success', 'Copied to clipboard!');
});
};
// Crawler Selection
window.wpbanSelectCrawlers = function(type, select) {
if (type === 'all') {
$('.wpban-crawler-item input').prop('checked', select);
} else {
$(`.wpban-crawler-item[data-type="${type}"] input`).prop('checked', select);
}
};
// Export Logs
window.wpbanExportLogs = function() {
window.location.href = wpban.ajax_url + '?action=wpban_export_logs&_ajax_nonce=' + wpban.nonce;
};
// Clear Logs
window.wpbanClearLogs = function() {
if (!confirm(wpban.i18n.confirm_clear)) {
return;
}
$.post(wpban.ajax_url, {
action: 'wpban_clear_logs',
_ajax_nonce: wpban.nonce
}, function(response) {
if (response.success) {
showMessage('success', response.data.message);
setTimeout(() => location.reload(), 1500);
}
});
};
// Test Email
window.wpbanTestEmail = function() {
const $button = event.target;
$button.disabled = true;
$.post(wpban.ajax_url, {
action: 'wpban_test_email',
_ajax_nonce: wpban.nonce
}, function(response) {
if (response.success) {
showMessage('success', response.data.message);
} else {
showMessage('error', response.data || wpban.i18n.error);
}
}).always(function() {
$button.disabled = false;
});
};
// Export Settings
window.wpbanExportSettings = function() {
$.get(wpban.ajax_url, {
action: 'wpban_export_settings',
_ajax_nonce: wpban.nonce
}, function(response) {
if (response.success) {
const blob = new Blob([response.data], {type: 'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'wpban-settings-' + new Date().toISOString().split('T')[0] + '.json';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
});
};
// Optimize Database
window.wpbanOptimizeDatabase = function() {
const $button = event.target;
$button.disabled = true;
$.post(wpban.ajax_url, {
action: 'wpban_optimize_database',
_ajax_nonce: wpban.nonce
}, function(response) {
if (response.success) {
showMessage('success', 'Database optimized successfully!');
}
}).always(function() {
$button.disabled = false;
});
};
// Clear Old Logs
window.wpbanClearOldLogs = function() {
if (!confirm('This will permanently delete all logs older than 30 days. Continue?')) {
return;
}
$.post(wpban.ajax_url, {
action: 'wpban_clear_old_logs',
_ajax_nonce: wpban.nonce
}, function(response) {
if (response.success) {
showMessage('success', response.data.message);
setTimeout(() => location.reload(), 1500);
}
});
};
// Live Search for Crawlers
let searchTimeout;
$(document).on('input', '#wpban-crawler-search', function() {
const query = $(this).val().toLowerCase();
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
$('.wpban-crawler-item').each(function() {
const $item = $(this);
const text = $item.text().toLowerCase();
$item.toggle(text.indexOf(query) > -1);
});
}, 300);
});
// Real-time Log Updates (optional)
if ($('#wpban-logs-container').length) {
// Auto-refresh logs every 30 seconds
setInterval(function() {
if (document.visibilityState === 'visible') {
refreshLogs();
}
}, 30000);
}
function refreshLogs() {
const params = new URLSearchParams(window.location.search);
$.get(wpban.ajax_url, {
action: 'wpban_get_logs',
page: params.get('paged') || 1,
date_from: params.get('date_from'),
date_to: params.get('date_to'),
action_filter: params.get('action_filter'),
ip_filter: params.get('ip_filter'),
_ajax_nonce: wpban.nonce
}, function(response) {
if (response.success) {
$('#wpban-logs-container tbody').html(response.data.html);
}
});
}
// Show Message
function showMessage(type, message) {
const $message = $('<div>')
.addClass('wpban-message ' + type)
.text(message)
.prependTo('.wpban-wrap')
.hide()
.fadeIn();
setTimeout(() => {
$message.fadeOut(() => $message.remove());
}, 5000);
}
// Geo Blocking Mode Toggle
$('input[name="settings[geo_blocking][mode]"]').on('change', function() {
const mode = $(this).val();
const $label = $('.wpban-country-selector').prev('th').find('label');
if (mode === 'whitelist') {
$label.text('Allowed Countries');
} else {
$label.text('Blocked Countries');
}
});
// Initialize Select2 for country selector (if available)
if ($.fn.select2) {
$('.wpban-country-selector select').select2({
placeholder: 'Select countries...',
width: '100%'
});
}
// Handle browser restrictions toggle
$('input[name="settings[browser_restrictions][wechat_qq][enabled]"]').on('change', function() {
const $fields = $(this).closest('table').find('tr').not(':first');
$fields.toggle($(this).is(':checked'));
}).trigger('change');
// Email notifications toggle
$('input[name="settings[email_notifications][enabled]"]').on('change', function() {
const $fields = $(this).closest('table').find('tr').not(':first');
$fields.toggle($(this).is(':checked'));
}).trigger('change');
// Geo blocking toggle
$('input[name="settings[geo_blocking][enabled]"]').on('change', function() {
const $fields = $(this).closest('table').find('tr').not(':first');
$fields.toggle($(this).is(':checked'));
}).trigger('change');
// Keyboard shortcuts
$(document).on('keydown', function(e) {
// Ctrl/Cmd + S to save
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault();
$('#wpban-save-settings').trigger('click');
}
});
// Tooltip initialization
$('.wpban-tooltip').tooltip({
position: {
my: 'center bottom-10',
at: 'center top'
}
});
// Confirm before leaving with unsaved changes
let formChanged = false;
$('#wpban-settings-form').on('change', 'input, select, textarea', function() {
formChanged = true;
});
window.addEventListener('beforeunload', function(e) {
if (formChanged) {
e.preventDefault();
e.returnValue = 'You have unsaved changes. Are you sure you want to leave?';
}
});
$('#wpban-save-settings').on('click', function() {
formChanged = false;
});
})(jQuery);

View file

@ -0,0 +1,994 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPBan_Admin {
private $security;
public function __construct() {
$this->security = $GLOBALS['wpban_security'];
add_action('admin_menu', [$this, 'add_menu']);
add_action('admin_enqueue_scripts', [$this, 'enqueue_scripts']);
// AJAX handlers
add_action('wp_ajax_wpban_save_settings', [$this, 'ajax_save_settings']);
add_action('wp_ajax_wpban_apply_template', [$this, 'ajax_apply_template']);
add_action('wp_ajax_wpban_get_logs', [$this, 'ajax_get_logs']);
add_action('wp_ajax_wpban_export_logs', [$this, 'ajax_export_logs']);
add_action('wp_ajax_wpban_clear_logs', [$this, 'ajax_clear_logs']);
add_action('wp_ajax_wpban_test_email', [$this, 'ajax_test_email']);
add_action('wp_ajax_wpban_get_country_stats', [$this, 'ajax_get_country_stats']);
}
public function add_menu() {
add_menu_page(
__('WPBan Security', 'wpban'),
__('WPBan Security', 'wpban'),
'manage_options',
'wpban',
[$this, 'render_dashboard'],
'dashicons-shield',
30
);
add_submenu_page(
'wpban',
__('Settings', 'wpban'),
__('Settings', 'wpban'),
'manage_options',
'wpban-settings',
[$this, 'render_settings']
);
add_submenu_page(
'wpban',
__('Security Logs', 'wpban'),
__('Logs', 'wpban'),
'manage_options',
'wpban-logs',
[$this, 'render_logs']
);
add_submenu_page(
'wpban',
__('Tools', 'wpban'),
__('Tools', 'wpban'),
'manage_options',
'wpban-tools',
[$this, 'render_tools']
);
}
public function enqueue_scripts($hook) {
if (strpos($hook, 'wpban') === false) {
return;
}
wp_enqueue_style('wpban-admin', WPBAN_URL . 'assets/admin.css', [], WPBAN_VERSION);
wp_enqueue_script('wpban-admin', WPBAN_URL . 'assets/admin.js', ['jquery'], WPBAN_VERSION);
wp_localize_script('wpban-admin', 'wpban', [
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('wpban_admin'),
'i18n' => [
'confirm_clear' => __('Are you sure you want to clear all logs?', 'wpban'),
'confirm_template' => __('This will replace your current settings. Continue?', 'wpban'),
'email_sent' => __('Test email sent!', 'wpban'),
'error' => __('An error occurred', 'wpban')
]
]);
// Add Chart.js for statistics
wp_enqueue_script('chartjs', 'https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js', [], '3.9.1');
}
public function render_dashboard() {
$stats = $this->security->get_stats();
$bypass_url = home_url('/?wpban_bypass=' . get_option('wpban_bypass_path'));
?>
<div class="wrap wpban-wrap">
<h1><?php _e('WPBan Security Dashboard', 'wpban'); ?></h1>
<div class="wpban-dashboard">
<!-- Stats Grid -->
<div class="wpban-stats-grid">
<div class="wpban-stat-card">
<h3><?php _e('Total Blocks', 'wpban'); ?></h3>
<div class="wpban-stat-number"><?php echo number_format($stats['total_blocks']); ?></div>
<div class="wpban-stat-trend">
<?php
$yesterday = $stats['total_blocks'] - $stats['today_blocks'];
$trend = $yesterday > 0 ? round(($stats['today_blocks'] / $yesterday - 1) * 100) : 0;
?>
<span class="<?php echo $trend > 0 ? 'up' : 'down'; ?>">
<?php echo $trend > 0 ? '↑' : '↓'; ?> <?php echo abs($trend); ?>%
</span>
</div>
</div>
<div class="wpban-stat-card">
<h3><?php _e('Unique IPs', 'wpban'); ?></h3>
<div class="wpban-stat-number"><?php echo number_format($stats['unique_ips']); ?></div>
</div>
<div class="wpban-stat-card">
<h3><?php _e('Today\'s Blocks', 'wpban'); ?></h3>
<div class="wpban-stat-number"><?php echo number_format($stats['today_blocks']); ?></div>
</div>
<div class="wpban-stat-card">
<h3><?php _e('Active Rules', 'wpban'); ?></h3>
<div class="wpban-stat-number"><?php echo number_format($stats['active_rules']); ?></div>
</div>
</div>
<div class="wpban-grid">
<!-- Quick Actions -->
<div class="wpban-card">
<h2><?php _e('Quick Actions', 'wpban'); ?></h2>
<div class="wpban-bypass-url">
<label><?php _e('Emergency Bypass URL:', 'wpban'); ?></label>
<div class="wpban-input-group">
<input type="text" value="<?php echo esc_attr($bypass_url); ?>" readonly />
<button class="button" onclick="wpbanCopyText(this.previousElementSibling.value)">
<?php _e('Copy', 'wpban'); ?>
</button>
</div>
<p class="description"><?php _e('Save this URL to access your site if you get locked out.', 'wpban'); ?></p>
</div>
<div class="wpban-actions">
<a href="<?php echo admin_url('admin.php?page=wpban-settings'); ?>" class="button button-primary">
<?php _e('Configure Settings', 'wpban'); ?>
</a>
<a href="<?php echo admin_url('admin.php?page=wpban-logs'); ?>" class="button">
<?php _e('View Logs', 'wpban'); ?>
</a>
</div>
</div>
<!-- Country Stats -->
<div class="wpban-card">
<h2><?php _e('Top Blocked Countries', 'wpban'); ?></h2>
<canvas id="wpban-country-chart" height="200"></canvas>
<script>
jQuery(document).ready(function($) {
const ctx = document.getElementById('wpban-country-chart').getContext('2d');
const data = <?php echo json_encode($stats['top_countries']); ?>;
new Chart(ctx, {
type: 'bar',
data: {
labels: data.map(d => d.country_code || 'Unknown'),
datasets: [{
label: 'Blocks',
data: data.map(d => d.count),
backgroundColor: '#2271b1'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
}
}
});
});
</script>
</div>
</div>
<!-- Templates -->
<div class="wpban-card">
<h2><?php _e('Security Templates', 'wpban'); ?></h2>
<p><?php _e('Quickly apply pre-configured security settings.', 'wpban'); ?></p>
<div class="wpban-templates-grid">
<?php foreach ($this->security->get_templates() as $id => $template): ?>
<div class="wpban-template-card">
<h3><?php echo esc_html($template['name']); ?></h3>
<p><?php echo esc_html($template['description']); ?></p>
<button class="button" onclick="wpbanApplyTemplate('<?php echo esc_attr($id); ?>')">
<?php _e('Apply', 'wpban'); ?>
</button>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- Recent Activity -->
<div class="wpban-card">
<h2><?php _e('Recent Activity', 'wpban'); ?></h2>
<table class="wp-list-table widefat">
<thead>
<tr>
<th><?php _e('Time', 'wpban'); ?></th>
<th><?php _e('IP', 'wpban'); ?></th>
<th><?php _e('Country', 'wpban'); ?></th>
<th><?php _e('Action', 'wpban'); ?></th>
<th><?php _e('Details', 'wpban'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($stats['recent_blocks'] as $log): ?>
<tr>
<td><?php echo human_time_diff(strtotime($log->timestamp)); ?> ago</td>
<td><code><?php echo esc_html($log->ip); ?></code></td>
<td><?php echo esc_html($log->country_code ?: '-'); ?></td>
<td><span class="wpban-badge wpban-badge-<?php echo esc_attr($log->action); ?>">
<?php echo esc_html($log->action); ?></span></td>
<td><?php echo esc_html($log->reason); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php
}
public function render_settings() {
$settings = get_option('wpban_settings', []);
$countries = wpban_get_country_list();
?>
<div class="wrap wpban-wrap">
<h1><?php _e('WPBan Security Settings', 'wpban'); ?></h1>
<form id="wpban-settings-form" method="post">
<?php wp_nonce_field('wpban_settings', 'wpban_nonce'); ?>
<div class="wpban-tabs">
<nav class="nav-tab-wrapper">
<a href="#general" class="nav-tab nav-tab-active"><?php _e('General', 'wpban'); ?></a>
<a href="#ip-rules" class="nav-tab"><?php _e('IP Rules', 'wpban'); ?></a>
<a href="#advanced" class="nav-tab"><?php _e('Advanced', 'wpban'); ?></a>
<a href="#rate-limit" class="nav-tab"><?php _e('Rate Limiting', 'wpban'); ?></a>
<a href="#geo-block" class="nav-tab"><?php _e('Geo Blocking', 'wpban'); ?></a>
<a href="#crawlers" class="nav-tab"><?php _e('Crawlers', 'wpban'); ?></a>
<a href="#notifications" class="nav-tab"><?php _e('Notifications', 'wpban'); ?></a>
</nav>
<!-- General Tab -->
<div id="general" class="tab-content active">
<table class="form-table">
<tr>
<th scope="row"><?php _e('Enable Logging', 'wpban'); ?></th>
<td>
<label>
<input type="checkbox" name="settings[enable_logging]" value="1"
<?php checked($settings['enable_logging'] ?? true); ?> />
<?php _e('Log all security events', 'wpban'); ?>
</label>
</td>
</tr>
<tr>
<th scope="row"><?php _e('Reverse Proxy', 'wpban'); ?></th>
<td>
<label>
<input type="checkbox" name="settings[reverse_proxy]" value="1"
<?php checked($settings['reverse_proxy'] ?? false); ?> />
<?php _e('Server is behind a reverse proxy (Cloudflare, nginx, etc.)', 'wpban'); ?>
</label>
</td>
</tr>
<tr>
<th scope="row"><?php _e('Ban Message', 'wpban'); ?></th>
<td>
<?php
wp_editor(
$settings['ban_message'] ?? '<h1>Access Denied</h1><p>Your access has been restricted.</p>',
'ban_message',
[
'textarea_name' => 'settings[ban_message]',
'textarea_rows' => 10,
'media_buttons' => false
]
);
?>
<p class="description"><?php _e('Variables: %IP%, %DATE%, %SITE%', 'wpban'); ?></p>
</td>
</tr>
</table>
</div>
<!-- IP Rules Tab -->
<div id="ip-rules" class="tab-content">
<table class="form-table">
<tr>
<th scope="row"><?php _e('Banned IPs', 'wpban'); ?></th>
<td>
<textarea name="settings[banned_ips]" rows="10" class="large-text code"><?php
echo esc_textarea(implode("\n", $settings['banned_ips'] ?? []));
?></textarea>
<p class="description"><?php _e('One per line. Use * for wildcards (e.g., 192.168.*.*)', 'wpban'); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php _e('IP Ranges', 'wpban'); ?></th>
<td>
<textarea name="settings[banned_ranges]" rows="5" class="large-text code"><?php
echo esc_textarea(implode("\n", $settings['banned_ranges'] ?? []));
?></textarea>
<p class="description"><?php _e('CIDR: 192.168.0.0/24 or Range: 192.168.0.1-192.168.0.255', 'wpban'); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php _e('Whitelist IPs', 'wpban'); ?></th>
<td>
<textarea name="settings[whitelist_ips]" rows="5" class="large-text code"><?php
echo esc_textarea(implode("\n", $settings['whitelist_ips'] ?? []));
?></textarea>
<p class="description"><?php _e('These IPs will never be blocked', 'wpban'); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php _e('Login Protection', 'wpban'); ?></th>
<td>
<textarea name="settings[login_allowed_ips]" rows="5" class="large-text code"><?php
echo esc_textarea(implode("\n", $settings['login_allowed_ips'] ?? []));
?></textarea>
<p class="description"><?php _e('Only these IPs can access wp-login.php (leave empty to allow all)', 'wpban'); ?></p>
</td>
</tr>
</table>
</div>
<!-- Advanced Tab -->
<div id="advanced" class="tab-content">
<table class="form-table">
<tr>
<th scope="row"><?php _e('Banned Hosts', 'wpban'); ?></th>
<td>
<textarea name="settings[banned_hosts]" rows="5" class="large-text code"><?php
echo esc_textarea(implode("\n", $settings['banned_hosts'] ?? []));
?></textarea>
<p class="description"><?php _e('e.g., *.badhost.com', 'wpban'); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php _e('Banned Referers', 'wpban'); ?></th>
<td>
<textarea name="settings[banned_referers]" rows="5" class="large-text code"><?php
echo esc_textarea(implode("\n", $settings['banned_referers'] ?? []));
?></textarea>
</td>
</tr>
<tr>
<th scope="row"><?php _e('Banned User Agents', 'wpban'); ?></th>
<td>
<textarea name="settings[banned_agents]" rows="5" class="large-text code"><?php
echo esc_textarea(implode("\n", $settings['banned_agents'] ?? []));
?></textarea>
</td>
</tr>
<tr>
<th scope="row"><?php _e('Browser Restrictions', 'wpban'); ?></th>
<td>
<fieldset>
<label>
<input type="checkbox" name="settings[browser_restrictions][wechat_qq][enabled]" value="1"
<?php checked($settings['browser_restrictions']['wechat_qq']['enabled'] ?? false); ?> />
<?php _e('Block WeChat/QQ Browsers', 'wpban'); ?>
</label>
</fieldset>
</td>
</tr>
</table>
</div>
<!-- Rate Limiting Tab -->
<div id="rate-limit" class="tab-content">
<h2><?php _e('Rate Limiting Settings', 'wpban'); ?></h2>
<p><?php _e('Protect against floods and brute force attacks by limiting request rates.', 'wpban'); ?></p>
<table class="form-table">
<tr>
<th scope="row"><?php _e('General Requests', 'wpban'); ?></th>
<td>
<input type="number" name="settings[rate_limits][requests_per_minute]"
value="<?php echo esc_attr($settings['rate_limits']['requests_per_minute'] ?? 60); ?>"
min="10" max="1000" />
<span class="description"><?php _e('requests per minute', 'wpban'); ?></span>
</td>
</tr>
<tr>
<th scope="row"><?php _e('Login Attempts', 'wpban'); ?></th>
<td>
<input type="number" name="settings[rate_limits][login_per_hour]"
value="<?php echo esc_attr($settings['rate_limits']['login_per_hour'] ?? 5); ?>"
min="1" max="50" />
<span class="description"><?php _e('attempts per hour', 'wpban'); ?></span>
</td>
</tr>
<tr>
<th scope="row"><?php _e('API Requests', 'wpban'); ?></th>
<td>
<input type="number" name="settings[rate_limits][api_per_minute]"
value="<?php echo esc_attr($settings['rate_limits']['api_per_minute'] ?? 30); ?>"
min="5" max="500" />
<span class="description"><?php _e('requests per minute', 'wpban'); ?></span>
</td>
</tr>
</table>
</div>
<!-- Geo Blocking Tab -->
<div id="geo-block" class="tab-content">
<h2><?php _e('Geographic Blocking', 'wpban'); ?></h2>
<table class="form-table">
<tr>
<th scope="row"><?php _e('Enable Geo Blocking', 'wpban'); ?></th>
<td>
<label>
<input type="checkbox" name="settings[geo_blocking][enabled]" value="1"
<?php checked($settings['geo_blocking']['enabled'] ?? false); ?> />
<?php _e('Block or allow access based on country', 'wpban'); ?>
</label>
</td>
</tr>
<tr>
<th scope="row"><?php _e('Block Mode', 'wpban'); ?></th>
<td>
<label>
<input type="radio" name="settings[geo_blocking][mode]" value="blacklist"
<?php checked(($settings['geo_blocking']['mode'] ?? 'blacklist'), 'blacklist'); ?> />
<?php _e('Block selected countries', 'wpban'); ?>
</label><br>
<label>
<input type="radio" name="settings[geo_blocking][mode]" value="whitelist"
<?php checked(($settings['geo_blocking']['mode'] ?? 'blacklist'), 'whitelist'); ?> />
<?php _e('Allow only selected countries', 'wpban'); ?>
</label>
</td>
</tr>
<tr>
<th scope="row"><?php _e('Countries', 'wpban'); ?></th>
<td>
<div class="wpban-country-selector">
<select multiple name="settings[geo_blocking][countries][]" size="10" style="width: 100%; max-width: 400px;">
<?php
$selected = $settings['geo_blocking']['blocked_countries'] ?? [];
foreach ($countries as $code => $name): ?>
<option value="<?php echo esc_attr($code); ?>"
<?php selected(in_array($code, $selected)); ?>>
<?php echo esc_html("$name ($code)"); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<p class="description"><?php _e('Hold Ctrl/Cmd to select multiple countries', 'wpban'); ?></p>
</td>
</tr>
</table>
</div>
<!-- Crawlers Tab -->
<div id="crawlers" class="tab-content">
<div class="wpban-crawler-controls">
<button type="button" class="button" onclick="wpbanSelectCrawlers('ai', true)">
<?php _e('Select All AI', 'wpban'); ?>
</button>
<button type="button" class="button" onclick="wpbanSelectCrawlers('seo', true)">
<?php _e('Select All SEO', 'wpban'); ?>
</button>
<button type="button" class="button" onclick="wpbanSelectCrawlers('all', false)">
<?php _e('Deselect All', 'wpban'); ?>
</button>
</div>
<?php
$crawlers = wpban_get_crawler_list();
$blocked = $settings['blocked_crawlers'] ?? [];
?>
<h3><?php _e('AI Crawlers', 'wpban'); ?></h3>
<div class="wpban-crawler-grid">
<?php foreach ($crawlers['ai'] as $crawler => $info): ?>
<label class="wpban-crawler-item" data-type="ai">
<input type="checkbox" name="settings[blocked_crawlers][]"
value="<?php echo esc_attr($crawler); ?>"
<?php checked(in_array($crawler, $blocked)); ?> />
<span class="wpban-crawler-name"><?php echo esc_html($crawler); ?></span>
<span class="wpban-crawler-desc"><?php echo esc_html($info['description']); ?></span>
</label>
<?php endforeach; ?>
</div>
<h3><?php _e('SEO Crawlers', 'wpban'); ?></h3>
<div class="wpban-notice wpban-notice-warning">
<p><?php _e('⚠️ Blocking SEO crawlers may affect your search engine rankings!', 'wpban'); ?></p>
</div>
<div class="wpban-crawler-grid">
<?php foreach ($crawlers['seo'] as $crawler => $info): ?>
<label class="wpban-crawler-item" data-type="seo">
<input type="checkbox" name="settings[blocked_crawlers][]"
value="<?php echo esc_attr($crawler); ?>"
<?php checked(in_array($crawler, $blocked)); ?> />
<span class="wpban-crawler-name"><?php echo esc_html($crawler); ?></span>
<span class="wpban-crawler-desc"><?php echo esc_html($info['description']); ?></span>
</label>
<?php endforeach; ?>
</div>
</div>
<!-- Notifications Tab -->
<div id="notifications" class="tab-content">
<table class="form-table">
<tr>
<th scope="row"><?php _e('Enable Email Notifications', 'wpban'); ?></th>
<td>
<label>
<input type="checkbox" name="settings[email_notifications][enabled]" value="1"
<?php checked($settings['email_notifications']['enabled'] ?? false); ?> />
<?php _e('Send email alerts for security events', 'wpban'); ?>
</label>
</td>
</tr>
<tr>
<th scope="row"><?php _e('Recipient Email', 'wpban'); ?></th>
<td>
<input type="email" name="settings[email_notifications][recipient]"
value="<?php echo esc_attr($settings['email_notifications']['recipient'] ?? get_option('admin_email')); ?>"
class="regular-text" />
<button type="button" class="button" onclick="wpbanTestEmail()">
<?php _e('Send Test Email', 'wpban'); ?>
</button>
</td>
</tr>
<tr>
<th scope="row"><?php _e('Alert Threshold', 'wpban'); ?></th>
<td>
<input type="number" name="settings[email_notifications][threshold]"
value="<?php echo esc_attr($settings['email_notifications']['threshold'] ?? 10); ?>"
min="1" max="100" />
<span class="description"><?php _e('Send alert after this many blocks from same IP', 'wpban'); ?></span>
</td>
</tr>
<tr>
<th scope="row"><?php _e('Alert Events', 'wpban'); ?></th>
<td>
<?php
$events = [
'rate_limit' => __('Rate limit exceeded', 'wpban'),
'geo_block' => __('Geographic block', 'wpban'),
'brute_force' => __('Brute force attempt', 'wpban'),
'crawler_block' => __('Crawler blocked', 'wpban')
];
$selected_events = $settings['email_notifications']['events'] ?? ['rate_limit', 'brute_force'];
foreach ($events as $event => $label): ?>
<label style="display: block; margin-bottom: 5px;">
<input type="checkbox" name="settings[email_notifications][events][]"
value="<?php echo esc_attr($event); ?>"
<?php checked(in_array($event, $selected_events)); ?> />
<?php echo esc_html($label); ?>
</label>
<?php endforeach; ?>
</td>
</tr>
</table>
</div>
</div>
<p class="submit">
<button type="submit" class="button button-primary" id="wpban-save-settings">
<?php _e('Save Settings', 'wpban'); ?>
</button>
<span class="spinner"></span>
</p>
</form>
</div>
<?php
}
public function render_logs() {
$current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
$per_page = 50;
$filters = [
'date_from' => sanitize_text_field($_GET['date_from'] ?? ''),
'date_to' => sanitize_text_field($_GET['date_to'] ?? ''),
'action' => sanitize_text_field($_GET['action_filter'] ?? ''),
'ip' => sanitize_text_field($_GET['ip_filter'] ?? ''),
'country' => sanitize_text_field($_GET['country_filter'] ?? '')
];
$result = $this->security->get_logs($filters, $current_page, $per_page);
?>
<div class="wrap wpban-wrap">
<h1><?php _e('Security Logs', 'wpban'); ?></h1>
<!-- Filters -->
<div class="wpban-logs-filters">
<form method="get" action="">
<input type="hidden" name="page" value="wpban-logs" />
<input type="date" name="date_from" value="<?php echo esc_attr($filters['date_from']); ?>"
placeholder="<?php esc_attr_e('From date', 'wpban'); ?>" />
<input type="date" name="date_to" value="<?php echo esc_attr($filters['date_to']); ?>"
placeholder="<?php esc_attr_e('To date', 'wpban'); ?>" />
<select name="action_filter">
<option value=""><?php _e('All Actions', 'wpban'); ?></option>
<option value="banned" <?php selected($filters['action'], 'banned'); ?>><?php _e('Banned', 'wpban'); ?></option>
<option value="blocked" <?php selected($filters['action'], 'blocked'); ?>><?php _e('Blocked', 'wpban'); ?></option>
<option value="failed_login" <?php selected($filters['action'], 'failed_login'); ?>><?php _e('Failed Login', 'wpban'); ?></option>
<option value="bypass" <?php selected($filters['action'], 'bypass'); ?>><?php _e('Bypass Used', 'wpban'); ?></option>
</select>
<input type="text" name="ip_filter" value="<?php echo esc_attr($filters['ip']); ?>"
placeholder="<?php esc_attr_e('IP Address', 'wpban'); ?>" />
<input type="text" name="country_filter" value="<?php echo esc_attr($filters['country']); ?>"
placeholder="<?php esc_attr_e('Country Code', 'wpban'); ?>" size="5" />
<button type="submit" class="button"><?php _e('Filter', 'wpban'); ?></button>
<a href="<?php echo admin_url('admin.php?page=wpban-logs'); ?>" class="button"><?php _e('Reset', 'wpban'); ?></a>
<button type="button" class="button" onclick="wpbanExportLogs()">
<?php _e('Export CSV', 'wpban'); ?>
</button>
<button type="button" class="button" onclick="wpbanClearLogs()">
<?php _e('Clear Logs', 'wpban'); ?>
</button>
</form>
</div>
<!-- Logs Table -->
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th style="width: 150px;"><?php _e('Time', 'wpban'); ?></th>
<th style="width: 120px;"><?php _e('IP Address', 'wpban'); ?></th>
<th style="width: 60px;"><?php _e('Country', 'wpban'); ?></th>
<th style="width: 100px;"><?php _e('Action', 'wpban'); ?></th>
<th><?php _e('Details', 'wpban'); ?></th>
<th><?php _e('User Agent', 'wpban'); ?></th>
<th><?php _e('URI', 'wpban'); ?></th>
</tr>
</thead>
<tbody>
<?php if (empty($result['logs'])): ?>
<tr>
<td colspan="7"><?php _e('No logs found.', 'wpban'); ?></td>
</tr>
<?php else: ?>
<?php foreach ($result['logs'] as $log): ?>
<tr>
<td><?php echo esc_html($log->timestamp); ?></td>
<td>
<code><?php echo esc_html($log->ip); ?></code>
<div class="row-actions">
<a href="?page=wpban-logs&ip_filter=<?php echo urlencode($log->ip); ?>">
<?php _e('Filter', 'wpban'); ?>
</a>
</div>
</td>
<td><?php echo esc_html($log->country_code ?: '-'); ?></td>
<td>
<span class="wpban-badge wpban-badge-<?php echo esc_attr($log->action); ?>">
<?php echo esc_html($log->action); ?>
</span>
</td>
<td><?php echo esc_html($log->reason); ?></td>
<td>
<span class="wpban-truncate" title="<?php echo esc_attr($log->user_agent); ?>">
<?php echo esc_html(substr($log->user_agent, 0, 50)); ?>
</span>
</td>
<td>
<span class="wpban-truncate" title="<?php echo esc_attr($log->uri); ?>">
<?php echo esc_html($log->uri); ?>
</span>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<!-- Pagination -->
<?php if ($result['pages'] > 1): ?>
<div class="tablenav bottom">
<div class="tablenav-pages">
<?php
echo paginate_links([
'base' => add_query_arg('paged', '%#%'),
'format' => '',
'prev_text' => '&laquo;',
'next_text' => '&raquo;',
'total' => $result['pages'],
'current' => $current_page
]);
?>
</div>
</div>
<?php endif; ?>
</div>
<?php
}
public function render_tools() {
?>
<div class="wrap wpban-wrap">
<h1><?php _e('WPBan Tools', 'wpban'); ?></h1>
<div class="wpban-tools-grid">
<!-- Import/Export -->
<div class="wpban-card">
<h2><?php _e('Import/Export Settings', 'wpban'); ?></h2>
<p><?php _e('Backup your settings or migrate to another site.', 'wpban'); ?></p>
<h3><?php _e('Export', 'wpban'); ?></h3>
<p>
<button class="button" onclick="wpbanExportSettings()">
<?php _e('Download Settings', 'wpban'); ?>
</button>
</p>
<h3><?php _e('Import', 'wpban'); ?></h3>
<form method="post" enctype="multipart/form-data" action="<?php echo admin_url('admin-post.php'); ?>">
<input type="hidden" name="action" value="wpban_import_settings" />
<?php wp_nonce_field('wpban_import', 'wpban_import_nonce'); ?>
<p>
<input type="file" name="import_file" accept=".json" required />
</p>
<p>
<button type="submit" class="button">
<?php _e('Import Settings', 'wpban'); ?>
</button>
</p>
</form>
</div>
<!-- Database Maintenance -->
<div class="wpban-card">
<h2><?php _e('Database Maintenance', 'wpban'); ?></h2>
<p><?php _e('Optimize your WPBan database tables.', 'wpban'); ?></p>
<?php
global $wpdb;
$logs_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}wpban_logs");
$logs_size = $wpdb->get_var("SELECT ROUND(((data_length + index_length) / 1024 / 1024), 2)
FROM information_schema.TABLES
WHERE table_schema = '" . DB_NAME . "'
AND table_name = '{$wpdb->prefix}wpban_logs'");
?>
<table class="wp-list-table widefat">
<tr>
<th><?php _e('Total Log Entries', 'wpban'); ?></th>
<td><?php echo number_format($logs_count); ?></td>
</tr>
<tr>
<th><?php _e('Database Size', 'wpban'); ?></th>
<td><?php echo $logs_size; ?> MB</td>
</tr>
</table>
<p>
<button class="button" onclick="wpbanOptimizeDatabase()">
<?php _e('Optimize Tables', 'wpban'); ?>
</button>
<button class="button" onclick="wpbanClearOldLogs()">
<?php _e('Clear Logs Older Than 30 Days', 'wpban'); ?>
</button>
</p>
</div>
<!-- System Info -->
<div class="wpban-card">
<h2><?php _e('System Information', 'wpban'); ?></h2>
<textarea readonly class="large-text" rows="10"><?php
echo "WordPress Version: " . get_bloginfo('version') . "\n";
echo "PHP Version: " . PHP_VERSION . "\n";
echo "MySQL Version: " . $wpdb->db_version() . "\n";
echo "WPBan Version: " . WPBAN_VERSION . "\n";
echo "Active Theme: " . wp_get_theme()->get('Name') . "\n";
echo "Active Plugins:\n";
foreach (get_option('active_plugins') as $plugin) {
$plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin);
echo "- " . $plugin_data['Name'] . " " . $plugin_data['Version'] . "\n";
}
?></textarea>
</div>
</div>
</div>
<?php
}
// AJAX Handlers
public function ajax_save_settings() {
check_ajax_referer('wpban_settings', '_ajax_nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error('Permission denied');
}
parse_str($_POST['settings'], $data);
$settings = $data['settings'] ?? [];
// Process textarea fields
$textarea_fields = [
'banned_ips', 'banned_ranges', 'banned_hosts',
'banned_referers', 'banned_agents', 'whitelist_ips',
'login_allowed_ips'
];
foreach ($textarea_fields as $field) {
if (isset($settings[$field])) {
$settings[$field] = array_filter(array_map('trim', explode("\n", $settings[$field])));
}
}
// Process geo blocking
if (isset($settings['geo_blocking']['mode']) && $settings['geo_blocking']['mode'] === 'whitelist') {
$settings['geo_blocking']['allowed_countries'] = $settings['geo_blocking']['countries'] ?? [];
$settings['geo_blocking']['blocked_countries'] = [];
} else {
$settings['geo_blocking']['blocked_countries'] = $settings['geo_blocking']['countries'] ?? [];
$settings['geo_blocking']['allowed_countries'] = [];
}
unset($settings['geo_blocking']['countries']);
// Validate settings
if (isset($settings['rate_limits'])) {
foreach ($settings['rate_limits'] as $key => $value) {
$settings['rate_limits'][$key] = max(1, intval($value));
}
}
update_option('wpban_settings', $settings);
$this->security->clear_cache();
wp_send_json_success(['message' => __('Settings saved successfully!', 'wpban')]);
}
public function ajax_apply_template() {
check_ajax_referer('wpban_admin', '_ajax_nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error('Permission denied');
}
$template_id = sanitize_key($_POST['template']);
$templates = $this->security->get_templates();
if (!isset($templates[$template_id])) {
wp_send_json_error(__('Invalid template', 'wpban'));
}
$current = get_option('wpban_settings', []);
$new_settings = array_merge($current, $templates[$template_id]['settings']);
update_option('wpban_settings', $new_settings);
$this->security->clear_cache();
wp_send_json_success(['message' => __('Template applied successfully!', 'wpban')]);
}
public function ajax_get_logs() {
check_ajax_referer('wpban_admin', '_ajax_nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error('Permission denied');
}
$page = intval($_POST['page'] ?? 1);
$filters = [
'date_from' => sanitize_text_field($_POST['date_from'] ?? ''),
'date_to' => sanitize_text_field($_POST['date_to'] ?? ''),
'action' => sanitize_text_field($_POST['action'] ?? ''),
'ip' => sanitize_text_field($_POST['ip'] ?? '')
];
$result = $this->security->get_logs($filters, $page);
ob_start();
// Render log rows
foreach ($result['logs'] as $log) {
// ... render table rows ...
}
$html = ob_get_clean();
wp_send_json_success([
'html' => $html,
'pagination' => paginate_links([
'total' => $result['pages'],
'current' => $page
])
]);
}
public function ajax_export_logs() {
check_ajax_referer('wpban_admin', '_ajax_nonce');
if (!current_user_can('manage_options')) {
wp_die('Permission denied');
}
$logs = $this->security->get_logs([], 1, 10000);
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="wpban-logs-' . date('Y-m-d') . '.csv"');
$output = fopen('php://output', 'w');
fputcsv($output, ['Time', 'IP', 'Country', 'Action', 'Reason', 'User Agent', 'Referer', 'URI']);
foreach ($logs['logs'] as $log) {
fputcsv($output, [
$log->timestamp,
$log->ip,
$log->country_code,
$log->action,
$log->reason,
$log->user_agent,
$log->referer,
$log->uri
]);
}
fclose($output);
exit;
}
public function ajax_clear_logs() {
check_ajax_referer('wpban_admin', '_ajax_nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error('Permission denied');
}
global $wpdb;
$wpdb->query("TRUNCATE TABLE {$wpdb->prefix}wpban_logs");
wp_send_json_success(['message' => __('Logs cleared successfully!', 'wpban')]);
}
public function ajax_test_email() {
check_ajax_referer('wpban_admin', '_ajax_nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error('Permission denied');
}
$settings = get_option('wpban_settings', []);
$to = $settings['email_notifications']['recipient'] ?? get_option('admin_email');
$subject = sprintf('[%s] WPBan Test Email', get_bloginfo('name'));
$message = "This is a test email from WPBan Security.\n\n";
$message .= "If you received this email, your notifications are working correctly!";
$sent = wp_mail($to, $subject, $message);
if ($sent) {
wp_send_json_success(['message' => __('Test email sent successfully!', 'wpban')]);
} else {
wp_send_json_error(__('Failed to send test email. Please check your email settings.', 'wpban'));
}
}
public function ajax_get_country_stats() {
check_ajax_referer('wpban_admin', '_ajax_nonce');
if (!current_user_can('manage_options')) {
wp_send_json_error('Permission denied');
}
global $wpdb;
$stats = $wpdb->get_results(
"SELECT country_code, COUNT(*) as count
FROM {$wpdb->prefix}wpban_logs
WHERE country_code IS NOT NULL
GROUP BY country_code
ORDER BY count DESC
LIMIT 20"
);
wp_send_json_success($stats);
}
}

View file

@ -0,0 +1,82 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPBan_Cache {
private $cache_group = 'wpban';
private $use_transients = false;
public function __construct() {
// Check if object caching is available
if (!wp_using_ext_object_cache()) {
$this->use_transients = true;
}
}
public function get($key, $callback = null, $expiration = 3600) {
$settings = get_option('wpban_settings', []);
if (empty($settings['enable_caching'])) {
return $callback ? $callback() : false;
}
$cache_key = $this->get_cache_key($key);
if ($this->use_transients) {
$value = get_transient($cache_key);
} else {
$value = wp_cache_get($key, $this->cache_group);
}
if ($value === false && $callback) {
$value = $callback();
$this->set($key, $value, $expiration);
}
return $value;
}
public function set($key, $value, $expiration = 3600) {
$settings = get_option('wpban_settings', []);
if (empty($settings['enable_caching'])) {
return false;
}
$cache_key = $this->get_cache_key($key);
if ($this->use_transients) {
return set_transient($cache_key, $value, $expiration);
} else {
return wp_cache_set($key, $value, $this->cache_group, $expiration);
}
}
public function delete($key) {
$cache_key = $this->get_cache_key($key);
if ($this->use_transients) {
return delete_transient($cache_key);
} else {
return wp_cache_delete($key, $this->cache_group);
}
}
public function clear() {
if ($this->use_transients) {
// Clear all WPBan transients
global $wpdb;
$wpdb->query(
"DELETE FROM {$wpdb->options}
WHERE option_name LIKE '_transient_wpban_%'
OR option_name LIKE '_transient_timeout_wpban_%'"
);
} else {
// Clear object cache group
wp_cache_delete_group($this->cache_group);
}
}
private function get_cache_key($key) {
return $this->use_transients ? 'wpban_' . md5($key) : $key;
}
}

View file

@ -0,0 +1,300 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPBan_Core {
private $cache;
private $logger;
private $settings;
private $bypass_cookie = false;
public function __construct($cache, $logger) {
$this->cache = $cache;
$this->logger = $logger;
$this->settings = $this->get_settings();
// Check bypass first
add_action('init', [$this, 'check_bypass'], 1);
// Main security checks
add_action('init', [$this, 'check_ban'], 10);
add_action('init', [$this, 'check_login_restriction'], 11);
add_action('wp_footer', [$this, 'check_browser_restrictions']);
// Robots.txt modifications
add_filter('robots_txt', [$this, 'modify_robots_txt'], 10, 2);
// Performance: only load heavy checks if needed
if ($this->should_check_crawlers()) {
add_action('init', [$this, 'check_crawlers'], 12);
}
}
private function get_settings() {
return $this->cache->get('settings', function() {
return get_option('wpban_settings', [
'banned_ips' => [],
'banned_ranges' => [],
'banned_hosts' => [],
'banned_referers' => [],
'banned_agents' => [],
'whitelist_ips' => [],
'login_allowed_ips' => [],
'blocked_crawlers' => [],
'browser_restrictions' => [],
'ban_message' => '<h1>Access Denied</h1><p>Your access to this site has been restricted.</p>',
'enable_logging' => true,
'enable_caching' => true,
'reverse_proxy' => false
]);
});
}
public function check_bypass() {
$bypass_path = get_option('wpban_bypass_path');
// Check URL bypass
if (isset($_GET['wpban_bypass']) && $_GET['wpban_bypass'] === $bypass_path) {
setcookie(WPBAN_BYPASS_KEY, $bypass_path, time() + DAY_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true);
$this->bypass_cookie = true;
wp_redirect(remove_query_arg('wpban_bypass'));
exit;
}
// Check cookie bypass
if (isset($_COOKIE[WPBAN_BYPASS_KEY]) && $_COOKIE[WPBAN_BYPASS_KEY] === $bypass_path) {
$this->bypass_cookie = true;
}
}
public function check_ban() {
if ($this->bypass_cookie) {
return;
}
$ip = wpban_get_ip($this->settings['reverse_proxy']);
$checks = [
'ip' => $this->is_ip_banned($ip),
'host' => $this->is_host_banned($ip),
'referer' => $this->is_referer_banned(),
'agent' => $this->is_agent_banned()
];
foreach ($checks as $type => $banned) {
if ($banned && !$this->is_whitelisted($ip)) {
$this->logger->log($ip, 'banned', $type . '_ban');
$this->show_ban_message();
}
}
}
private function is_ip_banned($ip) {
// Check exact IPs and wildcards
$banned_ips = $this->cache->get('banned_ips_compiled', function() {
$patterns = [];
foreach ($this->settings['banned_ips'] as $pattern) {
$patterns[] = $this->compile_wildcard_pattern($pattern);
}
return $patterns;
});
foreach ($banned_ips as $pattern) {
if (preg_match($pattern, $ip)) {
return true;
}
}
// Check IP ranges
foreach ($this->settings['banned_ranges'] as $range) {
if ($this->ip_in_range($ip, $range)) {
return true;
}
}
return false;
}
private function is_host_banned($ip) {
if (empty($this->settings['banned_hosts'])) {
return false;
}
$hostname = $this->cache->get('hostname_' . $ip, function() use ($ip) {
return gethostbyaddr($ip);
}, 3600); // Cache for 1 hour
foreach ($this->settings['banned_hosts'] as $pattern) {
if (wpban_match_wildcard($pattern, $hostname)) {
return true;
}
}
return false;
}
private function is_referer_banned() {
$referer = $_SERVER['HTTP_REFERER'] ?? '';
if (empty($referer) || empty($this->settings['banned_referers'])) {
return false;
}
foreach ($this->settings['banned_referers'] as $pattern) {
if (wpban_match_wildcard($pattern, $referer)) {
return true;
}
}
return false;
}
private function is_agent_banned() {
$agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
if (empty($agent) || empty($this->settings['banned_agents'])) {
return false;
}
foreach ($this->settings['banned_agents'] as $pattern) {
if (wpban_match_wildcard($pattern, $agent)) {
return true;
}
}
return false;
}
private function is_whitelisted($ip) {
foreach ($this->settings['whitelist_ips'] as $pattern) {
if (wpban_match_wildcard($pattern, $ip) || $this->ip_in_range($ip, $pattern)) {
return true;
}
}
return false;
}
public function check_login_restriction() {
if ($this->bypass_cookie || empty($this->settings['login_allowed_ips'])) {
return;
}
$login_files = ['wp-login.php', 'wp-register.php'];
$current_file = basename($_SERVER['SCRIPT_FILENAME']);
if (in_array($current_file, $login_files)) {
$ip = wpban_get_ip($this->settings['reverse_proxy']);
$allowed = false;
foreach ($this->settings['login_allowed_ips'] as $pattern) {
if (wpban_match_wildcard($pattern, $ip) || $this->ip_in_range($ip, $pattern)) {
$allowed = true;
break;
}
}
if (!$allowed) {
$this->logger->log($ip, 'blocked', 'login_restriction');
wp_redirect(home_url());
exit;
}
}
}
public function check_browser_restrictions() {
if ($this->bypass_cookie || empty($this->settings['browser_restrictions'])) {
return;
}
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
// WeChat/QQ browser check
if (isset($this->settings['browser_restrictions']['wechat_qq']) &&
$this->settings['browser_restrictions']['wechat_qq']['enabled'] &&
(strpos($ua, 'MQQBrowser') !== false || strpos($ua, 'MicroMessenger') !== false)) {
$this->logger->log(wpban_get_ip($this->settings['reverse_proxy']), 'blocked', 'browser_restriction');
$this->show_browser_block_message($this->settings['browser_restrictions']['wechat_qq']);
}
}
public function check_crawlers() {
if ($this->bypass_cookie || empty($this->settings['blocked_crawlers'])) {
return;
}
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
$ip = wpban_get_ip($this->settings['reverse_proxy']);
foreach ($this->settings['blocked_crawlers'] as $crawler) {
if (stripos($ua, $crawler) !== false) {
$this->logger->log($ip, 'blocked', 'crawler_block', $crawler);
http_response_code(403);
exit('Access denied for crawlers');
}
}
}
public function modify_robots_txt($output, $public) {
if (!empty($this->settings['blocked_crawlers'])) {
$output .= "\n# wpban Rules\n";
foreach ($this->settings['blocked_crawlers'] as $crawler) {
$output .= "User-agent: $crawler\n";
$output .= "Disallow: /\n\n";
}
}
return $output;
}
private function should_check_crawlers() {
// Performance optimization: only check if crawlers are configured
return !empty($this->settings['blocked_crawlers']);
}
private function compile_wildcard_pattern($pattern) {
$pattern = preg_quote($pattern, '/');
$pattern = str_replace('\*', '.*', $pattern);
return '/^' . $pattern . '$/i';
}
private function ip_in_range($ip, $range) {
if (strpos($range, '/') !== false) {
// CIDR notation
list($subnet, $bits) = explode('/', $range);
$ip_long = ip2long($ip);
$subnet_long = ip2long($subnet);
$mask = -1 << (32 - $bits);
return ($ip_long & $mask) == ($subnet_long & $mask);
} elseif (strpos($range, '-') !== false) {
// Range notation
list($start, $end) = explode('-', $range);
$ip_long = ip2long($ip);
return ($ip_long >= ip2long(trim($start)) && $ip_long <= ip2long(trim($end)));
}
return false;
}
private function show_ban_message() {
$message = $this->settings['ban_message'];
$message = str_replace(
['%IP%', '%DATE%', '%SITE%'],
[wpban_get_ip($this->settings['reverse_proxy']), date('Y-m-d H:i:s'), get_bloginfo('name')],
$message
);
wp_die($message, 'Access Denied', ['response' => 403]);
}
private function show_browser_block_message($settings) {
?>
<div style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); display: flex; justify-content: center; align-items: center; z-index: 999999;">
<div style="background: white; padding: 30px; border-radius: 10px; max-width: 500px; text-align: center;">
<h2><?php echo esc_html($settings['title']); ?></h2>
<p><?php echo esc_html($settings['message']); ?></p>
<button onclick="navigator.clipboard.writeText(window.location.href).then(() => alert('Link copied!'));" style="padding: 10px 20px; background: #2271b1; color: white; border: none; border-radius: 5px; cursor: pointer;">
<?php echo esc_html($settings['button_text']); ?>
</button>
</div>
</div>
<script>document.body.style.overflow = 'hidden';</script>
<?php
}
}

View file

@ -0,0 +1,124 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPBan_Logger {
private $table_name;
public function __construct() {
global $wpdb;
$this->table_name = $wpdb->prefix . 'wpban_logs';
}
public function log($ip, $action, $reason, $details = '') {
$settings = get_option('wpban_settings', []);
if (empty($settings['enable_logging'])) {
return;
}
global $wpdb;
$data = [
'ip' => $ip,
'user_agent' => substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 500),
'referer' => substr($_SERVER['HTTP_REFERER'] ?? '', 0, 500),
'uri' => substr($_SERVER['REQUEST_URI'] ?? '', 0, 500),
'action' => $action,
'reason' => $reason . ($details ? ' - ' . $details : ''),
'timestamp' => current_time('mysql')
];
$wpdb->insert($this->table_name, $data);
// Clean old logs (keep last 30 days)
$this->clean_old_logs();
}
public function get_logs($filters = []) {
global $wpdb;
$where = [];
$where_values = [];
if (!empty($filters['date'])) {
$where[] = "DATE(timestamp) = %s";
$where_values[] = $filters['date'];
}
if (!empty($filters['action'])) {
$where[] = "action = %s";
$where_values[] = $filters['action'];
}
if (!empty($filters['ip'])) {
$where[] = "ip = %s";
$where_values[] = $filters['ip'];
}
$where_clause = $where ? 'WHERE ' . implode(' AND ', $where) : '';
$limit = isset($filters['limit']) ? intval($filters['limit']) : 100;
$query = "SELECT * FROM {$this->table_name} {$where_clause} ORDER BY timestamp DESC LIMIT %d";
$where_values[] = $limit;
return $wpdb->get_results($wpdb->prepare($query, $where_values));
}
public function get_stats() {
global $wpdb;
$settings = get_option('wpban_settings', []);
// Total blocks
$total_blocks = $wpdb->get_var("SELECT COUNT(*) FROM {$this->table_name}");
// Unique IPs
$unique_ips = $wpdb->get_var("SELECT COUNT(DISTINCT ip) FROM {$this->table_name}");
// Today's blocks
$today = current_time('Y-m-d');
$today_blocks = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$this->table_name} WHERE DATE(timestamp) = %s",
$today
));
// Count active rules
$active_rules = 0;
$rule_types = ['banned_ips', 'banned_ranges', 'banned_hosts', 'banned_referers', 'banned_agents', 'blocked_crawlers'];
foreach ($rule_types as $type) {
if (!empty($settings[$type])) {
$active_rules += count($settings[$type]);
}
}
return [
'total_blocks' => $total_blocks,
'unique_ips' => $unique_ips,
'today_blocks' => $today_blocks,
'active_rules' => $active_rules
];
}
public function clear_logs() {
global $wpdb;
$wpdb->query("TRUNCATE TABLE {$this->table_name}");
}
private function clean_old_logs() {
global $wpdb;
// Run cleanup only 1% of the time to avoid performance impact
if (mt_rand(1, 100) > 1) {
return;
}
$days_to_keep = 30;
$cutoff_date = date('Y-m-d H:i:s', strtotime("-{$days_to_keep} days"));
$wpdb->query($wpdb->prepare(
"DELETE FROM {$this->table_name} WHERE timestamp < %s",
$cutoff_date
));
}
}

View file

@ -0,0 +1,719 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPBan_Security {
private $settings;
private $bypass_active = false;
private $cache_group = 'wpban';
private $ip_address;
public function __construct() {
$this->load_settings();
$this->ip_address = wpban_get_ip($this->settings['reverse_proxy'] ?? false);
// Core hooks
add_action('init', [$this, 'check_bypass'], 1);
add_action('init', [$this, 'check_rate_limit'], 5);
add_action('init', [$this, 'check_geo_block'], 6);
add_action('init', [$this, 'check_ban'], 10);
add_action('init', [$this, 'check_login_restriction'], 11);
add_action('init', [$this, 'check_crawlers'], 12);
add_action('wp_footer', [$this, 'check_browser_restrictions']);
add_filter('robots_txt', [$this, 'modify_robots_txt'], 10, 2);
// Login protection
add_action('wp_login_failed', [$this, 'handle_login_failed']);
add_filter('authenticate', [$this, 'check_login_attempt'], 30, 3);
}
private function load_settings() {
$this->settings = wp_cache_get('settings', $this->cache_group);
if ($this->settings === false) {
$this->settings = get_option('wpban_settings', []);
wp_cache_set('settings', $this->settings, $this->cache_group, 3600);
}
}
public function check_bypass() {
$bypass_path = get_option('wpban_bypass_path');
// URL bypass
if (isset($_GET['wpban_bypass']) && $_GET['wpban_bypass'] === $bypass_path) {
setcookie(WPBAN_BYPASS_KEY, $bypass_path, time() + DAY_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true);
$this->bypass_active = true;
$this->log('bypass', 'bypass_activated');
wp_redirect(remove_query_arg('wpban_bypass'));
exit;
}
// Cookie bypass
if (isset($_COOKIE[WPBAN_BYPASS_KEY]) && $_COOKIE[WPBAN_BYPASS_KEY] === $bypass_path) {
$this->bypass_active = true;
}
}
public function check_rate_limit() {
if ($this->bypass_active || empty($this->settings['rate_limits'])) {
return;
}
global $wpdb;
$table = $wpdb->prefix . 'wpban_rate_limits';
$limits = $this->settings['rate_limits'];
// Determine action type
$action = 'general';
if (strpos($_SERVER['REQUEST_URI'], '/wp-login.php') !== false) {
$action = 'login';
} elseif (strpos($_SERVER['REQUEST_URI'], '/wp-json/') !== false) {
$action = 'api';
}
// Get limit settings
$window = 60; // seconds
$max_requests = $limits['requests_per_minute'] ?? 60;
if ($action === 'login') {
$window = 3600;
$max_requests = $limits['login_per_hour'] ?? 5;
} elseif ($action === 'api') {
$max_requests = $limits['api_per_minute'] ?? 30;
}
$window_start = date('Y-m-d H:i:s', time() - $window);
// Check current count
$current = $wpdb->get_row($wpdb->prepare(
"SELECT count, window_start FROM $table WHERE ip = %s AND action = %s AND window_start > %s",
$this->ip_address, $action, $window_start
));
if ($current) {
if ($current->count >= $max_requests) {
$this->log('blocked', 'rate_limit', $action);
$this->send_notification('rate_limit', [
'action' => $action,
'count' => $current->count
]);
wp_die(__('Too many requests. Please try again later.', 'wpban'), 429);
}
// Increment counter
$wpdb->query($wpdb->prepare(
"UPDATE $table SET count = count + 1, last_attempt = %s WHERE ip = %s AND action = %s",
current_time('mysql'), $this->ip_address, $action
));
} else {
// Create new record
$wpdb->insert($table, [
'ip' => $this->ip_address,
'action' => $action,
'count' => 1,
'window_start' => current_time('mysql'),
'last_attempt' => current_time('mysql')
]);
}
}
public function check_geo_block() {
if ($this->bypass_active || empty($this->settings['geo_blocking']['enabled'])) {
return;
}
$country = $this->get_country_code($this->ip_address);
$blocked_countries = $this->settings['geo_blocking']['blocked_countries'] ?? [];
$allowed_countries = $this->settings['geo_blocking']['allowed_countries'] ?? [];
// If allowed list is set, only allow those countries
if (!empty($allowed_countries) && !in_array($country, $allowed_countries)) {
$this->log('blocked', 'geo_block', $country);
$this->send_notification('geo_block', ['country' => $country]);
wp_die(__('Access denied from your location.', 'wpban'), 403);
}
// Check blocked countries
if (in_array($country, $blocked_countries)) {
$this->log('blocked', 'geo_block', $country);
$this->send_notification('geo_block', ['country' => $country]);
wp_die(__('Access denied from your location.', 'wpban'), 403);
}
}
public function check_ban() {
if ($this->bypass_active) {
return;
}
$checks = [
'ip' => $this->is_ip_banned(),
'host' => $this->is_host_banned(),
'referer' => $this->is_referer_banned(),
'agent' => $this->is_agent_banned()
];
foreach ($checks as $type => $banned) {
if ($banned && !$this->is_whitelisted()) {
$this->log('banned', $type . '_ban');
$this->show_ban_message();
}
}
}
public function check_login_restriction() {
if ($this->bypass_active || empty($this->settings['login_allowed_ips'])) {
return;
}
$login_files = ['wp-login.php', 'wp-register.php', 'xmlrpc.php'];
$current_file = basename($_SERVER['SCRIPT_FILENAME']);
if (in_array($current_file, $login_files)) {
$allowed = false;
foreach ($this->settings['login_allowed_ips'] as $pattern) {
if (wpban_match_pattern($pattern, $this->ip_address)) {
$allowed = true;
break;
}
}
if (!$allowed) {
$this->log('blocked', 'login_restriction');
wp_redirect(home_url());
exit;
}
}
}
public function check_crawlers() {
if ($this->bypass_active || empty($this->settings['blocked_crawlers'])) {
return;
}
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
foreach ($this->settings['blocked_crawlers'] as $crawler) {
if (stripos($ua, $crawler) !== false) {
$this->log('blocked', 'crawler_block', $crawler);
http_response_code(403);
exit('Access denied');
}
}
}
public function check_browser_restrictions() {
if ($this->bypass_active || empty($this->settings['browser_restrictions'])) {
return;
}
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
if (isset($this->settings['browser_restrictions']['wechat_qq']) &&
$this->settings['browser_restrictions']['wechat_qq']['enabled'] &&
(strpos($ua, 'MQQBrowser') !== false || strpos($ua, 'MicroMessenger') !== false)) {
$this->log('blocked', 'browser_restriction', 'WeChat/QQ');
$this->show_browser_block_message($this->settings['browser_restrictions']['wechat_qq']);
}
}
public function handle_login_failed($username) {
$this->log('failed_login', 'login_attempt', $username);
// Check for brute force
global $wpdb;
$table = $wpdb->prefix . 'wpban_logs';
$count = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM $table WHERE ip = %s AND action = 'failed_login' AND timestamp > %s",
$this->ip_address,
date('Y-m-d H:i:s', strtotime('-1 hour'))
));
if ($count >= 10) {
$this->send_notification('brute_force', [
'attempts' => $count,
'username' => $username
]);
}
}
public function check_login_attempt($user, $username, $password) {
if (empty($username) || empty($password)) {
return $user;
}
// Additional login security checks can be added here
return $user;
}
public function modify_robots_txt($output, $public) {
if (!empty($this->settings['blocked_crawlers'])) {
$output .= "\n# WPBan Security Rules\n";
foreach ($this->settings['blocked_crawlers'] as $crawler) {
$output .= "User-agent: $crawler\n";
$output .= "Disallow: /\n\n";
}
}
return $output;
}
// Helper methods
private function is_ip_banned() {
foreach ($this->settings['banned_ips'] ?? [] as $pattern) {
if (wpban_match_pattern($pattern, $this->ip_address)) {
return true;
}
}
foreach ($this->settings['banned_ranges'] ?? [] as $range) {
if (wpban_ip_in_range($this->ip_address, $range)) {
return true;
}
}
return false;
}
private function is_host_banned() {
if (empty($this->settings['banned_hosts'])) {
return false;
}
$hostname = $this->get_cached_data('hostname_' . $this->ip_address, function() {
return gethostbyaddr($this->ip_address);
}, 3600);
foreach ($this->settings['banned_hosts'] as $pattern) {
if (wpban_match_pattern($pattern, $hostname)) {
return true;
}
}
return false;
}
private function is_referer_banned() {
$referer = $_SERVER['HTTP_REFERER'] ?? '';
if (empty($referer) || empty($this->settings['banned_referers'])) {
return false;
}
foreach ($this->settings['banned_referers'] as $pattern) {
if (wpban_match_pattern($pattern, $referer)) {
return true;
}
}
return false;
}
private function is_agent_banned() {
$agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
if (empty($agent) || empty($this->settings['banned_agents'])) {
return false;
}
foreach ($this->settings['banned_agents'] as $pattern) {
if (wpban_match_pattern($pattern, $agent)) {
return true;
}
}
return false;
}
private function is_whitelisted() {
foreach ($this->settings['whitelist_ips'] ?? [] as $pattern) {
if (wpban_match_pattern($pattern, $this->ip_address) ||
wpban_ip_in_range($this->ip_address, $pattern)) {
return true;
}
}
return false;
}
private function get_country_code($ip) {
global $wpdb;
$table = $wpdb->prefix . 'wpban_geo_cache';
// Check cache first
$cached = $wpdb->get_var($wpdb->prepare(
"SELECT country_code FROM $table WHERE ip = %s",
$ip
));
if ($cached) {
return $cached;
}
// Use IP geolocation service
$country = $this->lookup_country($ip);
// Cache result
if ($country) {
$wpdb->replace($table, [
'ip' => $ip,
'country_code' => $country['code'],
'country_name' => $country['name'],
'city' => $country['city'] ?? null,
'cached_at' => current_time('mysql')
]);
}
return $country['code'] ?? 'XX';
}
private function lookup_country($ip) {
// Try multiple services
$services = [
'ipapi' => "http://ip-api.com/json/{$ip}?fields=status,country,countryCode,city",
'ipinfo' => "https://ipinfo.io/{$ip}/json"
];
foreach ($services as $name => $url) {
$response = wp_remote_get($url, ['timeout' => 2]);
if (!is_wp_error($response)) {
$data = json_decode(wp_remote_retrieve_body($response), true);
if ($name === 'ipapi' && $data['status'] === 'success') {
return [
'code' => $data['countryCode'],
'name' => $data['country'],
'city' => $data['city']
];
} elseif ($name === 'ipinfo' && isset($data['country'])) {
return [
'code' => $data['country'],
'name' => $data['country'],
'city' => $data['city'] ?? null
];
}
}
}
return null;
}
private function log($action, $reason, $details = '') {
if (empty($this->settings['enable_logging'])) {
return;
}
global $wpdb;
$country = $action === 'geo_block' ? $details : $this->get_country_code($this->ip_address);
$wpdb->insert($wpdb->prefix . 'wpban_logs', [
'ip' => $this->ip_address,
'country_code' => $country,
'user_agent' => substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 500),
'referer' => substr($_SERVER['HTTP_REFERER'] ?? '', 0, 500),
'uri' => substr($_SERVER['REQUEST_URI'] ?? '', 0, 500),
'action' => $action,
'reason' => $reason . ($details ? ' - ' . $details : ''),
'timestamp' => current_time('mysql')
]);
}
private function send_notification($type, $data = []) {
if (empty($this->settings['email_notifications']['enabled']) ||
!in_array($type, $this->settings['email_notifications']['events'] ?? [])) {
return;
}
// Check threshold
global $wpdb;
$count = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->prefix}wpban_logs
WHERE ip = %s AND timestamp > %s",
$this->ip_address,
date('Y-m-d H:i:s', strtotime('-1 hour'))
));
if ($count < ($this->settings['email_notifications']['threshold'] ?? 10)) {
return;
}
// Send email
$to = $this->settings['email_notifications']['recipient'];
$subject = sprintf(__('[%s] Security Alert: %s', 'wpban'), get_bloginfo('name'), $type);
$message = $this->format_notification_email($type, $data);
wp_mail($to, $subject, $message);
}
private function format_notification_email($type, $data) {
$site = get_bloginfo('name');
$time = current_time('mysql');
$message = "Security alert on {$site}\n\n";
$message .= "Event: {$type}\n";
$message .= "Time: {$time}\n";
$message .= "IP Address: {$this->ip_address}\n";
$message .= "Country: " . $this->get_country_code($this->ip_address) . "\n";
if (!empty($data)) {
$message .= "\nDetails:\n";
foreach ($data as $key => $value) {
$message .= "- {$key}: {$value}\n";
}
}
$message .= "\nView logs: " . admin_url('admin.php?page=wpban-logs');
return $message;
}
private function show_ban_message() {
$message = $this->settings['ban_message'] ?? '<h1>Access Denied</h1>';
$message = str_replace(
['%IP%', '%DATE%', '%SITE%'],
[$this->ip_address, current_time('mysql'), get_bloginfo('name')],
$message
);
wp_die($message, 'Access Denied', ['response' => 403]);
}
private function show_browser_block_message($settings) {
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo esc_html($settings['title']); ?></title>
<style>
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: rgba(0,0,0,0.9);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.block-container {
background: white;
padding: 40px;
border-radius: 10px;
max-width: 500px;
text-align: center;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
h1 {
margin: 0 0 20px 0;
color: #333;
}
p {
color: #666;
line-height: 1.6;
margin-bottom: 30px;
}
button {
background: #2271b1;
color: white;
border: none;
padding: 12px 30px;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
transition: background 0.3s;
}
button:hover {
background: #135e96;
}
</style>
</head>
<body>
<div class="block-container">
<h1><?php echo esc_html($settings['title']); ?></h1>
<p><?php echo esc_html($settings['message']); ?></p>
<button onclick="copyLink()">
<?php echo esc_html($settings['button_text']); ?>
</button>
</div>
<script>
function copyLink() {
navigator.clipboard.writeText(window.location.href).then(() => {
alert('<?php _e('Link copied to clipboard!', 'wpban'); ?>');
});
}
</script>
</body>
</html>
<?php
exit;
}
private function get_cached_data($key, $callback, $expiration = 3600) {
$value = wp_cache_get($key, $this->cache_group);
if ($value === false) {
$value = $callback();
wp_cache_set($key, $value, $this->cache_group, $expiration);
}
return $value;
}
// Public methods for admin
public function get_logs($filters = [], $page = 1, $per_page = 50) {
global $wpdb;
$table = $wpdb->prefix . 'wpban_logs';
$where = [];
$where_values = [];
if (!empty($filters['date_from'])) {
$where[] = "timestamp >= %s";
$where_values[] = $filters['date_from'] . ' 00:00:00';
}
if (!empty($filters['date_to'])) {
$where[] = "timestamp <= %s";
$where_values[] = $filters['date_to'] . ' 23:59:59';
}
if (!empty($filters['action'])) {
$where[] = "action = %s";
$where_values[] = $filters['action'];
}
if (!empty($filters['ip'])) {
$where[] = "ip LIKE %s";
$where_values[] = '%' . $wpdb->esc_like($filters['ip']) . '%';
}
if (!empty($filters['country'])) {
$where[] = "country_code = %s";
$where_values[] = $filters['country'];
}
$where_clause = $where ? 'WHERE ' . implode(' AND ', $where) : '';
// Get total count
$count_query = "SELECT COUNT(*) FROM $table $where_clause";
$total = $wpdb->get_var($wpdb->prepare($count_query, $where_values));
// Get logs with pagination
$offset = ($page - 1) * $per_page;
$query = "SELECT * FROM $table $where_clause ORDER BY timestamp DESC LIMIT %d OFFSET %d";
$where_values[] = $per_page;
$where_values[] = $offset;
$logs = $wpdb->get_results($wpdb->prepare($query, $where_values));
return [
'logs' => $logs,
'total' => $total,
'pages' => ceil($total / $per_page),
'current_page' => $page
];
}
public function get_stats() {
global $wpdb;
$prefix = $wpdb->prefix;
$stats = [
'total_blocks' => $wpdb->get_var("SELECT COUNT(*) FROM {$prefix}wpban_logs"),
'unique_ips' => $wpdb->get_var("SELECT COUNT(DISTINCT ip) FROM {$prefix}wpban_logs"),
'today_blocks' => $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$prefix}wpban_logs WHERE DATE(timestamp) = %s",
current_time('Y-m-d')
)),
'active_rules' => $this->count_active_rules(),
'top_countries' => $wpdb->get_results(
"SELECT country_code, COUNT(*) as count
FROM {$prefix}wpban_logs
WHERE country_code IS NOT NULL
GROUP BY country_code
ORDER BY count DESC
LIMIT 10"
),
'recent_blocks' => $wpdb->get_results(
"SELECT * FROM {$prefix}wpban_logs
ORDER BY timestamp DESC
LIMIT 10"
)
];
return $stats;
}
private function count_active_rules() {
$count = 0;
$rule_types = [
'banned_ips', 'banned_ranges', 'banned_hosts',
'banned_referers', 'banned_agents', 'blocked_crawlers'
];
foreach ($rule_types as $type) {
$count += count($this->settings[$type] ?? []);
}
return $count;
}
public function clear_cache() {
wp_cache_delete_group($this->cache_group);
}
public function get_templates() {
return [
'basic' => [
'name' => __('Basic Protection', 'wpban'),
'description' => __('Essential security for most WordPress sites', 'wpban'),
'settings' => [
'banned_agents' => ['*bot*', '*crawler*', '*spider*'],
'enable_logging' => true,
'rate_limits' => [
'requests_per_minute' => 60,
'login_per_hour' => 5
]
]
],
'strict' => [
'name' => __('Strict Security', 'wpban'),
'description' => __('Maximum protection with geo-blocking and rate limiting', 'wpban'),
'settings' => [
'banned_agents' => ['*bot*', '*crawler*', '*spider*', '*scraper*', 'curl*', 'wget*'],
'blocked_crawlers' => ['GPTBot', 'ChatGPT-User', 'ClaudeBot', 'CCBot'],
'rate_limits' => [
'requests_per_minute' => 30,
'login_per_hour' => 3,
'api_per_minute' => 10
],
'geo_blocking' => [
'enabled' => true,
'blocked_countries' => []
]
]
],
'content' => [
'name' => __('Content Protection', 'wpban'),
'description' => __('Protect content from AI and scraping', 'wpban'),
'settings' => [
'blocked_crawlers' => array_keys(wpban_get_crawler_list()['ai']),
'banned_agents' => ['*GPT*', '*Claude*', '*AI*Bot*'],
'rate_limits' => [
'requests_per_minute' => 30
]
]
],
'performance' => [
'name' => __('Performance Mode', 'wpban'),
'description' => __('Balanced security with minimal performance impact', 'wpban'),
'settings' => [
'enable_logging' => false,
'rate_limits' => [
'requests_per_minute' => 120,
'login_per_hour' => 10
]
]
]
];
}
}

View file

@ -0,0 +1,131 @@
<?php
if (!defined('ABSPATH')) {
exit;
}
class WPBan_Templates {
private $templates;
public function __construct() {
$this->init_templates();
}
private function init_templates() {
$this->templates = [
'basic' => [
'name' => __('Basic Protection', 'wpban'),
'description' => __('Essential security for most WordPress sites', 'wpban'),
'settings' => [
'banned_agents' => ['*bot*', '*crawler*', '*spider*'],
'blocked_crawlers' => [],
'enable_logging' => true,
'enable_caching' => true,
'ban_message' => '<h1>Access Denied</h1><p>Your access to this site has been restricted.</p>'
]
],
'strict' => [
'name' => __('Strict Security', 'wpban'),
'description' => __('Maximum protection with login restrictions', 'wpban'),
'settings' => [
'banned_agents' => ['*bot*', '*crawler*', '*spider*', '*scraper*', 'curl*', 'wget*'],
'blocked_crawlers' => ['GPTBot', 'ChatGPT-User', 'ClaudeBot', 'CCBot', 'PerplexityBot', 'Bytespider'],
'browser_restrictions' => [
'wechat_qq' => [
'enabled' => true,
'title' => __('Browser Not Supported', 'wpban'),
'message' => __('Please use Chrome, Firefox, or Safari to access this site.', 'wpban'),
'button_text' => __('Copy Link', 'wpban')
]
],
'enable_logging' => true,
'enable_caching' => true
]
],
'content' => [
'name' => __('Content Protection', 'wpban'),
'description' => __('Protect original content from AI crawlers', 'wpban'),
'settings' => [
'blocked_crawlers' => [
'GPTBot', 'ChatGPT-User', 'ClaudeBot', 'Claude-Web', 'anthropic-ai',
'CCBot', 'PerplexityBot', 'Bytespider', 'FacebookBot', 'Meta-ExternalAgent',
'cohere-ai', 'AI2Bot', 'Ai2Bot-Dolma', 'Google-Extended', 'ImagesiftBot'
],
'banned_agents' => ['*GPT*', '*Claude*', '*AI*Bot*'],
'enable_logging' => true,
'enable_caching' => true
]
],
'membership' => [
'name' => __('Membership Site', 'wpban'),
'description' => __('Ideal for private or membership websites', 'wpban'),
'settings' => [
'banned_agents' => ['*bot*', '*crawler*', '*spider*', '*scraper*'],
'blocked_crawlers' => array_merge(
wpban_get_crawler_list()['ai'],
wpban_get_crawler_list()['seo']
),
'banned_referers' => ['*google.*', '*bing.*', '*yahoo.*', '*baidu.*'],
'enable_logging' => true,
'enable_caching' => true
]
],
'development' => [
'name' => __('Development Mode', 'wpban'),
'description' => __('Minimal restrictions for development sites', 'wpban'),
'settings' => [
'enable_logging' => true,
'enable_caching' => false,
'ban_message' => '<h1>Site Under Development</h1><p>This site is currently under development.</p>'
]
]
];
}
public function get_all_templates() {
return $this->templates;
}
public function get_template($id) {
return isset($this->templates[$id]) ? $this->templates[$id] : null;
}
public function apply_template($template_id) {
$template = $this->get_template($template_id);
if (!$template) {
return false;
}
// Get current settings
$current_settings = get_option('wpban_settings', []);
// Merge template settings with defaults
$new_settings = array_merge(
[
'banned_ips' => [],
'banned_ranges' => [],
'banned_hosts' => [],
'banned_referers' => [],
'banned_agents' => [],
'whitelist_ips' => [],
'login_allowed_ips' => [],
'blocked_crawlers' => [],
'browser_restrictions' => [],
'ban_message' => '<h1>Access Denied</h1><p>Your access to this site has been restricted.</p>',
'enable_logging' => true,
'enable_caching' => true,
'reverse_proxy' => false
],
$template['settings']
);
// Preserve some user settings
$preserve_fields = ['whitelist_ips', 'reverse_proxy'];
foreach ($preserve_fields as $field) {
if (isset($current_settings[$field])) {
$new_settings[$field] = $current_settings[$field];
}
}
return $new_settings;
}
}

View file

@ -1,44 +1,395 @@
<?php
namespace WPBan_Anything;
if (!defined('ABSPATH')) {
exit;
}
function ban_anything_get_ip() {
/**
* Get client IP address with proxy support
*/
function wpban_get_ip($use_proxy = false) {
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
$options = get_option('banned_options', ['reverse_proxy' => 0]);
if ($options['reverse_proxy']) {
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0];
} elseif (!empty($_SERVER['HTTP_X_REAL_IP'])) {
$ip = $_SERVER['HTTP_X_REAL_IP'];
} elseif (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
if ($use_proxy) {
$proxy_headers = [
'HTTP_CF_CONNECTING_IP', // Cloudflare
'HTTP_X_FORWARDED_FOR', // Standard proxy
'HTTP_X_REAL_IP', // Nginx proxy
'HTTP_CLIENT_IP', // Some proxies
'HTTP_X_FORWARDED', // Some proxies
'HTTP_X_CLUSTER_CLIENT_IP', // Some proxies
'HTTP_FORWARDED_FOR', // Some proxies
'HTTP_FORWARDED' // RFC 7239
];
foreach ($proxy_headers as $header) {
if (!empty($_SERVER[$header])) {
$ips = explode(',', $_SERVER[$header]);
$ip = trim($ips[0]);
// Validate IP
if (filter_var($ip, FILTER_VALIDATE_IP)) {
break;
}
}
}
return sanitize_text_field($ip);
}
function preg_match_wildcard($pattern, $subject) {
$pattern = preg_quote($pattern, '#');
$pattern = str_replace('\*', '.*', $pattern);
return preg_match("#^$pattern$#i", $subject);
// Final validation
return filter_var($ip, FILTER_VALIDATE_IP) ? $ip : '0.0.0.0';
}
function ban_anything_ip_in_range($ip, $ranges) {
foreach ($ranges as $range) {
/**
* Match pattern with wildcard support
*/
function wpban_match_pattern($pattern, $string) {
// Convert wildcard pattern to regex
$pattern = str_replace(
['*', '?', '[', ']', '(', ')', '{', '}', '^', '$', '+', '.', '\\'],
['.*', '.', '\[', '\]', '\(', '\)', '\{', '\}', '\^', '\$', '\+', '\.', '\\\\'],
$pattern
);
return preg_match('/^' . $pattern . '$/i', $string);
}
/**
* Check if IP is in range (CIDR or range notation)
*/
function wpban_ip_in_range($ip, $range) {
if (strpos($range, '/') !== false) {
// CIDR notation
list($subnet, $bits) = explode('/', $range);
$ip_long = ip2long($ip);
$subnet_long = ip2long($subnet);
$mask = -1 << (32 - (int)$bits);
if (($ip_long & $mask) === ($subnet_long & $mask)) {
return true;
}
} elseif ($ip === $range) {
return true;
}
}
if ($bits < 0 || $bits > 32) {
return false;
}
$ip_long = ip2long($ip);
$subnet_long = ip2long($subnet);
if ($ip_long === false || $subnet_long === false) {
return false;
}
$mask = -1 << (32 - $bits);
return ($ip_long & $mask) == ($subnet_long & $mask);
} elseif (strpos($range, '-') !== false) {
// Range notation (192.168.1.1-192.168.1.255)
list($start, $end) = explode('-', $range);
$ip_long = ip2long($ip);
$start_long = ip2long(trim($start));
$end_long = ip2long(trim($end));
if ($ip_long === false || $start_long === false || $end_long === false) {
return false;
}
return ($ip_long >= $start_long && $ip_long <= $end_long);
}
// Check if it's a pattern
return wpban_match_pattern($range, $ip);
}
/**
* Get crawler list
*/
function wpban_get_crawler_list() {
return [
'ai' => [
// OpenAI
'GPTBot' => ['description' => __('OpenAI GPT Web Crawler', 'wpban')],
'ChatGPT-User' => ['description' => __('ChatGPT Browser Tool', 'wpban')],
'OAI-SearchBot' => ['description' => __('OpenAI Search Bot', 'wpban')],
// Anthropic
'ClaudeBot' => ['description' => __('Anthropic Claude Bot', 'wpban')],
'Claude-Web' => ['description' => __('Claude Web Browser', 'wpban')],
'anthropic-ai' => ['description' => __('Anthropic AI Crawler', 'wpban')],
// Google
'Google-Extended' => ['description' => __('Google Bard/Gemini Bot', 'wpban')],
// Microsoft
'Bingbot' => ['description' => __('Microsoft Bing AI', 'wpban')],
// Meta
'FacebookBot' => ['description' => __('Meta/Facebook Bot', 'wpban')],
'Meta-ExternalAgent' => ['description' => __('Meta External Agent', 'wpban')],
'Meta-ExternalFetcher' => ['description' => __('Meta External Fetcher', 'wpban')],
// Others
'CCBot' => ['description' => __('Common Crawl Dataset', 'wpban')],
'PerplexityBot' => ['description' => __('Perplexity AI Search', 'wpban')],
'YouBot' => ['description' => __('You.com Search Bot', 'wpban')],
'Bytespider' => ['description' => __('ByteDance Spider', 'wpban')],
'cohere-ai' => ['description' => __('Cohere AI Bot', 'wpban')],
'Diffbot' => ['description' => __('Diffbot Crawler', 'wpban')],
'Amazonbot' => ['description' => __('Amazon Alexa Bot', 'wpban')],
'Applebot-Extended' => ['description' => __('Apple AI Bot', 'wpban')],
'AI2Bot' => ['description' => __('Allen Institute AI', 'wpban')],
'Ai2Bot-Dolma' => ['description' => __('AI2 Dolma Dataset', 'wpban')],
'Omgilibot' => ['description' => __('Webz.io Bot', 'wpban')],
'webzio' => ['description' => __('Webz.io Crawler', 'wpban')],
'ImagesiftBot' => ['description' => __('Image Analysis Bot', 'wpban')],
'PetalBot' => ['description' => __('Huawei Petal Search', 'wpban')],
],
'seo' => [
// Major Search Engines
'Googlebot' => ['description' => __('Google Search (DO NOT BLOCK)', 'wpban')],
'Bingbot' => ['description' => __('Bing Search (DO NOT BLOCK)', 'wpban')],
'Slurp' => ['description' => __('Yahoo Search', 'wpban')],
'DuckDuckBot' => ['description' => __('DuckDuckGo Search', 'wpban')],
'Baiduspider' => ['description' => __('Baidu Search (China)', 'wpban')],
'YandexBot' => ['description' => __('Yandex Search (Russia)', 'wpban')],
// SEO Tools
'AhrefsBot' => ['description' => __('Ahrefs SEO Tool', 'wpban')],
'SemrushBot' => ['description' => __('Semrush SEO Tool', 'wpban')],
'MJ12bot' => ['description' => __('Majestic SEO Tool', 'wpban')],
'DotBot' => ['description' => __('Moz SEO Tool', 'wpban')],
'Screaming Frog' => ['description' => __('SEO Spider Tool', 'wpban')],
// Other
'ia_archiver' => ['description' => __('Internet Archive', 'wpban')],
'facebookexternalhit' => ['description' => __('Facebook Link Preview', 'wpban')],
'LinkedInBot' => ['description' => __('LinkedIn Preview', 'wpban')],
'WhatsApp' => ['description' => __('WhatsApp Link Preview', 'wpban')],
'Twitterbot' => ['description' => __('Twitter Link Preview', 'wpban')],
]
];
}
/**
* Get country list
*/
function wpban_get_country_list() {
return [
'AF' => __('Afghanistan', 'wpban'),
'AL' => __('Albania', 'wpban'),
'DZ' => __('Algeria', 'wpban'),
'AR' => __('Argentina', 'wpban'),
'AM' => __('Armenia', 'wpban'),
'AU' => __('Australia', 'wpban'),
'AT' => __('Austria', 'wpban'),
'AZ' => __('Azerbaijan', 'wpban'),
'BD' => __('Bangladesh', 'wpban'),
'BY' => __('Belarus', 'wpban'),
'BE' => __('Belgium', 'wpban'),
'BR' => __('Brazil', 'wpban'),
'BG' => __('Bulgaria', 'wpban'),
'CA' => __('Canada', 'wpban'),
'CL' => __('Chile', 'wpban'),
'CN' => __('China', 'wpban'),
'CO' => __('Colombia', 'wpban'),
'HR' => __('Croatia', 'wpban'),
'CZ' => __('Czech Republic', 'wpban'),
'DK' => __('Denmark', 'wpban'),
'EG' => __('Egypt', 'wpban'),
'EE' => __('Estonia', 'wpban'),
'FI' => __('Finland', 'wpban'),
'FR' => __('France', 'wpban'),
'GE' => __('Georgia', 'wpban'),
'DE' => __('Germany', 'wpban'),
'GR' => __('Greece', 'wpban'),
'HK' => __('Hong Kong', 'wpban'),
'HU' => __('Hungary', 'wpban'),
'IS' => __('Iceland', 'wpban'),
'IN' => __('India', 'wpban'),
'ID' => __('Indonesia', 'wpban'),
'IR' => __('Iran', 'wpban'),
'IQ' => __('Iraq', 'wpban'),
'IE' => __('Ireland', 'wpban'),
'IL' => __('Israel', 'wpban'),
'IT' => __('Italy', 'wpban'),
'JP' => __('Japan', 'wpban'),
'KZ' => __('Kazakhstan', 'wpban'),
'KE' => __('Kenya', 'wpban'),
'KR' => __('South Korea', 'wpban'),
'LV' => __('Latvia', 'wpban'),
'LT' => __('Lithuania', 'wpban'),
'MY' => __('Malaysia', 'wpban'),
'MX' => __('Mexico', 'wpban'),
'MA' => __('Morocco', 'wpban'),
'NL' => __('Netherlands', 'wpban'),
'NZ' => __('New Zealand', 'wpban'),
'NG' => __('Nigeria', 'wpban'),
'NO' => __('Norway', 'wpban'),
'PK' => __('Pakistan', 'wpban'),
'PE' => __('Peru', 'wpban'),
'PH' => __('Philippines', 'wpban'),
'PL' => __('Poland', 'wpban'),
'PT' => __('Portugal', 'wpban'),
'RO' => __('Romania', 'wpban'),
'RU' => __('Russia', 'wpban'),
'SA' => __('Saudi Arabia', 'wpban'),
'RS' => __('Serbia', 'wpban'),
'SG' => __('Singapore', 'wpban'),
'SK' => __('Slovakia', 'wpban'),
'SI' => __('Slovenia', 'wpban'),
'ZA' => __('South Africa', 'wpban'),
'ES' => __('Spain', 'wpban'),
'SE' => __('Sweden', 'wpban'),
'CH' => __('Switzerland', 'wpban'),
'TW' => __('Taiwan', 'wpban'),
'TH' => __('Thailand', 'wpban'),
'TR' => __('Turkey', 'wpban'),
'UA' => __('Ukraine', 'wpban'),
'AE' => __('United Arab Emirates', 'wpban'),
'GB' => __('United Kingdom', 'wpban'),
'US' => __('United States', 'wpban'),
'UZ' => __('Uzbekistan', 'wpban'),
'VE' => __('Venezuela', 'wpban'),
'VN' => __('Vietnam', 'wpban'),
];
}
/**
* Sanitize and validate IP address
*/
function wpban_sanitize_ip($ip) {
// Remove any whitespace
$ip = trim($ip);
// Check if it's a valid IP
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
// Check if it's a pattern with wildcards
if (strpos($ip, '*') !== false) {
// Replace wildcards with 0 for validation
$test_ip = str_replace('*', '0', $ip);
if (filter_var($test_ip, FILTER_VALIDATE_IP)) {
return $ip;
}
}
return false;
}
/**
* Format bytes to human readable
*/
function wpban_format_bytes($bytes, $precision = 2) {
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= (1 << (10 * $pow));
return round($bytes, $precision) . ' ' . $units[$pow];
}
/**
* Get user's country flag emoji
*/
function wpban_country_flag($country_code) {
if (strlen($country_code) !== 2) {
return '';
}
$country_code = strtoupper($country_code);
$flag = '';
// Convert country code to flag emoji
for ($i = 0; $i < strlen($country_code); $i++) {
$flag .= mb_chr(ord($country_code[$i]) - ord('A') + 0x1F1E6, 'UTF-8');
}
return $flag;
}
/**
* Log security event (helper function)
*/
function wpban_log($action, $reason, $details = '') {
if (isset($GLOBALS['wpban_security'])) {
$GLOBALS['wpban_security']->log($action, $reason, $details);
}
}
/**
* Check if current user is whitelisted
*/
function wpban_is_current_user_whitelisted() {
$settings = get_option('wpban_settings', []);
$ip = wpban_get_ip($settings['reverse_proxy'] ?? false);
foreach ($settings['whitelist_ips'] ?? [] as $pattern) {
if (wpban_match_pattern($pattern, $ip) || wpban_ip_in_range($ip, $pattern)) {
return true;
}
}
return false;
}
/**
* Get attack type label
*/
function wpban_get_attack_type_label($type) {
$labels = [
'ip_ban' => __('IP Ban', 'wpban'),
'rate_limit' => __('Rate Limit', 'wpban'),
'geo_block' => __('Geo Block', 'wpban'),
'crawler_block' => __('Crawler Block', 'wpban'),
'failed_login' => __('Failed Login', 'wpban'),
'brute_force' => __('Brute Force', 'wpban'),
'banned' => __('Banned', 'wpban'),
'blocked' => __('Blocked', 'wpban'),
];
return $labels[$type] ?? $type;
}
/**
* Export settings
*/
function wpban_export_settings() {
$settings = get_option('wpban_settings', []);
$bypass_path = get_option('wpban_bypass_path', '');
$export = [
'version' => WPBAN_VERSION,
'timestamp' => current_time('timestamp'),
'site_url' => get_site_url(),
'settings' => $settings,
'bypass_path' => $bypass_path
];
return json_encode($export, JSON_PRETTY_PRINT);
}
/**
* Import settings
*/
function wpban_import_settings($json) {
$data = json_decode($json, true);
if (!$data || !isset($data['settings'])) {
return false;
}
// Validate version compatibility
if (version_compare($data['version'], '4.0', '<')) {
return false;
}
// Import settings
update_option('wpban_settings', $data['settings']);
// Optionally import bypass path
if (!empty($data['bypass_path'])) {
update_option('wpban_bypass_path', $data['bypass_path']);
}
// Clear cache
if (isset($GLOBALS['wpban_security'])) {
$GLOBALS['wpban_security']->clear_cache();
}
return true;
}

638
languages/wpban.pot Normal file
View file

@ -0,0 +1,638 @@
# wpban Pro Language Template
# Copyright (C) 2024 WPBan
# This file is distributed under the GPLv2 or later.
msgid ""
msgstr ""
"Project-Id-Version: wpban Pro 5.0\n"
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/wpban\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"POT-Creation-Date: 2024-01-01T00:00:00+00:00\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"X-Generator: WP-CLI 2.9.0\n"
"X-Domain: wpban\n"
#. Plugin Name of the plugin
msgid "wpban Pro"
msgstr ""
#. Plugin URI of the plugin
msgid "https://wpban.com/"
msgstr ""
#. Description of the plugin
msgid "Advanced WordPress security with geo-blocking, rate limiting, and intelligent threat detection."
msgstr ""
#. Author of the plugin
msgid "WPBan"
msgstr ""
#. Author URI of the plugin
msgid "https://wpban.com"
msgstr ""
#: includes/class-wpban-admin.php:30
msgid "WPBan Security"
msgstr ""
#: includes/class-wpban-admin.php:38
msgid "Settings"
msgstr ""
#: includes/class-wpban-admin.php:46
msgid "Security Logs"
msgstr ""
#: includes/class-wpban-admin.php:47
msgid "Logs"
msgstr ""
#: includes/class-wpban-admin.php:54
msgid "Tools"
msgstr ""
#: includes/class-wpban-admin.php:80
msgid "Are you sure you want to clear all logs?"
msgstr ""
#: includes/class-wpban-admin.php:81
msgid "This will replace your current settings. Continue?"
msgstr ""
#: includes/class-wpban-admin.php:82
msgid "Test email sent!"
msgstr ""
#: includes/class-wpban-admin.php:83
msgid "An error occurred"
msgstr ""
#: includes/class-wpban-admin.php:95
msgid "WPBan Security Dashboard"
msgstr ""
#: includes/class-wpban-admin.php:101
msgid "Total Blocks"
msgstr ""
#: includes/class-wpban-admin.php:115
msgid "Unique IPs"
msgstr ""
#: includes/class-wpban-admin.php:120
msgid "Today's Blocks"
msgstr ""
#: includes/class-wpban-admin.php:125
msgid "Active Rules"
msgstr ""
#: includes/class-wpban-admin.php:134
msgid "Quick Actions"
msgstr ""
#: includes/class-wpban-admin.php:136
msgid "Emergency Bypass URL:"
msgstr ""
#: includes/class-wpban-admin.php:142
msgid "Copy"
msgstr ""
#: includes/class-wpban-admin.php:145
msgid "Save this URL to access your site if you get locked out."
msgstr ""
#: includes/class-wpban-admin.php:150
msgid "Configure Settings"
msgstr ""
#: includes/class-wpban-admin.php:153
msgid "View Logs"
msgstr ""
#: includes/class-wpban-admin.php:160
msgid "Top Blocked Countries"
msgstr ""
#: includes/class-wpban-admin.php:187
msgid "Security Templates"
msgstr ""
#: includes/class-wpban-admin.php:188
msgid "Quickly apply pre-configured security settings."
msgstr ""
#: includes/class-wpban-admin.php:195
msgid "Apply"
msgstr ""
#: includes/class-wpban-admin.php:204
msgid "Recent Activity"
msgstr ""
#: includes/class-wpban-admin.php:209 includes/class-wpban-admin.php:468
msgid "Time"
msgstr ""
#: includes/class-wpban-admin.php:210 includes/class-wpban-admin.php:469
msgid "IP"
msgstr ""
#: includes/class-wpban-admin.php:211 includes/class-wpban-admin.php:470
msgid "Country"
msgstr ""
#: includes/class-wpban-admin.php:212 includes/class-wpban-admin.php:471
msgid "Action"
msgstr ""
#: includes/class-wpban-admin.php:213 includes/class-wpban-admin.php:472
msgid "Details"
msgstr ""
#: includes/class-wpban-admin.php:236
msgid "WPBan Security Settings"
msgstr ""
#: includes/class-wpban-admin.php:243
msgid "General"
msgstr ""
#: includes/class-wpban-admin.php:244
msgid "IP Rules"
msgstr ""
#: includes/class-wpban-admin.php:245
msgid "Advanced"
msgstr ""
#: includes/class-wpban-admin.php:246
msgid "Rate Limiting"
msgstr ""
#: includes/class-wpban-admin.php:247
msgid "Geo Blocking"
msgstr ""
#: includes/class-wpban-admin.php:248
msgid "Crawlers"
msgstr ""
#: includes/class-wpban-admin.php:249
msgid "Notifications"
msgstr ""
#: includes/class-wpban-admin.php:256
msgid "Enable Logging"
msgstr ""
#: includes/class-wpban-admin.php:261
msgid "Log all security events"
msgstr ""
#: includes/class-wpban-admin.php:266
msgid "Reverse Proxy"
msgstr ""
#: includes/class-wpban-admin.php:271
msgid "Server is behind a reverse proxy (Cloudflare, nginx, etc.)"
msgstr ""
#: includes/class-wpban-admin.php:276
msgid "Ban Message"
msgstr ""
#: includes/class-wpban-admin.php:287
msgid "Variables: %IP%, %DATE%, %SITE%"
msgstr ""
#: includes/class-wpban-admin.php:295
msgid "Banned IPs"
msgstr ""
#: includes/class-wpban-admin.php:300
msgid "One per line. Use * for wildcards (e.g., 192.168.*.*)"
msgstr ""
#: includes/class-wpban-admin.php:304
msgid "IP Ranges"
msgstr ""
#: includes/class-wpban-admin.php:309
msgid "CIDR: 192.168.0.0/24 or Range: 192.168.0.1-192.168.0.255"
msgstr ""
#: includes/class-wpban-admin.php:313
msgid "Whitelist IPs"
msgstr ""
#: includes/class-wpban-admin.php:318
msgid "These IPs will never be blocked"
msgstr ""
#: includes/class-wpban-admin.php:322
msgid "Login Protection"
msgstr ""
#: includes/class-wpban-admin.php:327
msgid "Only these IPs can access wp-login.php (leave empty to allow all)"
msgstr ""
#: includes/class-wpban-admin.php:336
msgid "Banned Hosts"
msgstr ""
#: includes/class-wpban-admin.php:341
msgid "e.g., *.badhost.com"
msgstr ""
#: includes/class-wpban-admin.php:345
msgid "Banned Referers"
msgstr ""
#: includes/class-wpban-admin.php:352
msgid "Banned User Agents"
msgstr ""
#: includes/class-wpban-admin.php:359
msgid "Browser Restrictions"
msgstr ""
#: includes/class-wpban-admin.php:365
msgid "Block WeChat/QQ Browsers"
msgstr ""
#: includes/class-wpban-admin.php:375
msgid "Rate Limiting Settings"
msgstr ""
#: includes/class-wpban-admin.php:376
msgid "Protect against floods and brute force attacks by limiting request rates."
msgstr ""
#: includes/class-wpban-admin.php:380
msgid "General Requests"
msgstr ""
#: includes/class-wpban-admin.php:385
msgid "requests per minute"
msgstr ""
#: includes/class-wpban-admin.php:389
msgid "Login Attempts"
msgstr ""
#: includes/class-wpban-admin.php:394
msgid "attempts per hour"
msgstr ""
#: includes/class-wpban-admin.php:398
msgid "API Requests"
msgstr ""
#: includes/class-wpban-admin.php:410
msgid "Geographic Blocking"
msgstr ""
#: includes/class-wpban-admin.php:414
msgid "Enable Geo Blocking"
msgstr ""
#: includes/class-wpban-admin.php:419
msgid "Block or allow access based on country"
msgstr ""
#: includes/class-wpban-admin.php:424
msgid "Block Mode"
msgstr ""
#: includes/class-wpban-admin.php:429
msgid "Block selected countries"
msgstr ""
#: includes/class-wpban-admin.php:434
msgid "Allow only selected countries"
msgstr ""
#: includes/class-wpban-admin.php:439
msgid "Countries"
msgstr ""
#: includes/class-wpban-admin.php:454
msgid "Hold Ctrl/Cmd to select multiple countries"
msgstr ""
#: includes/class-wpban-admin.php:463
msgid "Select All AI"
msgstr ""
#: includes/class-wpban-admin.php:466
msgid "Select All SEO"
msgstr ""
#: includes/class-wpban-admin.php:469
msgid "Deselect All"
msgstr ""
#: includes/class-wpban-admin.php:479
msgid "AI Crawlers"
msgstr ""
#: includes/class-wpban-admin.php:494
msgid "SEO Crawlers"
msgstr ""
#: includes/class-wpban-admin.php:496
msgid "⚠️ Blocking SEO crawlers may affect your search engine rankings!"
msgstr ""
#: includes/class-wpban-admin.php:516
msgid "Enable Email Notifications"
msgstr ""
#: includes/class-wpban-admin.php:521
msgid "Send email alerts for security events"
msgstr ""
#: includes/class-wpban-admin.php:526
msgid "Recipient Email"
msgstr ""
#: includes/class-wpban-admin.php:532
msgid "Send Test Email"
msgstr ""
#: includes/class-wpban-admin.php:537
msgid "Alert Threshold"
msgstr ""
#: includes/class-wpban-admin.php:542
msgid "Send alert after this many blocks from same IP"
msgstr ""
#: includes/class-wpban-admin.php:546
msgid "Alert Events"
msgstr ""
#: includes/class-wpban-admin.php:549
msgid "Rate limit exceeded"
msgstr ""
#: includes/class-wpban-admin.php:550
msgid "Geographic block"
msgstr ""
#: includes/class-wpban-admin.php:551
msgid "Brute force attempt"
msgstr ""
#: includes/class-wpban-admin.php:552
msgid "Crawler blocked"
msgstr ""
#: includes/class-wpban-admin.php:571
msgid "Save Settings"
msgstr ""
#: includes/class-wpban-admin.php:597
msgid "From date"
msgstr ""
#: includes/class-wpban-admin.php:600
msgid "To date"
msgstr ""
#: includes/class-wpban-admin.php:603
msgid "All Actions"
msgstr ""
#: includes/class-wpban-admin.php:604
msgid "Banned"
msgstr ""
#: includes/class-wpban-admin.php:605
msgid "Blocked"
msgstr ""
#: includes/class-wpban-admin.php:606
msgid "Failed Login"
msgstr ""
#: includes/class-wpban-admin.php:607
msgid "Bypass Used"
msgstr ""
#: includes/class-wpban-admin.php:611
msgid "IP Address"
msgstr ""
#: includes/class-wpban-admin.php:614
msgid "Country Code"
msgstr ""
#: includes/class-wpban-admin.php:616
msgid "Filter"
msgstr ""
#: includes/class-wpban-admin.php:617
msgid "Reset"
msgstr ""
#: includes/class-wpban-admin.php:620
msgid "Export CSV"
msgstr ""
#: includes/class-wpban-admin.php:624
msgid "Clear Logs"
msgstr ""
#: includes/class-wpban-admin.php:638
msgid "User Agent"
msgstr ""
#: includes/class-wpban-admin.php:639
msgid "URI"
msgstr ""
#: includes/class-wpban-admin.php:645
msgid "No logs found."
msgstr ""
#: includes/class-wpban-admin.php:688
msgid "WPBan Tools"
msgstr ""
#: includes/class-wpban-admin.php:694
msgid "Import/Export Settings"
msgstr ""
#: includes/class-wpban-admin.php:695
msgid "Backup your settings or migrate to another site."
msgstr ""
#: includes/class-wpban-admin.php:697
msgid "Export"
msgstr ""
#: includes/class-wpban-admin.php:700
msgid "Download Settings"
msgstr ""
#: includes/class-wpban-admin.php:704
msgid "Import"
msgstr ""
#: includes/class-wpban-admin.php:714
msgid "Import Settings"
msgstr ""
#: includes/class-wpban-admin.php:722
msgid "Database Maintenance"
msgstr ""
#: includes/class-wpban-admin.php:723
msgid "Optimize your WPBan database tables."
msgstr ""
#: includes/class-wpban-admin.php:736
msgid "Total Log Entries"
msgstr ""
#: includes/class-wpban-admin.php:740
msgid "Database Size"
msgstr ""
#: includes/class-wpban-admin.php:747
msgid "Optimize Tables"
msgstr ""
#: includes/class-wpban-admin.php:751
msgid "Clear Logs Older Than 30 Days"
msgstr ""
#: includes/class-wpban-admin.php:758
msgid "System Information"
msgstr ""
#: includes/class-wpban-admin.php:829
msgid "Settings saved successfully!"
msgstr ""
#: includes/class-wpban-admin.php:844
msgid "Invalid template"
msgstr ""
#: includes/class-wpban-admin.php:853
msgid "Template applied successfully!"
msgstr ""
#: includes/class-wpban-admin.php:928
msgid "Logs cleared successfully!"
msgstr ""
#: includes/class-wpban-admin.php:949
msgid "Test email sent successfully!"
msgstr ""
#: includes/class-wpban-admin.php:951
msgid "Failed to send test email. Please check your email settings."
msgstr ""
#: includes/class-wpban-security.php:109
msgid "Too many requests. Please try again later."
msgstr ""
#: includes/class-wpban-security.php:144
#: includes/class-wpban-security.php:152
msgid "Access denied from your location."
msgstr ""
#: includes/class-wpban-security.php:415
msgid "Basic Protection"
msgstr ""
#: includes/class-wpban-security.php:416
msgid "Essential security for most WordPress sites"
msgstr ""
#: includes/class-wpban-security.php:427
msgid "Strict Security"
msgstr ""
#: includes/class-wpban-security.php:428
msgid "Maximum protection with geo-blocking and rate limiting"
msgstr ""
#: includes/class-wpban-security.php:443
msgid "Content Protection"
msgstr ""
#: includes/class-wpban-security.php:444
msgid "Protect content from AI and scraping"
msgstr ""
#: includes/class-wpban-security.php:454
msgid "Performance Mode"
msgstr ""
#: includes/class-wpban-security.php:455
msgid "Balanced security with minimal performance impact"
msgstr ""
#: includes/functions.php:149
msgid "OpenAI GPT Web Crawler"
msgstr ""
#: includes/functions.php:150
msgid "ChatGPT Browser Tool"
msgstr ""
#: includes/functions.php:230
msgid "Google Search (DO NOT BLOCK)"
msgstr ""
#: includes/functions.php:231
msgid "Bing Search (DO NOT BLOCK)"
msgstr ""
#. Add all country names
#: includes/functions.php:264
msgid "Afghanistan"
msgstr ""
#: includes/functions.php:265
msgid "Albania"
msgstr ""
#: includes/functions.php:327
msgid "United States"
msgstr ""
#: includes/functions.php:416
msgid "IP Ban"
msgstr ""
#: includes/functions.php:417
msgid "Rate Limit"
msgstr ""
#: includes/functions.php:418
msgid "Geo Block"
msgstr ""
#: includes/functions.php:419
msgid "Crawler Block"
msgstr ""
#: includes/functions.php:420
msgid "Failed Login"
msgstr ""
#: includes/functions.php:421
msgid "Brute Force"
msgstr ""

236
readme.txt Normal file
View file

@ -0,0 +1,236 @@
=== wpban Pro ===
Contributors: wpban
Tags: security, firewall, ban, geo-blocking, rate-limiting, crawler-blocking, brute-force, ip-blocking
Requires at least: 6.7.2
Tested up to: 6.7.2
Stable tag: 5.0
Requires PHP: 7.4
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Advanced WordPress security plugin with geo-blocking, rate limiting, AI crawler blocking, and intelligent threat detection.
== Description ==
wpban Pro is a comprehensive security solution for WordPress that protects your site from various threats including malicious bots, brute force attacks, content scrapers, and unauthorized access attempts.
= Key Features =
**🛡️ IP Management**
* Ban IPs with wildcard support (e.g., 192.168.*.*)
* IP range blocking (CIDR and range notation)
* Whitelist trusted IPs
* Automatic reverse proxy detection
**🌍 Geographic Blocking**
* Block or allow specific countries
* Real-time IP geolocation
* Cached country lookups for performance
* Whitelist/blacklist modes
**⚡ Rate Limiting**
* Protect against DDoS and flood attacks
* Separate limits for general requests, login attempts, and API calls
* Automatic temporary bans for violators
* Customizable thresholds
**🤖 Crawler Control**
* Block 40+ AI crawlers (GPTBot, ClaudeBot, etc.)
* Control SEO crawler access
* Protect content from AI training datasets
* robots.txt integration
**📊 Advanced Logging**
* Detailed security event logs
* Filter by date, action, IP, or country
* Export logs to CSV
* Automatic log rotation
**📧 Email Notifications**
* Real-time security alerts
* Customizable alert thresholds
* Multiple event types
* Test email functionality
**🚪 Login Protection**
* Restrict wp-login.php access by IP
* Brute force detection
* Failed login tracking
* Emergency bypass URL
**🎯 Security Templates**
* Quick setup with pre-configured templates
* Basic, Strict, Content Protection, and Performance modes
* One-click application
* Customizable settings
**🔧 Additional Features**
* Browser restrictions (block WeChat/QQ)
* User agent filtering
* Referer blocking
* Host-based banning
* Import/export settings
* Database optimization tools
= Performance Optimized =
* Smart caching system
* Optimized database queries with indexes
* Minimal performance impact
* Lazy loading of features
= Emergency Access =
Never get locked out! WPBan provides an emergency bypass URL that allows you to access your site even if your IP gets banned accidentally.
== Installation ==
1. Upload the `wpban` folder to `/wp-content/plugins/`
2. Activate the plugin through the 'Plugins' menu in WordPress
3. Go to 'WPBan Security' in your admin menu
4. Choose a security template or configure settings manually
5. Save your emergency bypass URL in a safe place
= Minimum Requirements =
* WordPress 6.7.2 or higher
* PHP 7.4 or higher
* MySQL 5.7 or higher
== Frequently Asked Questions ==
= Will this plugin slow down my website? =
No. WPBan is designed with performance in mind. It uses intelligent caching, optimized database queries, and only loads features when needed.
= What happens if I accidentally ban myself? =
Use the emergency bypass URL provided in the dashboard. This special URL allows you to access your site and disable the ban. Always save this URL in a secure location.
= Can I block entire countries? =
Yes! WPBan includes geographic blocking that allows you to block or exclusively allow specific countries. The plugin uses free IP geolocation services for this feature.
= Will blocking SEO crawlers hurt my rankings? =
Yes, blocking major search engine crawlers (Googlebot, Bingbot) will negatively impact your SEO. The plugin shows warnings for these critical crawlers. Only block SEO crawlers if you have a specific reason.
= How do I protect against AI content scraping? =
Use the "Content Protection" template which blocks major AI crawlers, or manually select AI crawlers to block in the Crawlers settings. The plugin blocks access and adds robots.txt rules.
= Can I import/export settings? =
Yes! Go to Tools > Import/Export to backup your settings or migrate them to another site.
= How long are logs kept? =
Logs are automatically cleaned after 30 days to prevent database bloat. You can export logs before they're deleted or manually clear them at any time.
= Does it work with Cloudflare? =
Yes! Enable the "Reverse Proxy" option in General settings to properly detect visitor IPs when using Cloudflare or other proxy services.
== Screenshots ==
1. Dashboard - Overview of security statistics and quick actions
2. Security Templates - One-click security configurations
3. IP Rules - Manage banned IPs, ranges, and whitelists
4. Rate Limiting - Configure request limits
5. Geographic Blocking - Block or allow countries
6. Crawler Management - Control bot access
7. Security Logs - Detailed event tracking
8. Email Notifications - Real-time alerts
== Changelog ==
= 5.0 =
* Added geographic blocking with real-time IP geolocation
* Implemented advanced rate limiting system
* Added email notifications for security events
* Improved logging with country tracking and pagination
* Added import/export functionality
* Optimized database performance with indexes
* Added emergency bypass URL feature
* Improved UI with WordPress native design
* Added security templates for quick setup
* Fixed array structure issue in templates
= 4.0 =
* Complete rewrite with improved architecture
* Added caching system
* Enhanced performance
* Better code organization
= 3.3 =
* Initial public release
* Basic IP blocking functionality
* Crawler blocking
* Simple logging
== Upgrade Notice ==
= 5.0 =
Major update with geographic blocking, rate limiting, and email notifications. Backup your settings before upgrading.
== Advanced Usage ==
= Custom Templates =
You can create custom security templates by hooking into the `wpban_templates` filter:
`
add_filter('wpban_templates', function($templates) {
$templates['custom'] = [
'name' => 'My Custom Template',
'description' => 'Custom security configuration',
'settings' => [
'banned_ips' => ['1.2.3.4'],
'rate_limits' => [
'requests_per_minute' => 45
]
]
];
return $templates;
});
`
= Custom Country Detection =
Integrate with premium GeoIP services:
`
add_filter('wpban_ip_country', function($country, $ip) {
// Your custom country detection logic
return $detected_country;
}, 10, 2);
`
= Whitelist Specific Pages =
Exclude certain pages from security checks:
`
add_filter('wpban_skip_checks', function($skip) {
if (is_page('special-page')) {
return true;
}
return $skip;
});
`
== Support ==
For support, feature requests, or bug reports, please visit our [support forum](https://wordpress.org/support/plugin/wpban/) or [GitHub repository](https://github.com/wpban/wpban).
== Privacy Policy ==
This plugin stores:
* Security logs containing IP addresses, user agents, and geographic data
* Your security configuration settings
This plugin may connect to external services:
* IP geolocation APIs (ip-api.com, ipinfo.io) for country detection
* These services only receive IP addresses for lookup
All data is stored locally in your WordPress database and is not shared with third parties.

View file

@ -1,51 +0,0 @@
<?php
/*
Plugin Name: WPBan-Anything
Plugin URI: https://wpban.com/
Description: Ban users by IP, IP Range, host name, user agent, referer URL, restrict login page, block WeChat/QQ browsers, AI crawlers, and SEO crawlers.
Version: 3.3
Author: WPBan
Author URI: https://wpban.com
Text Domain: wpban-anything
Requires at least: 6.7.2
*/
if (!defined('ABSPATH')) {
exit;
}
define('WPBAN_ANYTHING_VERSION', '3.3');
define('WPBAN_ANYTHING_DIR', plugin_dir_path(__FILE__));
define('WPBAN_ANYTHING_URL', plugin_dir_url(__FILE__));
require_once WPBAN_ANYTHING_DIR . 'includes/class-wpban-anything.php';
require_once WPBAN_ANYTHING_DIR . 'includes/class-wpban-ai-crawlers.php';
require_once WPBAN_ANYTHING_DIR . 'includes/class-wpban-seo-crawlers.php';
require_once WPBAN_ANYTHING_DIR . 'includes/functions.php';
function wpban_anything_init() {
load_plugin_textdomain('wpban-anything', false, dirname(plugin_basename(__FILE__)) . '/languages/');
$wpban_anything = new WPBan_Anything\WPBan_Anything();
$wpban_ai_crawlers = new WPBan_Anything\WPBan_AI_Crawlers();
$wpban_seo_crawlers = new WPBan_Anything\WPBan_SEO_Crawlers();
}
add_action('plugins_loaded', 'wpban_anything_init');
register_uninstall_hook(__FILE__, 'wpban_anything_uninstall');
function wpban_anything_uninstall() {
$options = [
'banned_ips', 'banned_hosts', 'banned_stats', 'banned_message',
'banned_referers', 'banned_exclude_ips', 'banned_ips_range',
'banned_user_agents', 'banned_options', 'login_restrictions',
'wechat_qq_settings', 'ai_crawler_settings', 'seo_crawler_settings'
];
if (is_multisite()) {
foreach (get_sites(['fields' => 'ids']) as $blog_id) {
switch_to_blog($blog_id);
array_walk($options, 'delete_option');
restore_current_blog();
}
} else {
array_walk($options, 'delete_option');
}
}

183
wpban.php Normal file
View file

@ -0,0 +1,183 @@
<?php
/*
Plugin Name: WPBan
Plugin URI: https://wpban.com/
Description: Advanced WordPress security with geo-blocking, rate limiting, and intelligent threat detection.
Version: 1.5.0
Author: WenPai.org
Author URI: https://wpban.com
Text Domain: wpban
Requires at least: 6.7.2
*/
if (!defined('ABSPATH')) {
exit;
}
define('WPBAN_VERSION', '1.5.0');
define('WPBAN_DIR', plugin_dir_path(__FILE__));
define('WPBAN_URL', plugin_dir_url(__FILE__));
define('WPBAN_BYPASS_KEY', 'wpban_bypass_' . substr(md5(AUTH_KEY), 0, 12));
// Create database tables on activation
register_activation_hook(__FILE__, 'wpban_activate');
function wpban_activate() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
// Create logs table with improved indexes
$table_logs = $wpdb->prefix . 'wpban_logs';
$sql_logs = "CREATE TABLE $table_logs (
id bigint(20) NOT NULL AUTO_INCREMENT,
ip varchar(45) NOT NULL,
country_code varchar(2) DEFAULT NULL,
user_agent text,
referer text,
uri text,
action varchar(50) NOT NULL,
reason text,
timestamp datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY ip_timestamp (ip, timestamp),
KEY action_timestamp (action, timestamp),
KEY country_timestamp (country_code, timestamp)
) $charset_collate;";
// Create rate limit table
$table_rate = $wpdb->prefix . 'wpban_rate_limits';
$sql_rate = "CREATE TABLE $table_rate (
ip varchar(45) NOT NULL,
action varchar(50) NOT NULL,
count int(11) DEFAULT 1,
window_start datetime NOT NULL,
last_attempt datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (ip, action),
KEY window_start (window_start)
) $charset_collate;";
// Create geo cache table
$table_geo = $wpdb->prefix . 'wpban_geo_cache';
$sql_geo = "CREATE TABLE $table_geo (
ip varchar(45) NOT NULL,
country_code varchar(2) NOT NULL,
country_name varchar(100),
city varchar(100),
cached_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (ip),
KEY cached_at (cached_at)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql_logs);
dbDelta($sql_rate);
dbDelta($sql_geo);
// Set default options
$default_settings = [
'rate_limits' => [
'requests_per_minute' => 60,
'login_per_hour' => 5,
'api_per_minute' => 30
],
'geo_blocking' => [
'enabled' => false,
'blocked_countries' => [],
'allowed_countries' => []
],
'email_notifications' => [
'enabled' => false,
'recipient' => get_option('admin_email'),
'threshold' => 10,
'events' => ['rate_limit', 'geo_block', 'brute_force']
]
];
$existing = get_option('wpban_settings', []);
update_option('wpban_settings', array_merge($default_settings, $existing));
// Generate bypass URL if not exists
if (!get_option('wpban_bypass_path')) {
update_option('wpban_bypass_path', wp_generate_password(16, false));
}
// Schedule cron jobs
if (!wp_next_scheduled('wpban_cleanup')) {
wp_schedule_event(time(), 'daily', 'wpban_cleanup');
}
if (!wp_next_scheduled('wpban_geo_update')) {
wp_schedule_event(time(), 'weekly', 'wpban_geo_update');
}
}
// Load required files
require_once WPBAN_DIR . 'includes/functions.php';
require_once WPBAN_DIR . 'includes/class-wpban-security.php';
require_once WPBAN_DIR . 'includes/class-wpban-admin.php';
// Initialize plugin
add_action('plugins_loaded', 'wpban_init');
function wpban_init() {
load_plugin_textdomain('wpban', false, dirname(plugin_basename(__FILE__)) . '/languages/');
// Initialize security module
$GLOBALS['wpban_security'] = new WPBan_Security();
if (is_admin()) {
new WPBan_Admin();
}
}
// Cron jobs
add_action('wpban_cleanup', 'wpban_cleanup_old_data');
function wpban_cleanup_old_data() {
global $wpdb;
// Clean old logs (keep 30 days)
$wpdb->query($wpdb->prepare(
"DELETE FROM {$wpdb->prefix}wpban_logs WHERE timestamp < %s",
date('Y-m-d H:i:s', strtotime('-30 days'))
));
// Clean old rate limit records
$wpdb->query($wpdb->prepare(
"DELETE FROM {$wpdb->prefix}wpban_rate_limits WHERE window_start < %s",
date('Y-m-d H:i:s', strtotime('-24 hours'))
));
// Clean old geo cache (keep 7 days)
$wpdb->query($wpdb->prepare(
"DELETE FROM {$wpdb->prefix}wpban_geo_cache WHERE cached_at < %s",
date('Y-m-d H:i:s', strtotime('-7 days'))
));
}
// Cleanup on uninstall
register_uninstall_hook(__FILE__, 'wpban_uninstall');
function wpban_uninstall() {
global $wpdb;
// Remove all options
$options = [
'wpban_settings', 'wpban_templates', 'wpban_bypass_path',
'wpban_cache_data', 'wpban_stats', 'wpban_geo_db_version'
];
foreach ($options as $option) {
delete_option($option);
}
// Remove database tables
$tables = ['wpban_logs', 'wpban_rate_limits', 'wpban_geo_cache'];
foreach ($tables as $table) {
$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}{$table}");
}
// Clear scheduled events
wp_clear_scheduled_hook('wpban_cleanup');
wp_clear_scheduled_hook('wpban_geo_update');
// Clear cache
wp_cache_delete_group('wpban');
}