diff --git a/assets/admin.css b/assets/admin.css new file mode 100644 index 0000000..67f9131 --- /dev/null +++ b/assets/admin.css @@ -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; +} \ No newline at end of file diff --git a/assets/admin.js b/assets/admin.js new file mode 100644 index 0000000..4c791e8 --- /dev/null +++ b/assets/admin.js @@ -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 = $(''); + $('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 = $('
') + .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); \ No newline at end of file diff --git a/includes/class-wpban-admin.php b/includes/class-wpban-admin.php new file mode 100644 index 0000000..e619996 --- /dev/null +++ b/includes/class-wpban-admin.php @@ -0,0 +1,994 @@ +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')); + ?> +
+

+ +
+ +
+
+

+
+
+ 0 ? round(($stats['today_blocks'] / $yesterday - 1) * 100) : 0; + ?> + + 0 ? '↑' : '↓'; ?> % + +
+
+ +
+

+
+
+ +
+

+
+
+ +
+

+
+
+
+ +
+ +
+

+
+ +
+ + +
+

+
+ + +
+ + +
+

+ + +
+
+ + +
+

+

+
+ security->get_templates() as $id => $template): ?> +
+

+

+ +
+ +
+
+ + +
+

+ + + + + + + + + + + + + + + + + + + + + +
timestamp)); ?> agoip); ?>country_code ?: '-'); ?> + action); ?>reason); ?>
+
+
+
+ +
+

+ +
+ + +
+ + + +
+ + + + + + + + + + + + + +
+ +
+ +
+ Access Denied

Your access has been restricted.

', + 'ban_message', + [ + 'textarea_name' => 'settings[ban_message]', + 'textarea_rows' => 10, + 'media_buttons' => false + ] + ); + ?> +

+
+
+ + +
+ + + + + + + + + + + + + + + + + +
+ +

+
+ +

+
+ +

+
+ +

+
+
+ + +
+ + + + + + + + + + + + + + + + + +
+ +

+
+ +
+ +
+
+ +
+
+
+ + +
+

+

+ + + + + + + + + + + + + + +
+ + +
+ + +
+ + +
+
+ + +
+

+ + + + + + + + + + + + + + +
+ +
+
+ +
+
+ +
+

+
+
+ + +
+
+ + + +
+ + + +

+
+ $info): ?> + + +
+ +

+
+

+
+
+ $info): ?> + + +
+
+ + +
+ + + + + + + + + + + + + + + + + +
+ +
+ + +
+ + +
+ __('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): ?> + + +
+
+
+ +

+ + +

+
+
+ 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); + ?> +
+

+ + +
+
+ + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
timestamp); ?> + ip); ?> +
+ + + +
+
country_code ?: '-'); ?> + + action); ?> + + reason); ?> + + user_agent, 0, 50)); ?> + + + + uri); ?> + +
+ + + 1): ?> +
+
+ add_query_arg('paged', '%#%'), + 'format' => '', + 'prev_text' => '«', + 'next_text' => '»', + 'total' => $result['pages'], + 'current' => $current_page + ]); + ?> +
+
+ +
+ +
+

+ +
+ +
+

+

+ +

+

+ +

+ +

+
+ + +

+ +

+

+ +

+
+
+ + +
+

+

+ + 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'"); + ?> + + + + + + + + + + +
MB
+ +

+ + +

+
+ + +
+

+ +
+
+
+ $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); + } +} \ No newline at end of file diff --git a/includes/class-wpban-cache.php b/includes/class-wpban-cache.php new file mode 100644 index 0000000..c5237ef --- /dev/null +++ b/includes/class-wpban-cache.php @@ -0,0 +1,82 @@ +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; + } +} \ No newline at end of file diff --git a/includes/class-wpban-core.php b/includes/class-wpban-core.php new file mode 100644 index 0000000..33ff211 --- /dev/null +++ b/includes/class-wpban-core.php @@ -0,0 +1,300 @@ +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' => '

Access Denied

Your access to this site has been restricted.

', + '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) { + ?> +
+
+

+

+ +
+
+ + 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 + )); + } +} \ No newline at end of file diff --git a/includes/class-wpban-security.php b/includes/class-wpban-security.php new file mode 100644 index 0000000..9f5b22a --- /dev/null +++ b/includes/class-wpban-security.php @@ -0,0 +1,719 @@ +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'] ?? '

Access Denied

'; + $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) { + ?> + + + + + + <?php echo esc_html($settings['title']); ?> + + + +
+

+

+ +
+ + + + 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 + ] + ] + ] + ]; + } +} \ No newline at end of file diff --git a/includes/class-wpban-templates.php b/includes/class-wpban-templates.php new file mode 100644 index 0000000..4c6b5e0 --- /dev/null +++ b/includes/class-wpban-templates.php @@ -0,0 +1,131 @@ +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' => '

Access Denied

Your access to this site has been restricted.

' + ] + ], + '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' => '

Site Under Development

This site is currently under development.

' + ] + ] + ]; + } + + 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' => '

Access Denied

Your access to this site has been restricted.

', + '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; + } +} \ No newline at end of file diff --git a/includes/functions.php b/includes/functions.php index eebd1ab..cf7dab8 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -1,44 +1,395 @@ 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); + + // Final validation + return filter_var($ip, FILTER_VALIDATE_IP) ? $ip : '0.0.0.0'; } -function preg_match_wildcard($pattern, $subject) { - $pattern = preg_quote($pattern, '#'); - $pattern = str_replace('\*', '.*', $pattern); - return preg_match("#^$pattern$#i", $subject); +/** + * 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); } -function ban_anything_ip_in_range($ip, $ranges) { - foreach ($ranges as $range) { - if (strpos($range, '/') !== false) { - 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) { +/** + * 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); + 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; +} \ No newline at end of file diff --git a/languages/wpban.pot b/languages/wpban.pot new file mode 100644 index 0000000..2fff37b --- /dev/null +++ b/languages/wpban.pot @@ -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 \n" +"Language-Team: LANGUAGE \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 "" \ No newline at end of file diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..e833a9a --- /dev/null +++ b/readme.txt @@ -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. \ No newline at end of file diff --git a/wpban-anything.php b/wpban-anything.php deleted file mode 100644 index 488670b..0000000 --- a/wpban-anything.php +++ /dev/null @@ -1,51 +0,0 @@ - 'ids']) as $blog_id) { - switch_to_blog($blog_id); - array_walk($options, 'delete_option'); - restore_current_blog(); - } - } else { - array_walk($options, 'delete_option'); - } -} diff --git a/wpban.php b/wpban.php new file mode 100644 index 0000000..7be2009 --- /dev/null +++ b/wpban.php @@ -0,0 +1,183 @@ +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'); +}