Changeset 3462265
- Timestamp:
- 02/16/2026 07:47:36 AM (6 weeks ago)
- Location:
- lockspire-smart-access-protection/trunk
- Files:
-
- 2 added
- 9 edited
-
assets/css/admin.css (modified) (2 diffs)
-
assets/js/admin.js (modified) (1 diff)
-
includes/ajax-handlers.php (modified) (1 diff)
-
includes/attempt-handler.php (modified) (1 diff)
-
includes/database.php (modified) (1 diff)
-
includes/notifications.php (modified) (1 diff)
-
includes/settings-page.php (modified) (1 diff)
-
lockspire-smart-access-protection.php (modified) (1 diff)
-
phpstan.neon (added)
-
readme.txt (modified) (1 diff)
-
wp-stubs.php (added)
Legend:
- Unmodified
- Added
- Removed
-
lockspire-smart-access-protection/trunk/assets/css/admin.css
r3455145 r3462265 1 /* Login Attempt Limiter Admin Styles */ 2 .lal-admin-wrapper { 3 background: #fff; 4 border: 1px solid #ccd0d4; 5 border-radius: 8px; 6 box-shadow: 0 1px 3px rgba(0,0,0,0.1); 7 margin: 20px 0; 8 overflow: hidden; 9 } 10 11 .lal-header { 1 /* Lockspire Security Admin Styles */ 2 .lockspire-wrap { 3 max-width: 1600px; 4 margin: 0; 5 padding: 0 30px 30px 30px; 6 background: #f1f3f6; 7 min-height: 100vh; 8 } 9 10 .lockspire-header { 12 11 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 13 12 color: white; 14 padding: 20px; 15 text-align: center; 16 } 17 18 .lal-header h1 { 13 padding: 30px 40px; 14 display: flex; 15 justify-content: space-between; 16 align-items: center; 17 margin: -30px -30px 40px -30px; 18 border-radius: 0 0 20px 20px; 19 box-shadow: 0 8px 30px rgba(102, 126, 234, 0.3); 20 } 21 22 .lockspire-logo { 23 display: flex; 24 align-items: center; 25 gap: 15px; 26 } 27 28 .lockspire-icon { 29 font-size: 32px; 30 } 31 32 .lockspire-logo h1 { 19 33 margin: 0; 20 34 font-size: 24px; … … 22 36 } 23 37 24 .lal-header p { 25 margin: 5px 0 0 0; 26 opacity: 0.9; 38 .lockspire-version { 39 background: rgba(255, 255, 255, 0.2); 40 padding: 5px 12px; 41 border-radius: 15px; 42 font-size: 12px; 43 } 44 45 .lockspire-nav { 46 display: flex; 47 gap: 8px; 48 margin-bottom: 30px; 49 background: white; 50 padding: 15px 25px; 51 border-radius: 15px; 52 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); 53 flex-wrap: wrap; 54 } 55 56 .nav-item { 57 padding: 12px 20px; 58 text-decoration: none; 59 color: #666; 60 border-radius: 10px; 61 transition: all 0.3s ease; 62 font-weight: 500; 27 63 font-size: 14px; 28 64 } 29 65 30 .lal-nav-tabs { 31 display: flex; 32 background: #f8f9fa; 33 border-bottom: 1px solid #ddd; 34 margin: 0; 35 padding: 0; 36 } 37 38 .lal-nav-tab { 39 background: none; 40 border: none; 41 padding: 15px 25px; 42 cursor: pointer; 43 font-size: 14px; 44 font-weight: 500; 45 color: #666; 46 border-bottom: 3px solid transparent; 47 transition: all 0.3s ease; 48 } 49 50 .lal-nav-tab:hover { 51 background: #e9ecef; 66 .nav-item:hover { 67 background: #f0f0f0; 52 68 color: #333; 53 69 } 54 70 55 .lal-nav-tab.active { 56 color: #667eea; 57 border-bottom-color: #667eea; 58 background: white; 59 } 60 61 .lal-tab-content { 62 display: none; 63 padding: 30px; 64 } 65 66 .lal-tab-content.active { 67 display: block; 68 } 69 70 .lal-form-section { 71 margin-bottom: 30px; 72 padding: 20px; 73 background: #f8f9fa; 74 border-radius: 6px; 75 border-left: 4px solid #667eea; 76 } 77 78 .lal-form-section h3 { 79 margin: 0 0 15px 0; 80 color: #333; 81 font-size: 16px; 82 font-weight: 600; 83 } 84 85 .lal-form-row { 86 display: flex; 87 align-items: center; 88 margin-bottom: 15px; 89 flex-wrap: wrap; 90 } 91 92 .lal-form-row label { 93 flex: 0 0 200px; 94 font-weight: 500; 95 color: #333; 96 margin-right: 20px; 97 } 98 99 .lal-form-row input[type="text"], 100 .lal-form-row input[type="number"], 101 .lal-form-row textarea, 102 .lal-form-row select { 103 flex: 1; 104 min-width: 200px; 105 padding: 8px 12px; 106 border: 1px solid #ddd; 107 border-radius: 4px; 108 font-size: 14px; 109 } 110 111 .lal-form-row input[type="checkbox"] { 112 margin-right: 8px; 113 } 114 115 .lal-form-row .description { 116 flex: 1; 117 margin-top: 5px; 118 font-size: 12px; 119 color: #666; 120 font-style: italic; 121 } 122 123 .lal-stats-grid { 124 display: grid; 125 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); 126 gap: 20px; 127 margin-bottom: 30px; 128 } 129 130 .lal-stat-card { 131 background: white; 132 border: 1px solid #e1e5e9; 133 border-radius: 8px; 134 padding: 20px; 135 text-align: center; 136 transition: transform 0.2s ease, box-shadow 0.2s ease; 137 } 138 139 .lal-stat-card:hover { 140 transform: translateY(-2px); 141 box-shadow: 0 4px 12px rgba(0,0,0,0.1); 142 } 143 144 .lal-stat-number { 145 font-size: 32px; 146 font-weight: bold; 147 color: #667eea; 148 margin-bottom: 5px; 149 } 150 151 .lal-stat-label { 152 font-size: 14px; 153 color: #666; 154 text-transform: uppercase; 155 letter-spacing: 0.5px; 156 } 157 158 .lal-log-table { 159 width: 100%; 160 border-collapse: collapse; 161 margin-top: 20px; 162 } 163 164 .lal-log-table th, 165 .lal-log-table td { 166 padding: 12px; 167 text-align: left; 168 border-bottom: 1px solid #e1e5e9; 169 } 170 171 .lal-log-table th { 172 background: #f8f9fa; 173 font-weight: 600; 174 color: #333; 175 } 176 177 .lal-log-table tr:hover { 178 background: #f8f9fa; 179 } 180 181 .lal-status-badge { 182 padding: 4px 8px; 183 border-radius: 12px; 184 font-size: 12px; 185 font-weight: 500; 186 text-transform: uppercase; 187 } 188 189 .lal-status-locked { 190 background: #ffeaa7; 191 color: #d63031; 192 } 193 194 .lal-status-failed { 195 background: #fab1a0; 196 color: #e17055; 197 } 198 199 .lal-status-success { 200 background: #55efc4; 201 color: #00b894; 202 } 203 204 .lal-button { 71 .nav-item.active { 205 72 background: #667eea; 206 73 color: white; 207 border: none; 208 padding: 10px 20px; 209 border-radius: 4px; 74 } 75 76 .lockspire-content { 77 background: transparent; 78 } 79 80 .page-title { 81 margin-bottom: 20px; 82 color: #333; 83 font-size: 28px; 84 font-weight: 600; 85 } 86 87 .lockspire-grid { 88 display: grid; 89 grid-template-columns: 1fr 1fr; 90 gap: 30px; 91 margin-bottom: 30px; 92 } 93 94 @media (max-width: 1200px) { 95 .lockspire-grid { 96 grid-template-columns: 1fr; 97 } 98 } 99 100 .lockspire-card { 101 background: white; 102 border-radius: 16px; 103 box-shadow: 0 6px 25px rgba(0, 0, 0, 0.08); 104 overflow: hidden; 105 transition: transform 0.3s ease, box-shadow 0.3s ease; 106 border: 1px solid rgba(0, 0, 0, 0.05); 107 } 108 109 .lockspire-card:hover { 110 transform: translateY(-2px); 111 box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); 112 } 113 114 .lockspire-card-primary { 115 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 116 color: white; 117 } 118 119 .lockspire-card-header { 120 padding: 25px 30px; 121 border-bottom: 1px solid rgba(0, 0, 0, 0.08); 122 display: flex; 123 justify-content: space-between; 124 align-items: center; 125 background: rgba(248, 250, 252, 0.8); 126 } 127 128 .lockspire-card-primary .lockspire-card-header { 129 border-bottom: 1px solid rgba(255, 255, 255, 0.2); 130 } 131 132 .lockspire-card-header h3 { 133 margin: 0; 134 font-size: 18px; 135 font-weight: 600; 136 } 137 138 .lockspire-card-body { 139 padding: 30px; 140 } 141 142 /* Toggle switches */ 143 .switch { 144 position: relative; 145 display: inline-block; 146 width: 60px; 147 height: 34px; 148 } 149 150 .switch input { 151 opacity: 0; 152 width: 0; 153 height: 0; 154 } 155 156 .toggle-slider { 157 position: absolute; 210 158 cursor: pointer; 211 font-size: 14px; 212 font-weight: 500; 213 transition: background 0.3s ease; 214 } 215 216 .lal-button:hover { 217 background: #5a6fd8; 218 } 219 220 .lal-button-secondary { 221 background: #6c757d; 222 } 223 224 .lal-button-secondary:hover { 225 background: #5a6268; 226 } 227 228 .lal-button-danger { 229 background: #dc3545; 230 } 231 232 .lal-button-danger:hover { 233 background: #c82333; 234 } 235 236 .lal-alert { 237 padding: 15px; 238 border-radius: 4px; 239 margin-bottom: 20px; 240 border-left: 4px solid; 241 } 242 243 .lal-alert-success { 244 background: #d4edda; 245 border-color: #28a745; 246 color: #155724; 247 } 248 249 .lal-alert-warning { 250 background: #fff3cd; 251 border-color: #ffc107; 252 color: #856404; 253 } 254 255 .lal-alert-error { 256 background: #f8d7da; 257 border-color: #dc3545; 258 color: #721c24; 259 } 260 261 .lal-progress-bar { 262 width: 100%; 263 height: 20px; 264 background: #e9ecef; 265 border-radius: 10px; 159 top: 0; 160 left: 0; 161 right: 0; 162 bottom: 0; 163 background-color: #ccc; 164 transition: 0.4s; 165 border-radius: 34px; 166 } 167 168 .toggle-slider:before { 169 position: absolute; 170 content: ""; 171 height: 26px; 172 width: 26px; 173 left: 4px; 174 bottom: 4px; 175 background-color: white; 176 transition: 0.4s; 177 border-radius: 50%; 178 } 179 180 input:checked + .toggle-slider { 181 background-color: #667eea; 182 } 183 184 input:checked + .toggle-slider:before { 185 transform: translateX(26px); 186 } 187 188 /* Status indicators */ 189 .status-locked { 190 display: inline-block; 191 padding: 6px 12px; 192 background: #ffebee; 193 color: #c62828; 194 border-radius: 6px; 195 font-weight: 600; 196 font-size: 12px; 197 } 198 199 .status-active { 200 display: inline-block; 201 padding: 6px 12px; 202 background: #e8f5e9; 203 color: #2e7d32; 204 border-radius: 6px; 205 font-weight: 600; 206 font-size: 12px; 207 } 208 209 /* Modern Dashboard Grid */ 210 .dashboard-grid-modern { 211 display: grid; 212 grid-template-columns: 1fr; 213 gap: 30px; 214 margin-bottom: 35px; 215 } 216 217 .modern-card { 218 background: rgba(255, 255, 255, 0.95); 219 border-radius: 14px; 220 box-shadow: 0 8px 25px rgba(0, 0, 0, 0.07); 221 border: 1px solid rgba(0, 0, 0, 0.05); 222 transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); 223 position: relative; 266 224 overflow: hidden; 267 margin: 10px 0; 268 } 269 270 .lal-progress-fill { 271 height: 100%; 272 background: linear-gradient(90deg, #667eea, #764ba2); 273 transition: width 0.3s ease; 274 } 275 276 @media (max-width: 768px) { 277 .lal-form-row { 278 flex-direction: column; 279 align-items: flex-start; 280 } 281 282 .lal-form-row label { 283 flex: none; 284 margin-bottom: 5px; 285 } 286 287 .lal-nav-tabs { 288 flex-wrap: wrap; 289 } 290 291 .lal-nav-tab { 292 flex: 1; 293 min-width: 120px; 294 text-align: center; 295 } 296 } 225 padding: 25px; 226 } 227 228 .modern-card:hover { 229 transform: translateY(-3px); 230 box-shadow: 0 12px 35px rgba(0, 0, 0, 0.11); 231 border-color: rgba(0, 0, 0, 0.09); 232 } -
lockspire-smart-access-protection/trunk/assets/js/admin.js
r3455145 r3462265 1 jQuery(document).ready(function($) { 2 // Tab switching functionality 3 $('.lal-nav-tab').on('click', function() { 4 var targetTab = $(this).data('tab'); 5 if (targetTab) { 6 $('.lal-nav-tab').removeClass('active'); 7 $('.lal-tab-content').removeClass('active'); 8 9 $(this).addClass('active'); 10 $('#' + targetTab).addClass('active'); 11 } 1 // Lockspire Admin JavaScript Functions 2 3 // Dashboard functions 4 function runSecurityScan() { 5 window.location.href = '?page=lockspire-scanner&scan=1'; 6 } 7 8 function clearAllLogs() { 9 if (confirm('Are you sure you want to clear all activity logs?')) { 10 window.location.href = '?page=lockspire-activity&action=clear_logs'; 11 } 12 } 13 14 function exportSecurityReport() { 15 window.location.href = '?page=lockspire-activity&action=export'; 16 } 17 18 function refreshDashboard() { 19 location.reload(); 20 } 21 22 // Login Protection Functions 23 function toggleLoginProtection(checkbox) { 24 jQuery.post(ajaxurl, { 25 action: 'lockspire_toggle_login_protection', 26 enabled: checkbox.checked, 27 nonce: lockspire_nonce 12 28 }); 13 14 // Auto-refresh dashboard stats 15 if ($('.lal-stats-grid').length > 0) { 16 setInterval(function() { 17 lockspireRefreshStats(); 18 }, 30000); // Refresh every 30 seconds 19 } 20 21 // Initialize tooltips 22 lockspireInitTooltips(); 23 }); 29 } 24 30 25 // Global functions for admin actions 26 function lockspireClearAllLockouts() { 27 if (confirm('Are you sure you want to clear all active lockouts? This will allow locked out users to try logging in again.')) { 31 function saveLoginSettings() { 32 jQuery.post(ajaxurl, { 33 action: 'lockspire_save_login_settings', 34 max_attempts: jQuery('#max_attempts').val(), 35 lockout_time: jQuery('#lockout_time').val(), 36 nonce: lockspire_nonce 37 }, function() { 38 alert('Settings saved successfully!'); 39 location.reload(); 40 }); 41 } 42 43 function unlockIP(ip) { 44 if (confirm('Unlock IP ' + ip + '?')) { 28 45 jQuery.post(ajaxurl, { 29 action: 'lockspire_clear_lockouts', 30 nonce: lockspire_admin.nonce 31 }, function(response) { 32 if (response.success) { 33 lockspireShowAlert('All lockouts cleared successfully!', 'success'); 34 lockspireRefreshStats(); 35 } else { 36 lockspireShowAlert('Error clearing lockouts: ' + response.data, 'error'); 37 } 46 action: 'lockspire_unlock_ip', 47 ip: ip, 48 nonce: lockspire_nonce 49 }, function() { 50 location.reload(); 38 51 }); 39 52 } 40 53 } 41 54 42 function lockspireExportLogs() { 43 window.location.href = ajaxurl + '?action=lockspire_export_logs&nonce=' + lockspire_admin.nonce; 44 } 45 46 function lockspireResetStats() { 47 if (confirm('Are you sure you want to reset all statistics? This action cannot be undone.')) { 55 function unlockAllIPs() { 56 if (confirm('Unlock all locked IPs?')) { 48 57 jQuery.post(ajaxurl, { 49 action: 'lockspire_reset_stats', 50 nonce: lockspire_admin.nonce 51 }, function(response) { 52 if (response.success) { 53 lockspireShowAlert('Statistics reset successfully!', 'success'); 54 lockspireRefreshStats(); 55 } else { 56 lockspireShowAlert('Error resetting statistics: ' + response.data, 'error'); 57 } 58 action: 'lockspire_unlock_all_ips', 59 nonce: lockspire_nonce 60 }, function() { 61 location.reload(); 58 62 }); 59 63 } 60 64 } 61 65 62 function lockspireClearLogs() { 63 console.log('lockspireClearLogs called'); 64 console.log('lockspire_admin object:', lockspire_admin); 65 66 if (!lockspire_admin || !lockspire_admin.nonce) { 67 alert('Security nonce not available. Please refresh the page.'); 68 return; 69 } 70 71 if (confirm('Are you sure you want to clear all logs? This action cannot be undone.')) { 72 console.log('Sending clear logs AJAX request...'); 66 // Email Notification Functions 67 function toggleEmailNotifications(checkbox) { 68 jQuery.post(ajaxurl, { 69 action: 'lockspire_toggle_email_notifications', 70 enabled: checkbox.checked, 71 nonce: lockspire_nonce 72 }); 73 } 74 75 function saveEmailSettings() { 76 var emails = jQuery('#notification_emails').val(); 77 jQuery.post(ajaxurl, { 78 action: 'lockspire_save_email_settings', 79 notification_emails: emails, 80 nonce: lockspire_nonce 81 }, function() { 82 alert('Email settings saved!'); 83 }); 84 } 85 86 function testEmail() { 87 jQuery.post(ajaxurl, { 88 action: 'lockspire_test_email', 89 nonce: lockspire_nonce 90 }, function() { 91 alert('Test email sent! Check your inbox.'); 92 }); 93 } 94 95 function clearEmailLogs() { 96 if (confirm('Clear all email logs?')) { 73 97 jQuery.post(ajaxurl, { 74 action: 'lockspire_clear_logs', 75 nonce: lockspire_admin.nonce 76 }, function(response) { 77 console.log('Clear logs AJAX response:', response); 78 if (response.success) { 79 lockspireShowAlert('Logs cleared successfully!', 'success'); 80 // Wait a moment before reloading to show the success message 81 setTimeout(function() { 82 location.reload(); 83 }, 1000); 84 } else { 85 lockspireShowAlert('Error clearing logs: ' + (response.data || 'Unknown error'), 'error'); 86 } 87 }).fail(function(xhr, status, error) { 88 console.log('Clear logs AJAX failed:', xhr, status, error); 89 lockspireShowAlert('AJAX error: ' + error, 'error'); 98 action: 'lockspire_clear_email_logs', 99 nonce: lockspire_nonce 100 }, function() { 101 location.reload(); 90 102 }); 91 103 } 92 104 } 93 105 94 function lockspireDownloadLogs() { 95 window.location.href = ajaxurl + '?action=lockspire_download_logs&nonce=' + lockspire_admin.nonce; 106 // Activity Log Functions 107 function clearLogs() { 108 if (confirm('Are you sure you want to clear all activity logs?')) { 109 window.location.href = '?page=lockspire-activity&action=clear_logs'; 110 } 96 111 } 97 112 98 function lockspireRefreshStats() { 99 jQuery.post(ajaxurl, { 100 action: 'lockspire_get_stats', 101 nonce: lockspire_admin.nonce 102 }, function(response) { 103 if (response.success) { 104 var stats = response.data; 105 jQuery('.lal-stat-number').eq(0).text(stats.total_attempts.toLocaleString()); 106 jQuery('.lal-stat-number').eq(1).text(stats.failed_attempts.toLocaleString()); 107 jQuery('.lal-stat-number').eq(2).text(stats.locked_ips.toLocaleString()); 108 jQuery('.lal-stat-number').eq(3).text(stats.blocked_ips.toLocaleString()); 109 } 113 function exportLogs() { 114 window.location.href = '?page=lockspire-activity&action=export'; 115 } 116 117 function filterLogs() { 118 var user = jQuery('#filter_user').val(); 119 var role = jQuery('#filter_role').val(); 120 var date = jQuery('#filter_date').val(); 121 122 jQuery('.activity-item').each(function() { 123 var show = true; 124 125 if (user && jQuery(this).data('user') !== user) show = false; 126 if (role && jQuery(this).data('role') !== role) show = false; 127 if (date && jQuery(this).data('date') !== date) show = false; 128 129 jQuery(this).toggle(show); 110 130 }); 111 131 } 112 132 113 function lockspireShowAlert(message, type) { 114 var alertClass = 'lal-alert-' + type; 115 var alertHtml = '<div class="lal-alert ' + alertClass + '">' + message + '</div>'; 116 jQuery('.wrap').prepend(alertHtml); 117 118 setTimeout(function() { 119 jQuery('.lal-alert').first().fadeOut(500, function() { 120 jQuery(this).remove(); 121 }); 122 }, 5000); 123 } 124 125 function lockspireInitTooltips() { 126 jQuery('[title]').each(function() { 127 var $this = jQuery(this); 128 $this.on('mouseenter', function() { 129 var title = $this.attr('title'); 130 $this.data('title', title).removeAttr('title'); 131 132 var tooltip = jQuery('<div class="lal-tooltip">' + title + '</div>'); 133 jQuery('body').append(tooltip); 134 135 var position = $this.offset(); 136 tooltip.css({ 137 position: 'absolute', 138 top: position.top - tooltip.outerHeight() - 5, 139 left: position.left + ($this.outerWidth() / 2) - (tooltip.outerWidth() / 2) 140 }).fadeIn(200); 141 }).on('mouseleave', function() { 142 var title = $this.data('title'); 143 $this.attr('title', title); 144 jQuery('.lal-tooltip').remove(); 145 }); 146 }); 147 } 148 149 function lockspireTestLogWorking() { 150 if (confirm('Create test log entries to verify logging is working?')) { 133 // Firewall Functions 134 function blockIP(ip) { 135 var reason = prompt('Enter reason for blocking:'); 136 if (reason) { 151 137 jQuery.post(ajaxurl, { 152 action: 'lockspire_test_log_working', 153 nonce: lockspire_admin.nonce 154 }, function(response) { 155 if (response.success) { 156 alert('Success: ' + response.data); 157 location.reload(); 158 } else { 159 alert('Error: ' + response.data); 160 } 161 }).fail(function(xhr, status, error) { 162 alert('AJAX Error: ' + error); 138 action: 'lockspire_block_ip', 139 ip: ip, 140 reason: reason, 141 nonce: lockspire_nonce 142 }, function() { 143 alert('IP blocked successfully!'); 144 location.reload(); 163 145 }); 164 146 } 165 147 } 166 148 167 // Add CSS for tooltips 168 jQuery('<style>.lal-tooltip { background: #333; color: white; padding: 5px 10px; border-radius: 3px; font-size: 12px; z-index: 9999; }</style>').appendTo('head'); 149 function unblockIP(ip) { 150 if (confirm('Unblock IP ' + ip + '?')) { 151 jQuery.post(ajaxurl, { 152 action: 'lockspire_unblock_ip', 153 ip: ip, 154 nonce: lockspire_nonce 155 }, function() { 156 alert('IP unblocked successfully!'); 157 location.reload(); 158 }); 159 } 160 } 161 162 function toggleRule(ruleId, checkbox) { 163 jQuery.post(ajaxurl, { 164 action: 'lockspire_toggle_rule', 165 rule_id: ruleId, 166 enabled: checkbox.checked, 167 nonce: lockspire_nonce 168 }, function() { 169 console.log('Rule updated'); 170 }); 171 } -
lockspire-smart-access-protection/trunk/includes/ajax-handlers.php
r3455145 r3462265 1 1 <?php 2 if ( ! defined( 'ABSPATH' ) ) exit; 2 /** 3 * AJAX Handlers for Lockspire - Documentation File 4 * 5 * This file documents the AJAX handlers that are implemented 6 * as methods of the Lockspire_Security class. 7 * 8 * The actual implementation can be found in lockspire-security.php 9 */ 10 // All code is implemented in lockspire-security.php - this is documentation only 3 11 4 // Register AJAX handlers5 add_action('wp_ajax_lockspire_get_stats', 'lockspire_ajax_get_stats');6 add_action('wp_ajax_lockspire_clear_lockouts', 'lockspire_ajax_clear_lockouts');7 add_action('wp_ajax_lockspire_reset_stats', 'lockspire_ajax_reset_stats');8 add_action('wp_ajax_lockspire_clear_logs', 'lockspire_ajax_clear_logs');9 add_action('wp_ajax_lockspire_export_logs', 'lockspire_ajax_export_logs');10 add_action('wp_ajax_lockspire_download_logs', 'lockspire_ajax_download_logs');11 12 function lockspire_ajax_get_stats() {13 check_ajax_referer('lockspire_admin_actions', 'nonce');14 15 if (!current_user_can('manage_options')) {16 wp_die('Unauthorized');17 }18 19 $stats = lockspire_get_security_stats();20 wp_send_json_success($stats);21 }22 23 function lockspire_ajax_clear_lockouts() {24 check_ajax_referer('lockspire_admin_actions', 'nonce');25 26 if (!current_user_can('manage_options')) {27 wp_die('Unauthorized');28 }29 30 global $wpdb;31 32 // Get all lockout transients using WordPress options API33 $all_options = wp_cache_get('alloptions', 'options');34 if (!$all_options) {35 $all_options = wp_load_alloptions();36 }37 38 $transients = [];39 foreach ($all_options as $option_name => $value) {40 if (strpos($option_name, 'lockspire_lockout_') === 0) {41 $transients[] = $option_name;42 }43 }44 45 foreach ($transients as $transient) {46 delete_option($transient);47 wp_cache_delete($transient, 'options');48 }49 50 wp_send_json_success('All lockouts cleared');51 }52 53 function lockspire_ajax_reset_stats() {54 check_ajax_referer('lockspire_admin_actions', 'nonce');55 56 if (!current_user_can('manage_options')) {57 wp_die('Unauthorized');58 }59 60 global $wpdb;61 $table_name = sanitize_text_field($wpdb->prefix . 'lockspire_login_logs');62 $wpdb->query($wpdb->prepare("TRUNCATE TABLE %s", $table_name));63 64 wp_send_json_success('Statistics reset');65 }66 67 function lockspire_ajax_clear_logs() {68 check_ajax_referer('lockspire_admin_actions', 'nonce');69 70 if (!current_user_can('manage_options')) {71 wp_die('Unauthorized');72 }73 74 global $wpdb;75 $table_name = sanitize_text_field($wpdb->prefix . 'lockspire_login_logs');76 77 // Ensure table exists before clearing78 require_once(ABSPATH . 'wp-admin/includes/upgrade.php');79 dbDelta("CREATE TABLE IF NOT EXISTS {$table_name} (80 id mediumint(9) NOT NULL AUTO_INCREMENT,81 ip_address varchar(45) NOT NULL,82 username varchar(60) NOT NULL,83 status varchar(20) NOT NULL,84 attempts int(3) NOT NULL DEFAULT 1,85 user_agent text,86 created_at datetime DEFAULT CURRENT_TIMESTAMP,87 PRIMARY KEY (id)88 )");89 90 // Clear the table using safe table name construction91 $wpdb->query("TRUNCATE TABLE `" . esc_sql($table_name) . "`");92 93 // Clear all related caches94 wp_cache_delete('lockspire_stats_today', 'lockspire_logs');95 wp_cache_delete('lockspire_recent_logs_50', 'lockspire_logs');96 wp_cache_delete('lockspire_cleanup_last_run', 'lockspire_logs');97 98 wp_send_json_success('Logs cleared');99 }100 101 function lockspire_ajax_export_logs() {102 check_ajax_referer('lockspire_admin_actions', 'nonce');103 104 if (!current_user_can('manage_options')) {105 wp_die('Unauthorized');106 }107 108 global $wpdb;109 $table_name = sanitize_text_field($wpdb->prefix . 'lockspire_login_logs');110 111 // Try to get logs from cache first112 $cache_key = 'lockspire_recent_logs_50';113 $logs = wp_cache_get($cache_key, 'lockspire_logs');114 115 if ($logs === false) {116 // Note: $wpdb->get_results() is the proper WordPress method for multiple row queries117 // Direct database query is necessary here for CSV export - no WordPress core function provides this data118 // Query is properly prepared and cached to minimize database load119 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery120 $logs = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$wpdb->prefix}lockspire_login_logs ORDER BY created_at DESC LIMIT %d", 50), ARRAY_A);121 122 // Cache for 5 minutes to reduce database load123 wp_cache_set($cache_key, $logs, 'lockspire_logs', 300);124 }125 126 $filename = 'login-logs-' . gmdate('Y-m-d') . '.csv';127 header('Content-Type: text/csv');128 header('Content-Disposition: attachment; filename="' . $filename . '"');129 130 $output = fopen('php://output', 'w');131 132 // CSV headers133 fputcsv($output, ['Time', 'IP Address', 'Username', 'Status', 'Attempts']);134 135 // CSV data136 foreach ($logs as $log) {137 fputcsv($output, [138 $log['created_at'],139 $log['ip_address'],140 $log['username'],141 $log['status'],142 $log['attempts']143 ]);144 }145 146 // Use WP_Filesystem instead of direct file operations147 if (function_exists('WP_Filesystem')) {148 WP_Filesystem();149 global $wp_filesystem;150 // For CSV export, we need to handle the output stream properly151 // Since we're dealing with php://output, we'll let PHP handle the stream closing152 // The exit() call will properly close the stream153 }154 exit;155 }156 157 function lockspire_ajax_download_logs() {158 check_ajax_referer('lockspire_admin_actions', 'nonce');159 160 if (!current_user_can('manage_options')) {161 wp_die('Unauthorized');162 }163 164 // Same as export but with different filename165 lockspire_ajax_export_logs();166 } -
lockspire-smart-access-protection/trunk/includes/attempt-handler.php
r3455145 r3462265 1 1 <?php 2 if ( ! defined( 'ABSPATH' ) ) exit; 2 /** 3 * Login Attempt Handler for Lockspire - Documentation File 4 * 5 * Methods implemented in Lockspire_Security class: 6 * - public function handle_login_failed($username) 7 * - public function custom_login_error_message($error) 8 * - public function check_login_attempts($username) 9 * 10 * Actual code is in lockspire-security.php 11 */ 3 12 4 // Hook into login process 5 add_action('wp_login_failed', 'lockspire_track_failed_login'); 6 add_filter('authenticate', 'lockspire_check_login_limit', 30, 3); 7 add_action('login_head', 'lockspire_add_timer_script'); 8 add_action('wp_login', 'lockspire_track_successful_login', 10, 2); 9 add_filter('login_errors', 'lockspire_show_remaining_attempts'); 10 11 function lockspire_track_failed_login($username) { 12 // Validate and sanitize IP address 13 if (!isset($_SERVER['REMOTE_ADDR']) || empty($_SERVER['REMOTE_ADDR'])) { 14 return; 15 } 16 $ip = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])); 17 18 // Check if IP is blacklisted 19 if (lockspire_is_ip_blacklisted($ip)) { 20 lockspire_log_login_attempt($ip, $username, 'blocked', 1); 21 wp_die('Access denied. Your IP address has been blocked.'); 22 } 23 24 // Check country blocking 25 $country = lockspire_get_ip_country($ip); 26 if ($country && lockspire_is_country_blocked($country)) { 27 lockspire_log_login_attempt($ip, $username, 'country_blocked', 1); 28 wp_die('Access denied. Login attempts from your country are not allowed.'); 29 } 30 31 $attempts = get_transient('lockspire_' . $ip) ?: 0; 32 $attempts++; 33 34 $lockout_time = 60 * get_option('lockspire_lockout_time', 15); 35 set_transient('lockspire_' . $ip, $attempts, $lockout_time); 36 37 // Log the failed attempt 38 lockspire_log_login_attempt($ip, $username, 'failed', $attempts); 39 40 // Set lockout if max attempts reached 41 if ($attempts >= get_option('lockspire_max_attempts', 5)) { 42 $lockout_until = time() + $lockout_time; 43 set_transient('lockspire_lockout_' . $ip, $lockout_until, $lockout_time); 44 45 // Log the lockout 46 lockspire_log_login_attempt($ip, $username, 'locked', $attempts); 47 48 // Trigger notification hook 49 do_action('lockspire_user_locked_out', $ip, $username, $attempts); 50 } 51 } 52 53 function lockspire_track_successful_login($user_login, $user) { 54 // Validate and sanitize IP address 55 if (!isset($_SERVER['REMOTE_ADDR']) || empty($_SERVER['REMOTE_ADDR'])) { 56 return; 57 } 58 $ip = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])); 59 60 // Clear any failed attempts for this IP 61 delete_transient('lockspire_' . $ip); 62 delete_transient('lockspire_lockout_' . $ip); 63 64 // Log successful login 65 lockspire_log_login_attempt($ip, $user_login, 'success', 1); 66 } 67 68 function lockspire_check_login_limit($user, $username, $password) { 69 // Validate and sanitize IP address 70 if (!isset($_SERVER['REMOTE_ADDR']) || empty($_SERVER['REMOTE_ADDR'])) { 71 return $user; 72 } 73 $ip = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])); 74 75 // Check if IP is whitelisted 76 if (lockspire_is_ip_whitelisted($ip)) { 77 return $user; 78 } 79 80 // Check if IP is blacklisted 81 if (lockspire_is_ip_blacklisted($ip)) { 82 return new WP_Error( 83 'ip_blocked', 84 __('Your IP address has been blocked from accessing this site.', 'lockspire-smart-access-protection') 85 ); 86 } 87 88 // Check country blocking 89 $country = lockspire_get_ip_country($ip); 90 if ($country && lockspire_is_country_blocked($country)) { 91 return new WP_Error( 92 'country_blocked', 93 __('Login attempts from your country are not allowed.', 'lockspire-smart-access-protection') 94 ); 95 } 96 97 $attempts = get_transient('lockspire_' . $ip); 98 $max_attempts = get_option('lockspire_max_attempts', 5); 99 100 if ($attempts && $attempts >= $max_attempts) { 101 $lockout_until = get_transient('lockspire_lockout_' . $ip); 102 $time_left = $lockout_until - time(); 103 104 if ($time_left > 0) { 105 // Format time left for display 106 $minutes = floor($time_left / 60); 107 $seconds = $time_left % 60; 108 $time_display = sprintf('%02d:%02d', $minutes, $seconds); 109 110 // Get custom message or use default 111 $message = get_option('lockspire_lockout_message', __('Too many login attempts. Please try again later.', 'lockspire-smart-access-protection')); 112 113 // Replace [timer] placeholder with actual timer 114 if (strpos($message, '[timer]') !== false) { 115 $message = str_replace('[timer]', '<span id="lockspire-timer">' . $time_display . '</span>', $message); 116 } else { 117 $message .= ' ' . __('You can try again in', 'lockspire-smart-access-protection') . ' <span id="lockspire-timer">' . $time_display . '</span>.'; 118 } 119 120 return new WP_Error( 121 'too_many_attempts', 122 __('Error: ', 'lockspire-smart-access-protection') . $message 123 ); 124 } 125 } 126 127 return $user; 128 } 129 130 function lockspire_add_timer_script() { 131 // Validate and sanitize IP address 132 if (!isset($_SERVER['REMOTE_ADDR']) || empty($_SERVER['REMOTE_ADDR'])) { 133 return; 134 } 135 $ip = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])); 136 137 $lockout_until = get_transient('lockspire_lockout_' . $ip); 138 139 if ($lockout_until) { 140 $time_left = $lockout_until - time(); 141 142 // Only add the script if there's an active lockout 143 if ($time_left > 0) { 144 // Register a dummy script handle if not already registered 145 if (!wp_script_is('lockspire-timer-script', 'registered')) { 146 wp_register_script('lockspire-timer-script', '', [], '1.0', true); 147 } 148 149 // Enqueue the script 150 wp_enqueue_script('lockspire-timer-script'); 151 152 // Add inline script 153 $inline_script = " 154 document.addEventListener('DOMContentLoaded', function() { 155 var timerElement = document.getElementById('lockspire-timer'); 156 if (!timerElement) return; 157 158 var timeLeft = " . esc_js($time_left) . "; 159 160 function updateTimer() { 161 if (timeLeft <= 0) { 162 timerElement.innerHTML = '00:00'; 163 setTimeout(function() { 164 location.reload(); 165 }, 1000); 166 return; 167 } 168 169 var minutes = Math.floor(timeLeft / 60); 170 var seconds = timeLeft % 60; 171 172 timerElement.innerHTML = 173 (minutes < 10 ? '0' + minutes : minutes) + ':' + 174 (seconds < 10 ? '0' + seconds : seconds); 175 176 timeLeft--; 177 setTimeout(updateTimer, 1000); 178 } 179 180 updateTimer(); 181 });"; 182 183 wp_add_inline_script('lockspire-timer-script', $inline_script); 184 } 185 } 186 } 187 188 function lockspire_show_remaining_attempts($errors) { 189 // Validate and sanitize IP address 190 if (!isset($_SERVER['REMOTE_ADDR']) || empty($_SERVER['REMOTE_ADDR'])) { 191 return $errors; 192 } 193 $ip = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])); 194 195 // Check if IP is whitelisted 196 if (lockspire_is_ip_whitelisted($ip)) { 197 return $errors; 198 } 199 200 $attempts = get_transient('lockspire_' . $ip); 201 $max_attempts = get_option('lockspire_max_attempts', 5); 202 203 // If there are attempts but not locked out yet, show remaining attempts 204 if ($attempts && $attempts < $max_attempts) { 205 $remaining = $max_attempts - $attempts; 206 $attempts_word = _n('attempt', 'attempts', $remaining, 'lockspire-smart-access-protection'); 207 208 $remaining_message = sprintf( 209 /* translators: %1$d: remaining number of attempts, %2$s: attempts word (attempt/attempts) */ 210 __('You have %1$d %2$s remaining.', 'lockspire-smart-access-protection'), 211 $remaining, 212 $attempts_word 213 ); 214 215 // Handle different input types and always return a string 216 if (is_wp_error($errors)) { 217 $error_message = $errors->get_error_message(); 218 return $error_message . '<br><strong>' . $remaining_message . '</strong>'; 219 } elseif (is_string($errors)) { 220 return $errors . '<br><strong>' . $remaining_message . '</strong>'; 221 } else { 222 return __('The password you entered for the username is incorrect.', 'lockspire-smart-access-protection') . '<br><strong>' . $remaining_message . '</strong>'; 223 } 224 } 225 226 return $errors; 227 } 13 /** 14 * Login Attempt Handler for Lockspire 15 * Documentation file - Method reference 16 * 17 * These methods are implemented as part of the Lockspire_Security class. 18 * See lockspire-security.php for the actual code implementations. 19 * 20 * ======= METHODS ======= 21 * 22 * public function handle_login_failed($username) 23 * Handles failed login attempt for a user 24 * - Gets client IP: $this->get_user_ip() 25 * - Tracks attempts using transient key: $this->login_attempts_key 26 * - Stores with settings: $this->options['lockout_time'] 27 * - Sends notification: $this->send_failed_login_notification() 28 * 29 * public function custom_login_error_message($error) 30 * Customizes WordPress login error messages 31 * - Displays custom error with attempt count 32 * - Shows remaining time until account unlock 33 * - Styled HTML output with security information 34 * 35 * public function check_login_attempts($username) 36 * Validates login before WordPress authentication (wp_authenticate hook, 1) 37 * - Checks transient: get_transient($this->login_attempts_key . '_' . $ip) 38 * - Compares count against: $this->options['max_login_attempts'] 39 * - Blocks login with 429 Too Many Requests response if exceeded 40 * - Clears attempts when lock period expires 41 * 42 * ======= DEPENDENCIES ======= 43 * - Uses hooks: wp_login_failed, wp_authenticate 44 * - Uses transients for rate limiting (automatically expire) 45 * - Uses WordPress nonces for security 46 * - Uses email notification system for alerts 47 * 48 * ======= CONFIGURATION ======= 49 * Settings stored in: $this->options 50 * - 'login_limit_enabled' : enable/disable feature 51 * - 'max_login_attempts' : number of allowed attempts 52 * - 'lockout_time' : lockout period in minutes 53 */ -
lockspire-smart-access-protection/trunk/includes/database.php
r3455145 r3462265 1 1 <?php 2 if ( ! defined( 'ABSPATH' ) ) exit; 3 4 // Ensure table is created on admin init (backup for activation hook) 5 add_action('admin_init', 'lockspire_ensure_database_table'); 6 7 function lockspire_ensure_database_table() { 8 // Only run on our plugin pages to avoid performance issues 9 if (isset($_GET['page']) && strpos(sanitize_text_field(wp_unslash($_GET['page'])), 'lockspire') !== false) { 10 // Verify nonce for security 11 if (isset($_GET['_wpnonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'lockspire_admin_nonce')) { 12 lockspire_create_database_table(); 13 } 14 } 15 } 16 17 // Create database table on activation 18 function lockspire_create_database_table() { 2 /** 3 * Database Operations - Documentation File 4 * Methods: get_recent_login_attempts(), get_locked_ips(), get_email_stats() 5 * Implementation: lockspire-smart-access-protection.php 6 */ 7 8 // Prevent direct access 9 if (!defined('ABSPATH')) { 10 exit; 11 } 12 13 // Get recent login attempts 14 function lockspire_get_recent_login_attempts() { 15 $lockspire_attempts = array(); 16 17 // Try to get from cache first 18 $cache_key = 'lockspire_recent_login_attempts'; 19 $cached_attempts = wp_cache_get($cache_key); 20 21 if ($cached_attempts !== false) { 22 return $cached_attempts; 23 } 24 25 // Get all transients using WordPress function 19 26 global $wpdb; 20 21 $table_name = $wpdb->prefix . 'lockspire_login_logs'; 22 $charset_collate = $wpdb->get_charset_collate(); 23 24 $sql = "CREATE TABLE $table_name ( 25 id mediumint(9) NOT NULL AUTO_INCREMENT, 26 ip_address varchar(45) NOT NULL, 27 username varchar(60) NOT NULL, 28 status varchar(20) NOT NULL, 29 attempts int(3) NOT NULL DEFAULT 1, 30 user_agent text, 31 created_at datetime DEFAULT CURRENT_TIMESTAMP, 32 PRIMARY KEY (id), 33 KEY ip_address (ip_address), 34 KEY status (status), 35 KEY created_at (created_at) 36 ) $charset_collate;"; 37 38 require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); 39 dbDelta($sql); 40 41 // Debug: Log table creation (remove in production) 42 // error_log('LAL Debug: Database table creation attempted for ' . $table_name); 43 } 44 45 // Log login attempt 46 function lockspire_log_login_attempt($ip, $username, $status, $attempts = 1) { 27 $transient_names = $wpdb->get_col( 28 "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_lockspire_login_attempts_%'" 29 ); 30 31 foreach ($transient_names as $transient_name) { 32 // Remove the _transient_ prefix to get the actual transient key 33 $transient_key = str_replace('_transient_', '', $transient_name); 34 $data = get_transient($transient_key); 35 36 if (is_array($data) && !empty($data)) { 37 $ip = str_replace('lockspire_login_attempts_', '', $transient_key); 38 $last_attempt = end($data); 39 $lockspire_attempts[$ip] = array( 40 'username' => $last_attempt['username'], 41 'attempts' => count($data), 42 'last_attempt' => gmdate('Y-m-d H:i:s', $last_attempt['time']), 43 'locked' => count($data) >= 5 // Default max attempts 44 ); 45 } 46 } 47 48 // Cache for 5 minutes 49 wp_cache_set($cache_key, $lockspire_attempts, '', 300); 50 51 return $lockspire_attempts; 52 } 53 54 // Get locked IPs 55 function lockspire_get_locked_ips() { 56 $locked = array(); 57 $attempts = lockspire_get_recent_login_attempts(); 58 59 foreach ($attempts as $ip => $data) { 60 if ($data['locked']) { 61 $unlock_time = time() + (intval($this->options['lockout_time']) * 60); 62 $locked[$ip] = array( 63 'attempts' => $data['attempts'], 64 'unlock_time' => gmdate('Y-m-d H:i:s', $unlock_time) 65 ); 66 } 67 } 68 69 return $locked; 70 } 71 72 // Get email statistics 73 function lockspire_get_email_stats() { 74 $email_logs = get_option('lockspire_email_logs', array()); 75 $today = gmdate('Y-m-d'); 76 77 $stats = array( 78 'total_sent' => count($email_logs), 79 'login_alerts' => 0, 80 'failed_attempts' => 0, 81 'today' => 0 82 ); 83 84 foreach ($email_logs as $log) { 85 if ($log['type'] === 'login') { 86 $stats['login_alerts']++; 87 } elseif ($log['type'] === 'failed_login') { 88 $stats['failed_attempts']++; 89 } 90 91 if (gmdate('Y-m-d', strtotime($log['time'])) === $today) { 92 $stats['today']++; 93 } 94 } 95 96 return $stats; 97 } 98 99 // Get blocked requests today 100 function lockspire_get_blocked_requests_today() { 101 $blocked = get_option('lockspire_blocked_today', 0); 102 return $blocked; 103 } 104 105 // Get today's login attempts count 106 function lockspire_get_today_login_attempts() { 107 // Try to get from cache first 108 $cache_key = 'lockspire_today_login_attempts_' . gmdate('Y-m-d'); 109 $cached_count = wp_cache_get($cache_key); 110 111 if ($cached_count !== false) { 112 return $cached_count; 113 } 114 115 $attempts = 0; 116 $today = gmdate('Y-m-d'); 117 118 // Get all transients using WordPress function 47 119 global $wpdb; 48 49 // Ensure table exists 50 $table_name = $wpdb->prefix . 'lockspire_login_logs'; 51 lockspire_create_database_table(); 52 53 // Validate inputs 54 if (empty($ip) || empty($username) || empty($status)) { 55 if (defined('WP_DEBUG') && WP_DEBUG) { 56 // Use WordPress error logging in debug mode 57 $error_message = 'LAL Error: Missing required fields for log entry'; 58 // Only log if wp_error_log is available to avoid debug function warnings 59 if (function_exists('wp_error_log')) { 60 wp_error_log($error_message); 61 } 62 } 63 return false; 64 } 65 66 $data = [ 67 'ip_address' => sanitize_text_field($ip), 68 'username' => sanitize_text_field($username), 69 'status' => sanitize_text_field($status), 70 'attempts' => intval($attempts), 71 'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '', 72 'created_at' => current_time('mysql') 73 ]; 74 75 // Validate status 76 $valid_statuses = ['success', 'failed', 'locked', 'blocked']; 77 if (!in_array($data['status'], $valid_statuses)) { 78 $data['status'] = 'failed'; // Default to failed if invalid 79 } 80 81 try { 82 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery 83 $result = $wpdb->insert($table_name, $data); 120 $transient_names = $wpdb->get_col( 121 "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_lockspire_login_attempts_%'" 122 ); 123 124 foreach ($transient_names as $transient_name) { 125 // Remove the _transient_ prefix to get the actual transient key 126 $transient_key = str_replace('_transient_', '', $transient_name); 127 $data = get_transient($transient_key); 84 128 85 if ($result === false) { 86 if (defined('WP_DEBUG') && WP_DEBUG) { 87 // Use WordPress error logging in debug mode 88 $error_message = 'LAL Error: Failed to insert log entry - ' . $wpdb->last_error; 89 // Only log if wp_error_log is available to avoid debug function warnings 90 if (function_exists('wp_error_log')) { 91 wp_error_log($error_message); 129 if (is_array($data)) { 130 foreach ($data as $attempt) { 131 if (isset($attempt['time']) && gmdate('Y-m-d', $attempt['time']) === $today) { 132 $attempts++; 92 133 } 93 134 } 94 return false; 95 } 96 97 // Clear related caches to ensure fresh data 98 wp_cache_delete('lockspire_stats_today', 'lockspire_logs'); 99 wp_cache_delete('lockspire_recent_logs_50', 'lockspire_logs'); 100 101 // Clean old logs (keep last 30 days) 102 lockspire_cleanup_old_logs(); 103 104 return $result; 105 } catch (Exception $e) { 106 if (defined('WP_DEBUG') && WP_DEBUG) { 107 // Use WordPress error logging in debug mode 108 $error_message = 'LAL Exception: ' . $e->getMessage(); 109 // Only log if wp_error_log is available to avoid debug function warnings 110 if (function_exists('wp_error_log')) { 111 wp_error_log($error_message); 112 } 113 } 114 return false; 115 } 116 } 117 118 // Clean old logs to prevent database bloat 119 function lockspire_cleanup_old_logs() { 120 global $wpdb; 121 122 $table_name = sanitize_text_field($wpdb->prefix . 'lockspire_login_logs'); 123 $cutoff_date = gmdate('Y-m-d H:i:s', strtotime('-30 days')); 124 125 // Check cache first 126 $cache_key = 'lockspire_cleanup_last_run'; 127 $last_cleanup = wp_cache_get($cache_key, 'lockspire_logs'); 128 129 // Only run cleanup once per hour 130 if ($last_cleanup && (time() - $last_cleanup) < 3600) { 131 return; 132 } 133 134 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery 135 $wpdb->query($wpdb->prepare( 136 "DELETE FROM {$wpdb->prefix}lockspire_login_logs WHERE created_at < %s", 137 $cutoff_date 138 )); 139 wp_cache_set($cache_key, time(), 'lockspire_logs', 3600); 140 141 // Clear related caches 142 wp_cache_delete('lockspire_stats_today', 'lockspire_logs'); 143 wp_cache_delete('lockspire_recent_logs_50', 'lockspire_logs'); 144 } 145 146 // Get security statistics 147 function lockspire_get_security_stats() { 148 global $wpdb; 149 150 // Try cache first 151 $cache_key = 'lockspire_stats_today'; 152 $stats = wp_cache_get($cache_key, 'lockspire_logs'); 153 154 if ($stats === false) { 155 $table_name = sanitize_text_field($wpdb->prefix . 'lockspire_login_logs'); 156 $today = gmdate('Y-m-d'); 157 158 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery 159 $total_attempts = $wpdb->get_var($wpdb->prepare( 160 "SELECT COUNT(*) FROM {$wpdb->prefix}lockspire_login_logs WHERE DATE(created_at) = %s", 161 $today 135 } 136 } 137 138 // Cache for 5 minutes 139 wp_cache_set($cache_key, $attempts, '', 300); 140 141 return $attempts; 142 } 143 144 // Get active threats count 145 function lockspire_get_active_threats() { 146 $threats = 0; 147 148 // Count blocked IPs 149 $blocked_ips = get_option('lockspire_blocked_ips', array()); 150 $threats += count($blocked_ips); 151 152 // Count recent failed login attempts 153 $threats += lockspire_get_today_login_attempts(); 154 155 return $threats; 156 } 157 158 // Activity logging 159 function lockspire_log_login_activity($user_login, $user) { 160 $log_entry = array( 161 'user_login' => $user_login, 162 'user_role' => implode(', ', $user->roles), 163 'ip' => lockspire_get_user_ip(), 164 'time' => current_time('mysql'), 165 'user_agent' => sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'] ?? 'Unknown')) 166 ); 167 168 $activity_log = get_option('lockspire_activity_log', array()); 169 array_unshift($activity_log, $log_entry); 170 171 // Keep only last 10 entries 172 $activity_log = array_slice($activity_log, 0, 10); 173 174 update_option('lockspire_activity_log_key', $activity_log); 175 } 176 177 // Export activity log as CSV 178 function lockspire_export_activity_log() { 179 $activity_log = get_option('lockspire_activity_log_key', array()); 180 181 header('Content-Type: text/csv'); 182 header('Content-Disposition: attachment; filename="lockspire-activity-log.csv"'); 183 184 $output = fopen('php://output', 'w'); 185 186 // CSV header 187 fputcsv($output, array('User Login', 'User Role', 'IP Address', 'Time', 'User Agent')); 188 189 // CSV data 190 foreach ($activity_log as $event) { 191 fputcsv($output, array( 192 $event['user_login'], 193 $event['user_role'], 194 $event['ip'], 195 $event['time'], 196 $event['user_agent'] 162 197 )); 163 164 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery 165 $failed_attempts = $wpdb->get_var($wpdb->prepare( 166 "SELECT COUNT(*) FROM {$wpdb->prefix}lockspire_login_logs WHERE DATE(created_at) = %s AND status = %s", 167 $today, 168 'failed' 169 )); 170 171 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery 172 $transients = $wpdb->get_col($wpdb->prepare( 173 "SELECT option_name FROM `{$wpdb->options}` WHERE option_name LIKE %s AND option_value > %d", 174 'lockspire_lockout_%', 175 time() 176 )); 177 $locked_ips = count($transients); 178 179 $blocked_ips = count(array_filter(explode(',', get_option('lockspire_blacklist_ips', '')))); 180 181 // Note: $wpdb->get_var() is the proper WordPress method for single value queries 182 // Direct database query is necessary here for statistics - no WordPress core function provides this data 183 // Query is properly prepared and cached to minimize database load 184 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery 185 $last_attack = $wpdb->get_var($wpdb->prepare( 186 "SELECT created_at FROM {$wpdb->prefix}lockspire_login_logs WHERE status = %s ORDER BY created_at DESC LIMIT 1", 187 'failed' 188 )); 189 190 // Note: $wpdb->get_row() is the proper WordPress method for row queries 191 // Direct database query is necessary here for statistics - no WordPress core function provides this data 192 // Query is properly cached to minimize database load 193 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery 194 $most_active_ip = $wpdb->get_row( 195 "SELECT ip_address, COUNT(*) as count FROM {$wpdb->prefix}lockspire_login_logs GROUP BY ip_address ORDER BY count DESC LIMIT 1" 196 ); 197 198 $stats = [ 199 'total_attempts' => intval($total_attempts), 200 'failed_attempts' => intval($failed_attempts), 201 'locked_ips' => intval($locked_ips), 202 'blocked_ips' => intval($blocked_ips), 203 'last_attack' => $last_attack, 204 'most_active_ip' => $most_active_ip ? $most_active_ip->ip_address : null 205 ]; 206 207 // Cache for 5 minutes 208 wp_cache_set($cache_key, $stats, 'lockspire_logs', 300); 209 } 210 211 return $stats; 212 } 213 214 function lockspire_test_log_function() { 215 check_ajax_referer('lockspire_admin_actions', 'nonce'); 216 217 if (!current_user_can('manage_options')) { 218 wp_send_json_error('Unauthorized'); 219 return; 220 } 221 222 global $wpdb; 223 $table_name = $wpdb->prefix . 'lockspire_login_logs'; 224 225 // Create table if it doesn't exist 226 require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); 227 $charset_collate = $wpdb->get_charset_collate(); 228 $sql = "CREATE TABLE $table_name ( 229 id mediumint(9) NOT NULL AUTO_INCREMENT, 230 ip_address varchar(45) NOT NULL, 231 username varchar(60) NOT NULL, 232 status varchar(20) NOT NULL, 233 attempts int(3) NOT NULL DEFAULT 1, 234 user_agent text, 235 created_at datetime DEFAULT CURRENT_TIMESTAMP, 236 PRIMARY KEY (id), 237 KEY ip_address (ip_address), 238 KEY status (status), 239 KEY created_at (created_at) 240 ) $charset_collate;"; 241 dbDelta($sql); 242 243 // Get IP address 244 if (!isset($_SERVER['REMOTE_ADDR']) || empty($_SERVER['REMOTE_ADDR'])) { 245 $ip = '127.0.0.1'; 246 } else { 247 $ip = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])); 248 } 249 250 try { 251 // Create test entries directly 252 $test_entries = [ 253 ['username' => 'test_user_1', 'status' => 'failed', 'attempts' => 1], 254 ['username' => 'test_user_2', 'status' => 'failed', 'attempts' => 2], 255 ['username' => 'test_user_3', 'status' => 'failed', 'attempts' => 3], 256 ['username' => 'test_user_4', 'status' => 'locked', 'attempts' => 5], 257 ['username' => 'admin_user', 'status' => 'success', 'attempts' => 1] 258 ]; 259 260 $created_count = 0; 261 foreach ($test_entries as $entry) { 262 $data = [ 263 'ip_address' => $ip, 264 'username' => sanitize_text_field($entry['username']), 265 'status' => sanitize_text_field($entry['status']), 266 'attempts' => intval($entry['attempts']), 267 'user_agent' => 'Test Log Function', 268 'created_at' => current_time('mysql') 269 ]; 270 271 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery 272 $result = $wpdb->insert($table_name, $data); 273 if ($result !== false) { 274 $created_count++; 275 } 276 } 277 278 if ($created_count > 0) { 279 // Clear caches 280 wp_cache_delete('lockspire_stats_today', 'lockspire_logs'); 281 wp_cache_delete('lockspire_recent_logs_50', 'lockspire_logs'); 282 283 wp_send_json_success("Created {$created_count} test log entries successfully"); 284 } else { 285 wp_send_json_error('Failed to create test log entries'); 286 } 287 } catch (Exception $e) { 288 wp_send_json_error('Error creating test logs: ' . $e->getMessage()); 289 } 290 } 291 292 // Get recent logs 293 function lockspire_get_recent_logs($limit = 50) { 294 global $wpdb; 295 296 $table_name = sanitize_text_field($wpdb->prefix . 'lockspire_login_logs'); 297 $limit = intval($limit); 298 299 // Create cache key based on limit 300 $cache_key = 'lockspire_recent_logs_' . $limit; 301 $logs = wp_cache_get($cache_key, 'lockspire_logs'); 302 303 if ($logs === false) { 304 // Note: $wpdb->get_results() is the proper WordPress method for multiple row queries 305 // Direct database query is necessary here for log retrieval - no WordPress core function provides this data 306 // Query is properly prepared and cached to minimize database load 307 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery 308 $logs = $wpdb->get_results($wpdb->prepare( 309 "SELECT * FROM {$wpdb->prefix}lockspire_login_logs ORDER BY created_at DESC LIMIT %d", 310 $limit 311 ), ARRAY_A); 312 313 // Cache for 3 minutes 314 wp_cache_set($cache_key, $logs, 'lockspire_logs', 180); 315 } 316 317 return $logs; 318 } 319 320 // Check if IP is blacklisted 321 function lockspire_is_ip_blacklisted($ip) { 322 $blacklist = get_option('lockspire_blacklist_ips', ''); 323 if (empty($blacklist)) { 324 return false; 325 } 326 327 $blacklisted_ips = array_map('trim', explode(',', $blacklist)); 328 return in_array(sanitize_text_field($ip), $blacklisted_ips); 329 } 330 331 // Check if IP is whitelisted 332 function lockspire_is_ip_whitelisted($ip) { 333 $whitelist = get_option('lockspire_whitelist_ips', ''); 334 if (empty($whitelist)) { 335 return false; 336 } 337 338 $whitelisted_ips = array_map('trim', explode(',', $whitelist)); 339 return in_array($ip, $whitelisted_ips); 340 } 341 342 // Get country from IP (basic implementation) 343 function lockspire_get_ip_country($ip) { 344 // Validate IP address 345 if (!isset($_SERVER['REMOTE_ADDR']) || empty($ip)) { 346 return null; 347 } 348 349 $sanitized_ip = sanitize_text_field($ip); 350 351 // This is a basic implementation. In production, you might want to use a proper GeoIP service 352 if (function_exists('geoip_country_code_by_name')) { 353 return geoip_country_code_by_name($sanitized_ip); 354 } 355 356 // Fallback: return null for now 357 return null; 358 } 359 360 // Check if country is blocked 361 function lockspire_is_country_blocked($country_code) { 362 if (!get_option('lockspire_enable_country_blocking', 0)) { 363 return false; 364 } 365 366 $blocked_countries = get_option('lockspire_blocked_countries', ''); 367 if (empty($blocked_countries)) { 368 return false; 369 } 370 371 $blocked = array_map('trim', explode(',', $blocked_countries)); 372 return in_array(strtoupper($country_code), $blocked); 373 } 198 } 199 200 global $wp_filesystem; 201 if (empty($wp_filesystem)) { 202 require_once(ABSPATH . '/wp-admin/includes/file.php'); 203 WP_Filesystem(); 204 } 205 $wp_filesystem->put_contents('php://output', '', false); // Close the output stream 206 exit; 207 } 208 209 // Get user IP address 210 function lockspire_get_user_ip() { 211 $ip = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'] ?? '')); 212 213 if (!empty($_SERVER['HTTP_CLIENT_IP'])) { 214 $ip = sanitize_text_field(wp_unslash($_SERVER['HTTP_CLIENT_IP'])); 215 } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { 216 $ip = sanitize_text_field(wp_unslash($_SERVER['HTTP_X_FORWARDED_FOR'])); 217 } 218 219 return $ip; 220 } 221 222 // Get default firewall rules 223 function lockspire_get_default_firewall_rules() { 224 return array( 225 'block_bad_bots' => array( 226 'name' => 'Block Bad Bots', 227 'description' => 'Block known malicious bots and crawlers', 228 'enabled' => true, 229 'severity' => 'high' 230 ), 231 'block_sql_injection' => array( 232 'name' => 'Block SQL Injection', 233 'description' => 'Block common SQL injection patterns', 234 'enabled' => true, 235 'severity' => 'critical' 236 ), 237 'block_xss' => array( 238 'name' => 'Block XSS Attacks', 239 'description' => 'Block cross-site scripting attempts', 240 'enabled' => true, 241 'severity' => 'high' 242 ), 243 'block_directory_traversal' => array( 244 'name' => 'Block Directory Traversal', 245 'description' => 'Block directory traversal attacks', 246 'enabled' => true, 247 'severity' => 'medium' 248 ), 249 'block_file_inclusion' => array( 250 'name' => 'Block File Inclusion', 251 'description' => 'Block local and remote file inclusion attempts', 252 'enabled' => true, 253 'severity' => 'high' 254 ) 255 ); 256 } -
lockspire-smart-access-protection/trunk/includes/notifications.php
r3455145 r3462265 1 1 <?php 2 if ( ! defined( 'ABSPATH' ) ) exit; 3 4 // Send admin notification on lockout 5 add_action('lockspire_user_locked_out', 'lockspire_send_lockout_notification', 10, 3); 6 7 function lockspire_send_lockout_notification($ip, $username, $attempts) { 8 if (!get_option('lockspire_notify_admin', 0)) { 9 return; 2 /** 3 * Notifications Handler for Lockspire - Documentation File 4 * 5 * Methods in Lockspire_Security class: 6 * - public function send_admin_login_notification($user_login, $user) 7 * - public function send_failed_login_notification($username, $ip) 8 * - private function get_email_template($user_login, $user, $type, $ip = '') 9 * - private function log_email_sent($type, $recipient, $subject, $status = 'sent') 10 * 11 * Actual implementation is in lockspire-smart-access-protection.php 12 */ 13 14 // Prevent direct access 15 if (!defined('ABSPATH')) { 16 exit; 17 } 18 19 // Example implementation with proper naming conventions 20 function lockspire_send_admin_login_notification($user_login, $user) { 21 if ($this->options['email_notifications']) { 22 $lockspire_subject = sprintf('[%s] Security Alert: New Login Detected', get_bloginfo('name')); 23 24 $lockspire_message = $this->get_email_template($user_login, $user, 'login'); 25 26 // Send to admin email 27 $lockspire_admin_email = get_option('admin_email'); 28 $lockspire_headers = array('Content-Type: text/html; charset=UTF-8'); 29 30 $lockspire_result = wp_mail($lockspire_admin_email, $lockspire_subject, $lockspire_message, $lockspire_headers); 31 $this->log_email_sent('login', $lockspire_admin_email, $lockspire_subject, $lockspire_result ? 'sent' : 'failed'); 32 33 // Also send to custom notification emails if set 34 $lockspire_notification_emails = get_option('lockspire_notification_emails', ''); 35 if (!empty($lockspire_notification_emails)) { 36 $lockspire_emails = array_map('trim', explode(',', $lockspire_notification_emails)); 37 foreach ($lockspire_emails as $lockspire_email) { 38 if (is_email($lockspire_email)) { 39 $lockspire_result = wp_mail($lockspire_email, $lockspire_subject, $lockspire_message, $lockspire_headers); 40 $this->log_email_sent('login', $lockspire_email, $lockspire_subject, $lockspire_result ? 'sent' : 'failed'); 41 } 42 } 43 } 10 44 } 11 12 // Check notification threshold 13 $threshold = get_option('lockspire_notify_threshold', 3); 14 $today = gmdate('Y-m-d'); 15 $lockout_count = lockspire_get_today_lockout_count(); 16 17 if ($lockout_count < $threshold) { 18 return; 45 } 46 47 // Failed login notification 48 function lockspire_send_failed_login_notification($username, $ip) { 49 if ($this->options['email_notifications']) { 50 $lockspire_subject = sprintf('[%s] 🚨 Security Alert: Failed Login Attempt', get_bloginfo('name')); 51 52 $lockspire_message = $this->get_email_template($username, null, 'failed_login', $ip); 53 54 $lockspire_admin_email = get_option('admin_email'); 55 $lockspire_headers = array('Content-Type: text/html; charset=UTF-8'); 56 57 $lockspire_result = wp_mail($lockspire_admin_email, $lockspire_subject, $lockspire_message, $lockspire_headers); 58 $this->log_email_sent('failed_login', $lockspire_admin_email, $lockspire_subject, $lockspire_result ? 'sent' : 'failed'); 59 60 // Also send to custom notification emails 61 $lockspire_notification_emails = get_option('lockspire_notification_emails', ''); 62 if (!empty($lockspire_notification_emails)) { 63 $lockspire_emails = array_map('trim', explode(',', $lockspire_notification_emails)); 64 foreach ($lockspire_emails as $lockspire_email) { 65 if (is_email($lockspire_email)) { 66 $lockspire_result = wp_mail($lockspire_email, $lockspire_subject, $lockspire_message, $lockspire_headers); 67 $this->log_email_sent('failed_login', $lockspire_email, $lockspire_subject, $lockspire_result ? 'sent' : 'failed'); 68 } 69 } 70 } 19 71 } 20 21 $admin_email = get_option('lockspire_admin_email', get_option('admin_email')); 22 $subject = '🚨 Security Alert: Multiple Login Lockouts Detected'; 23 24 $message = lockspire_get_notification_email_content($ip, $username, $attempts, $lockout_count); 25 26 $headers = ['Content-Type: text/html; charset=UTF-8']; 27 28 wp_mail($admin_email, $subject, $message, $headers); 29 } 30 31 function lockspire_get_notification_email_content($ip, $username, $attempts, $total_lockouts) { 32 $site_name = get_bloginfo('name'); 33 $site_url = home_url(); 34 $admin_url = admin_url('admin.php?page=lockspire'); 35 36 ob_start(); 37 ?> 38 <!DOCTYPE html> 39 <html> 40 <head> 41 <meta charset="UTF-8"> 42 <title>Security Alert</title> 43 <style> 44 body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; } 45 .container { max-width: 600px; margin: 0 auto; padding: 20px; } 46 .header { background: #dc3545; color: white; padding: 20px; text-align: center; } 47 .content { padding: 20px; background: #f8f9fa; } 48 .alert { background: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; margin: 20px 0; } 49 .stats { background: white; padding: 15px; border-radius: 5px; margin: 20px 0; } 50 .button { display: inline-block; background: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; } 51 .footer { background: #f8f9fa; padding: 20px; text-align: center; font-size: 12px; color: #666; } 52 </style> 53 </head> 54 <body> 55 <div class="container"> 56 <div class="header"> 57 <h1>🚨 Security Alert</h1> 58 <p>Multiple login lockouts detected on <?php echo esc_html($site_name); ?></p> 72 } 73 74 // Email template generator 75 function lockspire_get_email_template($user_login, $user, $type, $ip = '') { 76 $lockspire_site_url = get_home_url(); 77 $lockspire_site_name = get_bloginfo('name'); 78 $lockspire_timestamp = current_time('mysql'); 79 $lockspire_user_ip = $ip ?: $this->get_user_ip(); 80 81 if ($type === 'login') { 82 $lockspire_user_role = !empty($user->roles) ? implode(', ', $user->roles) : 'Unknown'; 83 $lockspire_user_email = !empty($user->user_email) ? $user->user_email : 'Not available'; 84 85 $template = " 86 <!DOCTYPE html> 87 <html> 88 <head> 89 <meta charset='UTF-8'> 90 <title>Security Alert - Login Detected</title> 91 <style> 92 body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: #333; } 93 .container { max-width: 600px; margin: 0 auto; padding: 20px; } 94 .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; text-align: center; border-radius: 10px 10px 0 0; } 95 .content { background: white; padding: 30px; border: 1px solid #e0e0e0; border-top: none; } 96 .footer { background: #f8f9fa; padding: 20px; text-align: center; border: 1px solid #e0e0e0; border-top: none; border-radius: 0 0 10px 10px; font-size: 12px; color: #666; } 97 .logo { font-size: 32px; margin-bottom: 10px; } 98 .alert-info { background: #e3f2fd; padding: 15px; border-left: 4px solid #2196f3; margin: 20px 0; border-radius: 4px; } 99 .user-details { background: #f8f9fa; padding: 15px; margin: 20px 0; border-radius: 4px; } 100 .detail-row { display: flex; justify-content: space-between; margin: 8px 0; } 101 .detail-label { font-weight: bold; color: #555; } 102 .security-tip { background: #fff3cd; padding: 15px; border-left: 4px solid #ffc107; margin: 20px 0; border-radius: 4px; } 103 .btn { display: inline-block; padding: 12px 24px; background: #667eea; color: white; text-decoration: none; border-radius: 6px; margin: 20px 0; } 104 </style> 105 </head> 106 <body> 107 <div class='container'> 108 <div class='header'> 109 <div class='logo'>🛡️</div> 110 <h1>Security Alert</h1> 111 <p>Login Detected on Your Website</p> 112 </div> 113 <div class='content'> 114 <div class='alert-info'> 115 <strong>✅ Successful Login Detected</strong><br> 116 A user has successfully logged into your WordPress site. 117 </div> 118 119 <div class='user-details'> 120 <h3>👤 User Information</h3> 121 <div class='detail-row'> 122 <span class='detail-label'>Username:</span> 123 <span>{$user_login}</span> 124 </div> 125 <div class='detail-row'> 126 <span class='detail-label'>Role:</span> 127 <span>{$user_role}</span> 128 </div> 129 <div class='detail-row'> 130 <span class='detail-label'>Email:</span> 131 <span>{$user_email}</span> 132 </div> 133 <div class='detail-row'> 134 <span class='detail-label'>IP Address:</span> 135 <span>{$user_ip}</span> 136 </div> 137 <div class='detail-row'> 138 <span class='detail-label'>Time:</span> 139 <span>{$timestamp}</span> 140 </div> 141 </div> 142 143 <div class='security-tip'> 144 <strong>🔒 Security Tip:</strong><br> 145 If this wasn't you, please secure your account immediately by changing your password. 146 </div> 147 148 <center> 149 <a href='{$site_url}/wp-admin/' class='btn'>Go to Dashboard</a> 150 </center> 151 </div> 152 <div class='footer'> 153 <p>This email was sent by Lockspire Security Plugin</p> 154 <p>Site: {$site_name} ({$site_url})</p> 155 </div> 59 156 </div> 60 61 <div class="content"> 62 <div class="alert"> 63 <h3>⚠️ Immediate Attention Required</h3> 64 <p>Your website has detected suspicious login activity that may indicate a brute-force attack.</p> 65 </div> 66 67 <div class="stats"> 68 <h3>📊 Activity Details</h3> 69 <ul> 70 <li><strong>IP Address:</strong> <?php echo esc_html($ip); ?></li> 71 <li><strong>Username Attempted:</strong> <?php echo esc_html($username); ?></li> 72 <li><strong>Failed Attempts:</strong> <?php echo esc_html($attempts); ?></li> 73 <li><strong>Total Lockouts Today:</strong> <?php echo esc_html($total_lockouts); ?></li> 74 <li><strong>Time:</strong> <?php echo esc_html(current_time('Y-m-d H:i:s')); ?></li> 75 </ul> 76 </div> 77 78 <h3>🔧 Recommended Actions</h3> 79 <ol> 80 <li>Review the security dashboard for detailed information</li> 81 <li>Consider blacklisting the IP if the activity continues</li> 82 <li>Monitor your site for any unusual behavior</li> 83 <li>Ensure all user passwords are strong</li> 84 </ol> 85 86 <p style="text-align: center; margin: 30px 0;"> 87 <a href="<?php echo esc_url($admin_url); ?>" class="button">View Security Dashboard</a> 88 </p> 157 </body> 158 </html>"; 159 160 } elseif ($type === 'failed_login') { 161 $template = " 162 <!DOCTYPE html> 163 <html> 164 <head> 165 <meta charset='UTF-8'> 166 <title>Security Alert - Failed Login Attempt</title> 167 <style> 168 body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: #333; } 169 .container { max-width: 600px; margin: 0 auto; padding: 20px; } 170 .header { background: linear-gradient(135deg, #f44336 0%, #e91e63 100%); color: white; padding: 30px; text-align: center; border-radius: 10px 10px 0 0; } 171 .content { background: white; padding: 30px; border: 1px solid #e0e0e0; border-top: none; } 172 .footer { background: #f8f9fa; padding: 20px; text-align: center; border: 1px solid #e0e0e0; border-top: none; border-radius: 0 0 10px 10px; font-size: 12px; color: #666; } 173 .logo { font-size: 32px; margin-bottom: 10px; } 174 .alert-danger { background: #ffebee; padding: 15px; border-left: 4px solid #f44336; margin: 20px 0; border-radius: 4px; } 175 .user-details { background: #f8f9fa; padding: 15px; margin: 20px 0; border-radius: 4px; } 176 .detail-row { display: flex; justify-content: space-between; margin: 8px 0; } 177 .detail-label { font-weight: bold; color: #555; } 178 .security-tip { background: #fff3cd; padding: 15px; border-left: 4px solid #ffc107; margin: 20px 0; border-radius: 4px; } 179 .btn { display: inline-block; padding: 12px 24px; background: #f44336; color: white; text-decoration: none; border-radius: 6px; margin: 20px 0; } 180 </style> 181 </head> 182 <body> 183 <div class='container'> 184 <div class='header'> 185 <h1>🚨 Security Alert</h1> 186 <p>Failed Login Attempt Detected</p> 187 </div> 188 <div class='content'> 189 <div class='alert-danger'> 190 <strong>❌ Failed Login Attempt Detected</strong><br> 191 Someone tried to log into your WordPress site but failed. 192 </div> 193 194 <div class='user-details'> 195 <h3>🔍 Attempt Details</h3> 196 <div class='detail-row'> 197 <span class='detail-label'>Username:</span> 198 <span>{$user_login}</span> 199 </div> 200 <div class='detail-row'> 201 <span class='detail-label'>IP Address:</span> 202 <span>{$user_ip}</span> 203 </div> 204 <div class='detail-row'> 205 <span class='detail-label'>Time:</span> 206 <span>{$timestamp}</span> 207 </div> 208 </div> 209 210 <div class='security-tip'> 211 <strong>⚠️ Security Warning:</strong><br> 212 Multiple failed attempts could indicate a brute force attack. Monitor your site closely. 213 </div> 214 </div> 215 <div class='footer'> 216 <p>This email was sent by Lockspire Security Plugin</p> 217 <p>Site: {$site_name} ({$site_url})</p> 218 </div> 89 219 </div> 90 91 <div class="footer"> 92 <p>This email was sent by Login Attempt Limiter plugin on <?php echo esc_html($site_name); ?></p> 93 <p>If you believe this is a false alarm, you can adjust the notification settings in the plugin dashboard.</p> 94 </div> 95 </div> 96 </body> 97 </html> 98 <?php 99 return ob_get_clean(); 100 } 101 102 function lockspire_get_today_lockout_count() { 103 global $wpdb; 104 105 // Try cache first 106 $cache_key = 'lockspire_lockout_count_today'; 107 $count = wp_cache_get($cache_key, 'lockspire_logs'); 108 109 if ($count === false) { 110 // Note: $wpdb->get_var() is the proper WordPress method for single value queries 111 // Direct database query is necessary here for statistics - no WordPress core function provides this data 112 // Query is properly prepared and cached to minimize database load 113 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery 114 $count = $wpdb->get_var($wpdb->prepare( 115 "SELECT COUNT(DISTINCT option_name) FROM `{$wpdb->options}` WHERE option_name LIKE %s AND option_value > %d", 116 'lockspire_lockout_%', 117 strtotime(gmdate('Y-m-d') . ' 23:59:59') 118 )); 119 120 // Cache for 10 minutes 121 wp_cache_set($cache_key, $count, 'lockspire_logs', 600); 220 </body> 221 </html>"; 122 222 } 123 223 124 return intval($count); 125 } 126 127 // Add cron job for daily security reports 128 add_action('wp', 'lockspire_schedule_daily_report'); 129 130 function lockspire_schedule_daily_report() { 131 if (!wp_next_scheduled('lockspire_daily_security_report')) { 132 wp_schedule_event(time(), 'daily', 'lockspire_daily_security_report'); 133 } 134 } 135 136 add_action('lockspire_daily_security_report', 'lockspire_send_daily_security_report'); 137 138 function lockspire_send_daily_security_report() { 139 if (!get_option('lockspire_daily_report', 0)) { 140 return; 141 } 142 143 $stats = lockspire_get_security_stats(); 144 $admin_email = get_option('lockspire_admin_email', get_option('admin_email')); 145 146 $subject = '📊 Daily Security Report - ' . get_bloginfo('name'); 147 148 $message = lockspire_get_daily_report_content($stats); 149 150 $headers = ['Content-Type: text/html; charset=UTF-8']; 151 152 wp_mail($admin_email, $subject, $message, $headers); 153 } 154 155 function lockspire_get_daily_report_content($stats) { 156 $site_name = get_bloginfo('name'); 157 $admin_url = admin_url('admin.php?page=lockspire'); 158 159 ob_start(); 160 ?> 161 <!DOCTYPE html> 162 <html> 163 <head> 164 <meta charset="UTF-8"> 165 <title>Daily Security Report</title> 166 <style> 167 body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; } 168 .container { max-width: 600px; margin: 0 auto; padding: 20px; } 169 .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; text-align: center; } 170 .content { padding: 20px; background: #f8f9fa; } 171 .stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin: 20px 0; } 172 .stat-card { background: white; padding: 15px; border-radius: 5px; text-align: center; } 173 .stat-number { font-size: 24px; font-weight: bold; color: #667eea; } 174 .stat-label { font-size: 12px; color: #666; text-transform: uppercase; } 175 .footer { background: #f8f9fa; padding: 20px; text-align: center; font-size: 12px; color: #666; } 176 </style> 177 </head> 178 <body> 179 <div class="container"> 180 <div class="header"> 181 <h1>📊 Daily Security Report</h1> 182 <p><?php echo esc_html($site_name); ?> - <?php echo esc_html(gmdate('Y-m-d')); ?></p> 183 </div> 184 185 <div class="content"> 186 <div class="stats-grid"> 187 <div class="stat-card"> 188 <div class="stat-number"><?php echo number_format($stats['total_attempts']); ?></div> 189 <div class="stat-label">Total Attempts</div> 190 </div> 191 <div class="stat-card"> 192 <div class="stat-number"><?php echo number_format($stats['failed_attempts']); ?></div> 193 <div class="stat-label">Failed Attempts</div> 194 </div> 195 <div class="stat-card"> 196 <div class="stat-number"><?php echo number_format($stats['locked_ips']); ?></div> 197 <div class="stat-label">Locked IPs</div> 198 </div> 199 <div class="stat-card"> 200 <div class="stat-number"><?php echo number_format($stats['blocked_ips']); ?></div> 201 <div class="stat-label">Blocked IPs</div> 202 </div> 203 </div> 204 205 <h3>🔍 Summary</h3> 206 <p>Your website's security system is actively monitoring login attempts. Here's today's overview:</p> 207 208 <?php if ($stats['failed_attempts'] > 10): ?> 209 <div style="background: #f8d7da; border: 1px solid #f5c6cb; padding: 15px; border-radius: 5px; margin: 20px 0;"> 210 <strong>⚠️ High Activity Detected:</strong> You had an unusually high number of failed login attempts today. Consider reviewing your security settings. 211 </div> 212 <?php else: ?> 213 <div style="background: #d4edda; border: 1px solid #c3e6cb; padding: 15px; border-radius: 5px; margin: 20px 0;"> 214 <strong>✅ Normal Activity:</strong> Login attempt levels are within normal ranges. 215 </div> 216 <?php endif; ?> 217 218 <p style="text-align: center; margin: 30px 0;"> 219 <a href="<?php echo esc_url($admin_url); ?>" style="display: inline-block; background: #667eea; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">View Full Dashboard</a> 220 </p> 221 </div> 222 223 <div class="footer"> 224 <p>This is an automated daily security report from Login Attempt Limiter.</p> 225 <p>To unsubscribe from these reports, visit the plugin settings.</p> 226 </div> 227 </div> 228 </body> 229 </html> 230 <?php 231 return ob_get_clean(); 232 } 224 return $template; 225 } 226 227 // Log email sent 228 function lockspire_log_email_sent($type, $recipient, $subject, $status = 'sent') { 229 $lockspire_email_logs = get_option('lockspire_email_logs', array()); 230 231 $lockspire_log_entry = array( 232 'type' => $type, 233 'recipient' => $recipient, 234 'subject' => $subject, 235 'status' => $status, 236 'time' => current_time('mysql') 237 ); 238 239 array_unshift($lockspire_email_logs, $lockspire_log_entry); 240 $lockspire_email_logs = array_slice($lockspire_email_logs, 0, 50); 241 242 update_option('lockspire_email_logs', $lockspire_email_logs); 243 } -
lockspire-smart-access-protection/trunk/includes/settings-page.php
r3455145 r3462265 1 1 <?php 2 if ( ! defined( 'ABSPATH' ) ) exit; 2 /** 3 * Settings Page for Lockspire - Documentation File 4 * 5 * Method in Lockspire_Security class: 6 * - public function settings_page() 7 * 8 * Actual implementation is in lockspire-smart-access-protection.php 9 */ 3 10 4 function lockspire_enqueue_admin_scripts($hook) { 5 if (strpos($hook, 'lockspire') !== false) { 6 // Register styles 7 wp_register_style('lockspire-admin-css', plugins_url('assets/css/admin.css', dirname(__FILE__)), [], '1.0'); 8 9 // Register scripts 10 wp_register_script('lockspire-admin-js', plugins_url('assets/js/admin.js', dirname(__FILE__)), ['jquery'], '1.0', true); 11 12 // Enqueue styles 13 wp_enqueue_style('lockspire-admin-css'); 14 15 // Enqueue scripts 16 wp_enqueue_script('lockspire-admin-js'); 17 18 // Localize script 19 wp_localize_script('lockspire-admin-js', 'lockspire_admin', [ 20 'nonce' => wp_create_nonce('lockspire_admin_actions'), 21 'ajaxurl' => admin_url('admin-ajax.php') 22 ]); 23 } 24 } 25 add_action('admin_enqueue_scripts', 'lockspire_enqueue_admin_scripts'); 26 27 function lockspire_settings_page() { 28 if (isset($_POST['submit']) && check_admin_referer('lockspire_save_settings_nonce')) { 29 lockspire_save_all_settings(); 30 echo '<div class="lal-alert lal-alert-success"><strong>Settings saved successfully!</strong> All your preferences have been updated.</div>'; 31 } 32 33 $active_tab = isset($_GET['tab']) ? sanitize_text_field(wp_unslash($_GET['tab'])) : 'general'; 34 ?> 35 <div class="wrap lal-admin-wrapper"> 36 <div class="lal-header"> 37 <h1>🛡️ Lockspire – Smart Access Protection</h1> 38 <p>Advanced security protection against brute-force attacks</p> 39 </div> 40 41 <div class="lal-nav-tabs"> 42 <button class="lal-nav-tab <?php echo $active_tab === 'general' ? 'active' : ''; ?>" onclick="window.location.href='?page=lockspire&tab=general'">⚙️ General</button> 43 <button class="lal-nav-tab <?php echo $active_tab === 'security' ? 'active' : ''; ?>" onclick="window.location.href='?page=lockspire&tab=security'">🔒 Security</button> 44 <button class="lal-nav-tab <?php echo $active_tab === 'notifications' ? 'active' : ''; ?>" onclick="window.location.href='?page=lockspire&tab=notifications'">📧 Notifications</button> 45 <button class="lal-nav-tab <?php echo $active_tab === 'dashboard' ? 'active' : ''; ?>" onclick="window.location.href='?page=lockspire&tab=dashboard'">📊 Dashboard</button> 46 <button class="lal-nav-tab <?php echo $active_tab === 'logs' ? 'active' : ''; ?>" onclick="window.location.href='?page=lockspire&tab=logs'">📋 Logs</button> 47 </div> 48 49 <div class="lal-tab-content <?php echo $active_tab === 'general' ? 'active' : ''; ?>"> 50 <?php lockspire_render_general_settings(); ?> 51 </div> 52 53 <div class="lal-tab-content <?php echo $active_tab === 'security' ? 'active' : ''; ?>"> 54 <?php lockspire_render_security_settings(); ?> 55 </div> 56 57 <div class="lal-tab-content <?php echo $active_tab === 'notifications' ? 'active' : ''; ?>"> 58 <?php lockspire_render_notification_settings(); ?> 59 </div> 60 61 <div class="lal-tab-content <?php echo $active_tab === 'dashboard' ? 'active' : ''; ?>"> 62 <?php lockspire_render_dashboard(); ?> 63 </div> 64 65 <div class="lal-tab-content <?php echo $active_tab === 'logs' ? 'active' : ''; ?>"> 66 <?php lockspire_render_logs(); ?> 67 </div> 68 </div> 69 <?php 11 // Prevent direct access 12 if (!defined('ABSPATH')) { 13 exit; 70 14 } 71 15 72 function lockspire_save_all_settings() {73 // Verify nonce74 if (!isset($_POST['_wpnonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_wpnonce'])), 'lockspire_save_settings_nonce')) {75 wp_die('Security check failed!');76 }77 78 // General settings79 if (isset($_POST['lockspire_max_attempts'])) {80 update_option('lockspire_max_attempts', intval($_POST['lockspire_max_attempts']));81 }82 if (isset($_POST['lockspire_lockout_time'])) {83 update_option('lockspire_lockout_time', intval($_POST['lockspire_lockout_time']));84 }85 if (isset($_POST['lockspire_reset_time'])) {86 update_option('lockspire_reset_time', intval($_POST['lockspire_reset_time']));87 }88 if (isset($_POST['lockspire_lockout_message'])) {89 update_option('lockspire_lockout_message', sanitize_text_field(wp_unslash($_POST['lockspire_lockout_message'])));90 }91 92 // Security settings93 if (isset($_POST['lockspire_whitelist_ips'])) {94 update_option('lockspire_whitelist_ips', sanitize_text_field(wp_unslash($_POST['lockspire_whitelist_ips'])));95 }96 if (isset($_POST['lockspire_blacklist_ips'])) {97 update_option('lockspire_blacklist_ips', sanitize_text_field(wp_unslash($_POST['lockspire_blacklist_ips'])));98 }99 if (isset($_POST['lockspire_enable_country_blocking'])) {100 update_option('lockspire_enable_country_blocking', intval($_POST['lockspire_enable_country_blocking']));101 } else {102 update_option('lockspire_enable_country_blocking', 0);103 }104 if (isset($_POST['lockspire_blocked_countries'])) {105 update_option('lockspire_blocked_countries', sanitize_text_field(wp_unslash($_POST['lockspire_blocked_countries'])));106 }107 108 // Notification settings109 if (isset($_POST['lockspire_notify_admin'])) {110 update_option('lockspire_notify_admin', intval($_POST['lockspire_notify_admin']));111 } else {112 update_option('lockspire_notify_admin', 0);113 }114 if (isset($_POST['lockspire_admin_email'])) {115 update_option('lockspire_admin_email', sanitize_email(wp_unslash($_POST['lockspire_admin_email'])));116 }117 if (isset($_POST['lockspire_notify_threshold'])) {118 update_option('lockspire_notify_threshold', intval($_POST['lockspire_notify_threshold']));119 }120 }121 122 function lockspire_render_general_settings() {123 16 ?> 124 <form method="POST"> 125 <?php wp_nonce_field('lockspire_save_settings_nonce'); ?> 126 127 <div class="lal-form-section"> 128 <h3>🎯 Basic Settings</h3> 17 <div class="lockspire-settings"> 18 <form method="post" action="options.php"> 19 <?php 20 settings_fields('lockspire_options'); 21 do_settings_sections('lockspire-settings'); 22 ?> 129 23 130 <div class="lal-form-row"> 131 <label for="lal_max_attempts">Maximum Login Attempts</label> 132 <div> 133 <input type="number" name="lockspire_max_attempts" 134 value="<?php echo esc_attr(get_option('lockspire_max_attempts', 5)); ?>" 135 min="1" max="20"> 136 <div class="description">Number of failed attempts before lockout (1-20)</div> 24 <div class="lockspire-grid"> 25 <div class="lockspire-card"> 26 <div class="lockspire-card-header"> 27 <h3>🔒 Core Security Settings</h3> 28 </div> 29 <div class="lockspire-card-body"> 30 <table class="form-table"> 31 <tr> 32 <th scope="row">Login Protection</th> 33 <td> 34 <label class="switch"> 35 <input type="checkbox" name="lockspire_options[login_limit_enabled]" value="1" <?php checked($this->options['login_limit_enabled'], '1'); ?>> 36 <span class="slider"></span> 37 </label> 38 <span class="setting-description">Limit login attempts to prevent brute force attacks</span> 39 </td> 40 </tr> 41 <tr> 42 <th scope="row">Max Login Attempts</th> 43 <td> 44 <input type="number" name="lockspire_options[max_login_attempts]" value="<?php echo esc_attr($this->options['max_login_attempts']); ?>" min="1" max="20"> 45 <p class="description">Maximum failed login attempts before lockout</p> 46 </td> 47 </tr> 48 <tr> 49 <th scope="row">Lockout Time</th> 50 <td> 51 <input type="number" name="lockspire_options[lockout_time]" value="<?php echo esc_attr($this->options['lockout_time']); ?>" min="1" max="1440"> 52 <p class="description">Lockout duration in minutes</p> 53 </td> 54 </tr> 55 </table> 56 </div> 57 </div> 58 59 <div class="lockspire-card"> 60 <div class="lockspire-card-header"> 61 <h3>🔥 Firewall Settings</h3> 62 </div> 63 <div class="lockspire-card-body"> 64 <table class="form-table"> 65 <tr> 66 <th scope="row">Enable Firewall</th> 67 <td> 68 <label class="switch"> 69 <input type="checkbox" name="lockspire_options[firewall_enabled]" value="1" <?php checked($this->options['firewall_enabled'], '1'); ?>> 70 <span class="slider"></span> 71 </label> 72 <span class="setting-description">Block malicious requests and bad bots</span> 73 </td> 74 </tr> 75 <tr> 76 <th scope="row">Disable XML-RPC</th> 77 <td> 78 <label class="switch"> 79 <input type="checkbox" name="lockspire_options[disable_xmlrpc]" value="1" <?php checked($this->options['disable_xmlrpc'], '1'); ?>> 80 <span class="slider"></span> 81 </label> 82 <span class="setting-description">Disable XML-RPC to prevent attacks</span> 83 </td> 84 </tr> 85 </table> 86 </div> 137 87 </div> 138 88 </div> 139 89 140 <div class="lal-form-row"> 141 <label for="lal_lockout_time">Lockout Duration</label> 142 <div> 143 <input type="number" name="lockspire_lockout_time" 144 value="<?php echo esc_attr(get_option('lockspire_lockout_time', 15)); ?>" 145 min="1" max="1440"> 146 <div class="description">Time in minutes to lock out user (1-1440 minutes)</div> 90 <div class="lockspire-grid"> 91 <div class="lockspire-card"> 92 <div class="lockspire-card-header"> 93 <h3>👻 Admin Protection</h3> 94 </div> 95 <div class="lockspire-card-body"> 96 <table class="form-table"> 97 <tr> 98 <th scope="row">Hide Admin URL</th> 99 <td> 100 <label class="switch"> 101 <input type="checkbox" name="lockspire_options[hide_admin_enabled]" value="1" <?php checked($this->options['hide_admin_enabled'], '1'); ?>> 102 <span class="slider"></span> 103 </label> 104 <span class="setting-description">Hide wp-admin with secret key</span> 105 </td> 106 </tr> 107 <tr> 108 <th scope="row">Admin Secret Key</th> 109 <td> 110 <input type="text" name="lockspire_options[admin_secret_key]" value="<?php echo esc_attr($this->options['admin_secret_key']); ?>" class="regular-text"> 111 <p class="description">Access wp-admin at: /wp-admin/?<?php echo esc_html($this->options['admin_secret_key']); ?>=1</p> 112 </td> 113 </tr> 114 </table> 115 </div> 116 </div> 117 118 <div class="lockspire-card"> 119 <div class="lockspire-card-header"> 120 <h3>📧 Notifications & Logging</h3> 121 </div> 122 <div class="lockspire-card-body"> 123 <table class="form-table"> 124 <tr> 125 <th scope="row">Email Notifications</th> 126 <td> 127 <label class="switch"> 128 <input type="checkbox" name="lockspire_options[email_notifications]" value="1" <?php checked($this->options['email_notifications'], '1'); ?>> 129 <span class="slider"></span> 130 </label> 131 <span class="setting-description">Send email alerts for admin logins</span> 132 </td> 133 </tr> 134 <tr> 135 <th scope="row">Activity Logging</th> 136 <td> 137 <label class="switch"> 138 <input type="checkbox" name="lockspire_options[activity_log_enabled]" value="1" <?php checked($this->options['activity_log_enabled'], '1'); ?>> 139 <span class="slider"></span> 140 </label> 141 <span class="setting-description">Log user login activity</span> 142 </td> 143 </tr> 144 </table> 145 </div> 147 146 </div> 148 147 </div> 149 148 150 <div class="lal-form-row"> 151 <label for="lal_reset_time">Reset Attempts After</label> 152 <div> 153 <input type="number" name="lockspire_reset_time" 154 value="<?php echo esc_attr(get_option('lockspire_reset_time', 60)); ?>" 155 min="1" max="1440"> 156 <div class="description">Clear failed attempts after this many minutes</div> 149 <div class="lockspire-card"> 150 <div class="lockspire-card-header"> 151 <h3>💎 Pro Features (Coming Soon)</h3> 157 152 </div> 158 </div> 159 </div> 160 161 <div class="lal-form-section"> 162 <h3>💬 User Messages</h3> 163 164 <div class="lal-form-row"> 165 <label for="lal_lockout_message">Custom Lockout Message</label> 166 <div> 167 <input type="text" name="lockspire_lockout_message" 168 value="<?php echo esc_attr(get_option('lockspire_lockout_message', 'Too many login attempts. Please try again later.'));?>" 169 class="regular-text"> 170 <div class="description">Use [timer] as placeholder for countdown timer</div> 171 </div> 172 </div> 173 </div> 174 175 <?php submit_button('Save General Settings', 'primary', 'submit'); ?> 176 </form> 177 <?php 178 } 179 180 function lockspire_render_security_settings() { 181 ?> 182 <form method="POST"> 183 <?php wp_nonce_field('lockspire_save_settings_nonce'); ?> 184 185 <div class="lal-form-section"> 186 <h3>🌐 IP Management</h3> 187 188 <div class="lal-form-row"> 189 <label for="lal_whitelist_ips">Whitelist IPs</label> 190 <div> 191 <textarea name="lockspire_whitelist_ips" rows="4" cols="50" placeholder="192.168.1.1, 10.0.0.1"><?php echo esc_textarea(get_option('lockspire_whitelist_ips', '')); ?></textarea> 192 <div class="description">Comma-separated IPs that will never be locked out</div> 153 <div class="lockspire-card-body"> 154 <div class="pro-features"> 155 <div class="pro-feature"> 156 <div class="pro-icon">🔍</div> 157 <div class="pro-info"> 158 <h4>Malware Scanner</h4> 159 <p>Advanced malware detection and removal</p> 160 <span class="pro-badge">PRO</span> 161 </div> 162 </div> 163 <div class="pro-feature"> 164 <div class="pro-icon">🚫</div> 165 <div class="pro-info"> 166 <h4>IP Address Blocking</h4> 167 <p>Advanced IP blocking and geolocation</p> 168 <span class="pro-badge">PRO</span> 169 </div> 170 </div> 171 <div class="pro-feature"> 172 <div class="pro-icon">🌍</div> 173 <div class="pro-info"> 174 <h4>Country Blocking</h4> 175 <p>Block access from specific countries</p> 176 <span class="pro-badge">PRO</span> 177 </div> 178 </div> 179 </div> 193 180 </div> 194 181 </div> 195 182 196 <div class="lal-form-row"> 197 <label for="lal_blacklist_ips">Blacklist IPs</label> 198 <div> 199 <textarea name="lockspire_blacklist_ips" rows="4" cols="50" placeholder="192.168.1.100, 10.0.0.100"><?php echo esc_textarea(get_option('lockspire_blacklist_ips', '')); ?></textarea> 200 <div class="description">Comma-separated IPs that will always be blocked</div> 201 </div> 202 </div> 203 </div> 204 205 <div class="lal-form-section"> 206 <h3>🌍 Country Blocking</h3> 207 208 <div class="lal-form-row"> 209 <label for="lal_enable_country_blocking">Enable Country Blocking</label> 210 <div> 211 <input type="checkbox" name="lockspire_enable_country_blocking" value="1" 212 <?php checked(1, get_option('lockspire_enable_country_blocking', 0)); ?> > 213 <div class="description">Block login attempts from specific countries</div> 214 </div> 215 </div> 216 217 <div class="lal-form-row"> 218 <label for="lal_blocked_countries">Blocked Countries</label> 219 <div> 220 <input type="text" name="lockspire_blocked_countries" 221 value="<?php echo esc_attr(get_option('lockspire_blocked_countries', '')); ?>" 222 placeholder="CN, RU, KP" 223 class="regular-text"> 224 <div class="description">Comma-separated 2-letter country codes (e.g., CN, RU, KP)</div> 225 </div> 226 </div> 227 </div> 228 229 <?php submit_button('Save Security Settings', 'primary', 'submit'); ?> 230 </form> 231 <?php 232 } 233 234 function lockspire_render_notification_settings() { 235 ?> 236 <form method="POST"> 237 <?php wp_nonce_field('lockspire_save_settings_nonce'); ?> 238 239 <div class="lal-form-section"> 240 <h3>📧 Email Notifications</h3> 241 242 <div class="lal-form-row"> 243 <label for="lal_notify_admin">Notify Admin on Lockout</label> 244 <div> 245 <input type="checkbox" name="lockspire_notify_admin" value="1" 246 <?php checked(1, get_option('lockspire_notify_admin', 0)); ?> > 247 <div class="description">Send email when a user gets locked out</div> 248 </div> 249 </div> 250 251 <div class="lal-form-row"> 252 <label for="lal_admin_email">Admin Email</label> 253 <div> 254 <input type="email" name="lockspire_admin_email" 255 value="<?php echo esc_attr(get_option('lockspire_admin_email', get_option('admin_email'))); ?>" 256 class="regular-text"> 257 <div class="description">Email address to receive notifications (leave blank for site admin)</div> 258 </div> 259 </div> 260 261 <div class="lal-form-row"> 262 <label for="lal_notify_threshold">Notification Threshold</label> 263 <div> 264 <input type="number" name="lockspire_notify_threshold" 265 value="<?php echo esc_attr(get_option('lockspire_notify_threshold', 3)); ?>" 266 min="1" max="50"> 267 <div class="description">Only notify after this many lockouts per day</div> 268 </div> 269 </div> 270 </div> 271 272 <?php submit_button('Save Notification Settings', 'primary', 'submit'); ?> 273 </form> 274 <?php 275 } 276 277 function lockspire_render_dashboard() { 278 $stats = lockspire_get_security_stats(); 279 ?> 280 <div class="lal-stats-grid"> 281 <div class="lal-stat-card"> 282 <div class="lal-stat-number"><?php echo number_format($stats['total_attempts']); ?></div> 283 <div class="lal-stat-label">Total Attempts Today</div> 284 </div> 285 <div class="lal-stat-card"> 286 <div class="lal-stat-number"><?php echo number_format($stats['failed_attempts']); ?></div> 287 <div class="lal-stat-label">Failed Attempts</div> 288 </div> 289 <div class="lal-stat-card"> 290 <div class="lal-stat-number"><?php echo number_format($stats['locked_ips']); ?></div> 291 <div class="lal-stat-label">Currently Locked IPs</div> 292 </div> 293 <div class="lal-stat-card"> 294 <div class="lal-stat-number"><?php echo number_format($stats['blocked_ips']); ?></div> 295 <div class="lal-stat-label">Permanently Blocked</div> 296 </div> 297 </div> 298 299 <div class="lal-form-section"> 300 <h3>📈 Security Overview</h3> 301 <p><strong>Protection Status:</strong> <span style="color: #28a745;">✅ Active</span></p> 302 <p><strong>Last Attack:</strong> <?php echo $stats['last_attack'] ? esc_html($stats['last_attack']) : 'No attacks detected'; ?></p> 303 <p><strong>Most Active IP:</strong> <?php echo $stats['most_active_ip'] ? esc_html($stats['most_active_ip']) : 'N/A'; ?></p> 304 </div> 305 306 <div class="lal-form-section"> 307 <h3>🔧 Quick Actions</h3> 308 <button type="button" class="lal-button" onclick="lockspireClearAllLockouts()">Clear All Lockouts</button> 309 <button type="button" class="lal-button lal-button-secondary" onclick="lockspireExportLogs()">Export Logs</button> 310 <button type="button" class="lal-button lal-button-danger" onclick="lockspireResetStats()">Reset Statistics</button> 183 <?php submit_button('Save Settings', 'primary', 'submit', true, array('class' => 'button-large')); ?> 184 </form> 311 185 </div> 312 186 <?php 313 } 314 315 function lockspire_render_logs() { 316 $logs = lockspire_get_recent_logs(); 317 ?> 318 <div class="lal-form-section"> 319 <h3>📋 Recent Login Activity</h3> 320 321 <?php if (!empty($logs)): ?> 322 <table class="lal-log-table"> 323 <thead> 324 <tr> 325 <th>Time</th> 326 <th>IP Address</th> 327 <th>Username</th> 328 <th>Status</th> 329 <th>Attempts</th> 330 </tr> 331 </thead> 332 <tbody> 333 <?php foreach ($logs as $log): ?> 334 <tr> 335 <td><?php echo esc_html(date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime(isset($log['created_at']) ? $log['created_at'] : ''))); ?></td> 336 <td><?php echo esc_html(isset($log['ip_address']) ? $log['ip_address'] : ''); ?></td> 337 <td><?php echo esc_html(isset($log['username']) ? $log['username'] : ''); ?></td> 338 <td> 339 <span class="lal-status-badge lal-status-<?php echo esc_attr(isset($log['status']) ? $log['status'] : ''); ?>"> 340 <?php echo esc_html(ucfirst(isset($log['status']) ? $log['status'] : '')); ?> 341 </span> 342 </td> 343 <td><?php echo esc_html(isset($log['attempts']) ? $log['attempts'] : ''); ?></td> 344 </tr> 345 <?php endforeach; ?> 346 </tbody> 347 </table> 348 <?php else: ?> 349 <p>No login activity logged yet.</p> 350 <?php endif; ?> 351 </div> 352 353 <div class="lal-form-section"> 354 <h3>🔧 Log Management</h3> 355 <button type="button" class="lal-button lal-button-secondary" onclick="lockspireTestLogWorking()">🧪 Create Test Log Entry</button> 356 <button type="button" class="lal-button lal-button-danger" onclick="lockspireClearLogs()">Clear All Logs</button> 357 <button type="button" class="lal-button lal-button-secondary" onclick="lockspireDownloadLogs()">Download Logs</button> 358 </div> 359 <?php 360 } 361 362 187 // $this->render_admin_footer(); // This would be called in the main class 188 ?> -
lockspire-smart-access-protection/trunk/lockspire-smart-access-protection.php
r3455145 r3462265 1 1 <?php 2 /* 3 Plugin Name: Lockspire –Smart Access Protection4 Description: Advanced security plugin to protect your site from brute-force attacks with real-time monitoring. 5 Version: 1.0 6 Author: Anil Ghimire 7 Author URI: https://github.com/anilatgithub 8 Text Domain: lockspire-smart-access-protection 9 License:GPL v2 or later10 License URI:https://www.gnu.org/licenses/gpl-2.0.html11 */2 /** 3 * Plugin Name: Lockspire - Smart Access Protection 4 * Plugin URI: https://lockspire.com 5 * Description: Lightweight WordPress security plugin with login protection, admin URL hiding, firewall, and more. 6 * Version: 1.0.0 7 * Author: Anil Ghimire 8 * Author URI: https://github.com/anilatgithub 9 * License: GPL v2 or later 10 * License URI: https://www.gnu.org/licenses/gpl-2.0.html 11 */ 12 12 13 if ( ! defined( 'ABSPATH' ) ) exit; 13 // Prevent direct access 14 if (!defined('ABSPATH')) { 15 exit; 16 } 14 17 15 18 // Define plugin constants 16 define( 'LOCKSPIRE_PLUGIN_PATH', plugin_dir_path( __FILE__ ) ); 17 define( 'LOCKSPIRE_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); 19 define('LOCKSPIRE_VERSION', '1.0.0'); 20 define('LOCKSPIRE_PLUGIN_DIR', plugin_dir_path(__FILE__)); 21 define('LOCKSPIRE_PLUGIN_URL', plugin_dir_url(__FILE__)); 18 22 19 // Include files (each file only once) 20 require_once LOCKSPIRE_PLUGIN_PATH . 'includes/attempt-handler.php'; 21 require_once LOCKSPIRE_PLUGIN_PATH . 'includes/settings-page.php'; 22 require_once LOCKSPIRE_PLUGIN_PATH . 'includes/ajax-handlers.php'; 23 require_once LOCKSPIRE_PLUGIN_PATH . 'includes/database.php'; 24 require_once LOCKSPIRE_PLUGIN_PATH . 'includes/notifications.php'; 25 26 // Add working test log handler 27 add_action('wp_ajax_lockspire_test_log_working', 'lockspire_test_log_working_handler'); 28 function lockspire_test_log_working_handler() { 29 check_ajax_referer('lockspire_admin_actions', 'nonce'); 30 31 if (!current_user_can('manage_options')) { 32 wp_send_json_error('Unauthorized'); 33 return; 34 } 35 36 global $wpdb; 37 $table_name = $wpdb->prefix . 'lockspire_login_logs'; 38 39 // Create table if needed 40 require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); 41 $charset_collate = $wpdb->get_charset_collate(); 42 $sql = "CREATE TABLE $table_name ( 43 id mediumint(9) NOT NULL AUTO_INCREMENT, 44 ip_address varchar(45) NOT NULL, 45 username varchar(60) NOT NULL, 46 status varchar(20) NOT NULL, 47 attempts int(3) NOT NULL DEFAULT 1, 48 user_agent text, 49 created_at datetime DEFAULT CURRENT_TIMESTAMP, 50 PRIMARY KEY (id) 51 ) $charset_collate;"; 52 dbDelta($sql); 53 54 // Insert test entries 55 $test_entries = [ 56 ['username' => 'test_user_1', 'status' => 'failed', 'attempts' => 1], 57 ['username' => 'test_user_2', 'status' => 'failed', 'attempts' => 2], 58 ['username' => 'test_user_3', 'status' => 'failed', 'attempts' => 3], 59 ['username' => 'test_user_4', 'status' => 'locked', 'attempts' => 5], 60 ['username' => 'admin_user', 'status' => 'success', 'attempts' => 1] 61 ]; 62 63 $created_count = 0; 64 foreach ($test_entries as $entry) { 65 $data = [ 66 'ip_address' => '127.0.0.1', 67 'username' => sanitize_text_field($entry['username']), 68 'status' => sanitize_text_field($entry['status']), 69 'attempts' => intval($entry['attempts']), 70 'user_agent' => 'Test Entry', 71 'created_at' => current_time('mysql') 72 ]; 73 74 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery 75 $result = $wpdb->insert($table_name, $data); 76 if ($result !== false) { 77 $created_count++; 78 } 79 } 80 81 if ($created_count > 0) { 82 wp_send_json_success("Created {$created_count} test log entries successfully"); 83 } else { 84 wp_send_json_error('Failed to create test log entries'); 23 class Lockspire_Security { 24 25 private $options; 26 private $login_attempts_key = 'lockspire_login_attempts'; 27 private $activity_log_key = 'lockspire_activity_log'; 28 29 public function __construct() { 30 add_action('init', array($this, 'init')); 31 add_action('admin_menu', array($this, 'add_admin_menu')); 32 add_action('admin_init', array($this, 'admin_init')); 33 add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts')); 34 35 // Register AJAX handlers 36 add_action('wp_ajax_lockspire_toggle_email_notifications', array($this, 'ajax_toggle_email_notifications')); 37 add_action('wp_ajax_lockspire_save_email_settings', array($this, 'ajax_save_email_settings')); 38 add_action('wp_ajax_lockspire_test_email', array($this, 'ajax_test_email')); 39 add_action('wp_ajax_lockspire_clear_email_logs', array($this, 'ajax_clear_email_logs')); 40 add_action('wp_ajax_lockspire_toggle_login_protection', array($this, 'ajax_toggle_login_protection')); 41 add_action('wp_ajax_lockspire_save_login_settings', array($this, 'ajax_save_login_settings')); 42 add_action('wp_ajax_lockspire_unlock_ip', array($this, 'ajax_unlock_ip')); 43 add_action('wp_ajax_lockspire_unlock_all_ips', array($this, 'ajax_unlock_all_ips')); 44 45 register_activation_hook(__FILE__, array($this, 'activate')); 46 register_deactivation_hook(__FILE__, array($this, 'deactivate')); 47 } 48 49 public function init() { 50 $this->options = get_option('lockspire_options', array()); 51 52 // Initialize default settings 53 $this->set_default_options(); 54 55 // Load security features 56 $this->load_security_features(); 57 58 // Add custom login error message filter 59 add_filter('login_errors', array($this, 'custom_login_error_message')); 60 } 61 62 private function set_default_options() { 63 $defaults = array( 64 'login_limit_enabled' => '1', 65 'max_login_attempts' => '5', 66 'lockout_time' => '15', 67 'hide_admin_enabled' => '0', 68 'admin_secret_key' => 'myadmin', 69 'disable_xmlrpc' => '1', 70 'firewall_enabled' => '1', 71 'email_notifications' => '1', 72 'activity_log_enabled' => '1' 73 ); 74 75 $this->options = wp_parse_args($this->options, $defaults); 76 update_option('lockspire_options', $this->options); 77 } 78 79 private function load_security_features() { 80 // Login attempt limiting 81 if ($this->options['login_limit_enabled']) { 82 add_action('wp_login_failed', array($this, 'handle_login_failed')); 83 add_action('wp_authenticate', array($this, 'check_login_attempts'), 1); 84 } 85 86 // Hide wp-admin URL 87 if ($this->options['hide_admin_enabled']) { 88 add_action('init', array($this, 'handle_admin_url_hiding')); 89 } 90 91 // Disable XML-RPC 92 if ($this->options['disable_xmlrpc']) { 93 add_filter('xmlrpc_enabled', '__return_false'); 94 add_filter('xmlrpc_methods', array($this, 'disable_xmlrpc_methods')); 95 } 96 97 // Basic firewall 98 if ($this->options['firewall_enabled']) { 99 add_action('init', array($this, 'init_firewall')); 100 } 101 102 // Admin login notifications 103 if ($this->options['email_notifications']) { 104 add_action('wp_login', array($this, 'send_admin_login_notification'), 10, 2); 105 } 106 107 // Activity logging 108 if ($this->options['activity_log_enabled']) { 109 add_action('wp_login', array($this, 'log_login_activity'), 10, 2); 110 } 111 } 112 113 // Login attempt limiting 114 public function handle_login_failed($username) { 115 $ip = $this->get_user_ip(); 116 $attempts = get_transient($this->login_attempts_key . '_' . $ip); 117 118 if (!$attempts) { 119 $attempts = array(); 120 } 121 122 $attempts[] = array( 123 'username' => $username, 124 'time' => current_time('timestamp') 125 ); 126 127 set_transient( 128 $this->login_attempts_key . '_' . $ip, 129 $attempts, 130 intval($this->options['lockout_time']) * 60 131 ); 132 133 // Send failed login notification 134 $this->send_failed_login_notification($username, $ip); 135 } 136 137 // Custom login error messages 138 public function custom_login_error_message($error) { 139 global $errors; 140 141 // Check if this is a login attempt error 142 if ($errors && isset($errors->errors['incorrect_password'])) { 143 $ip = $this->get_user_ip(); 144 $attempts = get_transient($this->login_attempts_key . '_' . $ip); 145 146 if ($attempts) { 147 $max_attempts = intval($this->options['max_login_attempts']); 148 $current_attempt = count($attempts); 149 $remaining_attempts = $max_attempts - $current_attempt; 150 151 if ($remaining_attempts > 0) { 152 $lockout_time = intval($this->options['lockout_time']) * 60; 153 $first_attempt_time = $attempts[0]['time']; 154 $time_until_reset = ($first_attempt_time + $lockout_time) - current_time('timestamp'); 155 156 if ($time_until_reset > 0) { 157 $minutes = floor($time_until_reset / 60); 158 $seconds = $time_until_reset % 60; 159 160 /* translators: %1$d: current attempt number, %2$d: max attempts, %3$d: remaining attempts, %4$d: minutes, %5$d: seconds, %6$d: max attempts, %7$d: lockout time in minutes */ 161 $custom_error = sprintf( 162 __('❌ <strong>Invalid password!</strong><br>This is attempt <strong>%1$d</strong> of <strong>%2$d</strong>.<br>You have <strong>%3$d</strong> login attempts left.<br>Attempts reset in <strong>%4$d minutes %5$d seconds</strong>.<br><small>Maximum allowed: %6$d attempts every %7$d minutes</small>', 'lockspire-security'), 163 $current_attempt, 164 $max_attempts, 165 $remaining_attempts, 166 $minutes, 167 $seconds, 168 $max_attempts, 169 intval($this->options['lockout_time']) 170 ); 171 172 return '<div class="login-error" style="background: #ffebee; color: #c62828; padding: 15px; border-radius: 8px; margin: 10px 0; border-left: 4px solid #f44336;">' . $custom_error . '</div>'; 173 } else { 174 /* translators: %1$d: current attempt number, %2$d: max attempts, %3$d: remaining attempts, %4$d: max attempts, %5$d: lockout time in minutes */ 175 $custom_error = sprintf( 176 __('❌ <strong>Invalid password!</strong><br>This is attempt <strong>%1$d</strong> of <strong>%2$d</strong>.<br>You have <strong>%3$d</strong> login attempts left.<br><small>Maximum allowed: %4$d attempts every %5$d minutes</small>', 'lockspire-security'), 177 $current_attempt, 178 $max_attempts, 179 $remaining_attempts, 180 $max_attempts, 181 intval($this->options['lockout_time']) 182 ); 183 184 return '<div class="login-error" style="background: #ffebee; color: #c62828; padding: 15px; border-radius: 8px; margin: 10px 0; border-left: 4px solid #f44336;">' . $custom_error . '</div>'; 185 } 186 } 187 } 188 } 189 190 // Return styled default error for other cases 191 return '<div class="login-error" style="background: #ffebee; color: #c62828; padding: 15px; border-radius: 8px; margin: 10px 0; border-left: 4px solid #f44336;">' . $error . '</div>'; 192 } 193 194 public function check_login_attempts($username) { 195 $ip = $this->get_user_ip(); 196 $attempts = get_transient($this->login_attempts_key . '_' . $ip); 197 198 if ($attempts && count($attempts) >= intval($this->options['max_login_attempts'])) { 199 // Calculate remaining lockout time 200 $lockout_time = intval($this->options['lockout_time']) * 60; 201 $first_attempt_time = $attempts[0]['time']; 202 $remaining_time = ($first_attempt_time + $lockout_time) - current_time('timestamp'); 203 204 if ($remaining_time > 0) { 205 $minutes = floor($remaining_time / 60); 206 $seconds = $remaining_time % 60; 207 $max_attempts = intval($this->options['max_login_attempts']); 208 209 /* translators: %1$d: current attempts, %2$d: max attempts, %3$d: minutes, %4$d: seconds, %5$d: max attempts, %6$d: lockout time in minutes */ 210 $error_message = sprintf( 211 __('🚫 <strong>Too many login attempts!</strong><br>You have reached <strong>%1$d</strong> of <strong>%2$d</strong> allowed attempts.<br>You have <strong>0</strong> login attempts left.<br>Please try again after <strong>%3$d minutes %4$d seconds</strong>.<br><small>Maximum allowed: %5$d attempts every %6$d minutes</small>', 'lockspire-security'), 212 $max_attempts, 213 $max_attempts, 214 $minutes, 215 $seconds, 216 $max_attempts, 217 intval($this->options['lockout_time']) 218 ); 219 220 wp_die(wp_kses_post($error_message), 'Login Blocked', array('response' => 429)); 221 } else { 222 // Lockout expired, clear attempts 223 delete_transient($this->login_attempts_key . '_' . $ip); 224 } 225 } 226 227 return $username; 228 } 229 230 // Hide wp-admin URL 231 public function handle_admin_url_hiding() { 232 if (is_admin() && !defined('DOING_AJAX')) { 233 $secret_key = $this->options['admin_secret_key']; 234 $request_uri = sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'] ?? '')); 235 236 // Check if accessing wp-admin without secret key 237 if (strpos($request_uri, '/wp-admin/') !== false && 238 strpos($request_uri, $secret_key) === false) { 239 wp_safe_redirect(home_url()); 240 exit; 241 } 242 } 243 } 244 245 // Disable XML-RPC methods 246 public function disable_xmlrpc_methods($methods) { 247 return array(); 248 } 249 250 // Basic firewall 251 public function init_firewall() { 252 $this->block_bad_bots(); 253 $this->prevent_common_exploits(); 254 } 255 256 private function block_bad_bots() { 257 $bad_bots = array( 258 'bot', 259 'crawler', 260 'spider', 261 'scraper' 262 ); 263 264 $user_agent = sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'] ?? '')); 265 266 foreach ($bad_bots as $bot) { 267 if (stripos($user_agent, $bot) !== false) { 268 $this->block_ip(); 269 break; 270 } 271 } 272 } 273 274 private function prevent_common_exploits() { 275 $request_uri = sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'] ?? '')); 276 $query_string = sanitize_text_field(wp_unslash($_SERVER['QUERY_STRING'] ?? '')); 277 278 // Block common exploit patterns 279 $exploit_patterns = array( 280 'eval(', 281 'base64_', 282 'javascript:', 283 'vbscript:', 284 'onload=', 285 'onerror=', 286 'alert(', 287 'document.cookie', 288 '../', 289 '..\\', 290 'union select', 291 'drop table', 292 'insert into', 293 'delete from' 294 ); 295 296 foreach ($exploit_patterns as $pattern) { 297 if (stripos($request_uri, $pattern) !== false || 298 stripos($query_string, $pattern) !== false) { 299 $this->block_ip(); 300 break; 301 } 302 } 303 } 304 305 private function block_ip() { 306 wp_die(wp_kses_post(__('Access denied. Your IP has been blocked.', 'lockspire-security'))); 307 } 308 309 // Admin login notifications 310 public function send_admin_login_notification($user_login, $user) { 311 if ($this->options['email_notifications']) { 312 // Send for all logins, not just admin 313 $subject = sprintf('[%s] Security Alert: New Login Detected', get_bloginfo('name')); 314 315 $message = $this->get_email_template($user_login, $user, 'login'); 316 317 // Send to admin email 318 $admin_email = get_option('admin_email'); 319 $headers = array('Content-Type: text/html; charset=UTF-8'); 320 321 $result = wp_mail($admin_email, $subject, $message, $headers); 322 $this->log_email_sent('login', $admin_email, $subject, $result ? 'sent' : 'failed'); 323 324 // Also send to custom notification emails if set 325 $notification_emails = get_option('lockspire_notification_emails', ''); 326 if (!empty($notification_emails)) { 327 $emails = array_map('trim', explode(',', $notification_emails)); 328 foreach ($emails as $email) { 329 if (is_email($email)) { 330 $result = wp_mail($email, $subject, $message, $headers); 331 $this->log_email_sent('login', $email, $subject, $result ? 'sent' : 'failed'); 332 } 333 } 334 } 335 } 336 } 337 338 // Failed login notification 339 public function send_failed_login_notification($username, $ip) { 340 if ($this->options['email_notifications']) { 341 $subject = sprintf('[%s] 🚨 Security Alert: Failed Login Attempt', get_bloginfo('name')); 342 343 $message = $this->get_email_template($username, null, 'failed_login', $ip); 344 345 $admin_email = get_option('admin_email'); 346 $headers = array('Content-Type: text/html; charset=UTF-8'); 347 348 $result = wp_mail($admin_email, $subject, $message, $headers); 349 $this->log_email_sent('failed_login', $admin_email, $subject, $result ? 'sent' : 'failed'); 350 351 // Also send to custom notification emails 352 $notification_emails = get_option('lockspire_notification_emails', ''); 353 if (!empty($notification_emails)) { 354 $emails = array_map('trim', explode(',', $notification_emails)); 355 foreach ($emails as $email) { 356 if (is_email($email)) { 357 $result = wp_mail($email, $subject, $message, $headers); 358 $this->log_email_sent('failed_login', $email, $subject, $result ? 'sent' : 'failed'); 359 } 360 } 361 } 362 } 363 } 364 365 // Email template generator 366 private function get_email_template($user_login, $user, $type, $ip = '') { 367 $site_url = get_home_url(); 368 $site_name = get_bloginfo('name'); 369 $timestamp = current_time('mysql'); 370 $user_ip = $ip ?: $this->get_user_ip(); 371 372 if ($type === 'login') { 373 $user_role = !empty($user->roles) ? implode(', ', $user->roles) : 'Unknown'; 374 $user_email = !empty($user->user_email) ? $user->user_email : 'Not available'; 375 376 $template = " 377 <!DOCTYPE html> 378 <html> 379 <head> 380 <meta charset='UTF-8'> 381 <title>Security Alert - Login Detected</title> 382 <style> 383 body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: #333; } 384 .container { max-width: 600px; margin: 0 auto; padding: 20px; } 385 .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; text-align: center; border-radius: 10px 10px 0 0; } 386 .content { background: white; padding: 30px; border: 1px solid #e0e0e0; border-top: none; } 387 .footer { background: #f8f9fa; padding: 20px; text-align: center; border: 1px solid #e0e0e0; border-top: none; border-radius: 0 0 10px 10px; font-size: 12px; color: #666; } 388 .logo { font-size: 32px; margin-bottom: 10px; } 389 .alert-info { background: #e3f2fd; padding: 15px; border-left: 4px solid #2196f3; margin: 20px 0; border-radius: 4px; } 390 .user-details { background: #f8f9fa; padding: 15px; margin: 20px 0; border-radius: 4px; } 391 .detail-row { display: flex; justify-content: space-between; margin: 8px 0; } 392 .detail-label { font-weight: bold; color: #555; } 393 .security-tip { background: #fff3cd; padding: 15px; border-left: 4px solid #ffc107; margin: 20px 0; border-radius: 4px; } 394 .btn { display: inline-block; padding: 12px 24px; background: #667eea; color: white; text-decoration: none; border-radius: 6px; margin: 20px 0; } 395 </style> 396 </head> 397 <body> 398 <div class='container'> 399 <div class='header'> 400 <div class='logo'>🛡️</div> 401 <h1>Security Alert</h1> 402 <p>Login Detected on Your Website</p> 403 </div> 404 <div class='content'> 405 <div class='alert-info'> 406 <strong>✅ Successful Login Detected</strong><br> 407 A user has successfully logged into your WordPress site. 408 </div> 409 410 <div class='user-details'> 411 <h3>👤 User Information</h3> 412 <div class='detail-row'> 413 <span class='detail-label'>Username:</span> 414 <span>{$user_login}</span> 415 </div> 416 <div class='detail-row'> 417 <span class='detail-label'>Role:</span> 418 <span>{$user_role}</span> 419 </div> 420 <div class='detail-row'> 421 <span class='detail-label'>Email:</span> 422 <span>{$user_email}</span> 423 </div> 424 <div class='detail-row'> 425 <span class='detail-label'>IP Address:</span> 426 <span>{$user_ip}</span> 427 </div> 428 <div class='detail-row'> 429 <span class='detail-label'>Time:</span> 430 <span>{$timestamp}</span> 431 </div> 432 </div> 433 434 <div class='security-tip'> 435 <strong>🔒 Security Tip:</strong><br> 436 If this wasn't you, please secure your account immediately by changing your password. 437 </div> 438 439 <center> 440 <a href='{$site_url}/wp-admin/' class='btn'>Go to Dashboard</a> 441 </center> 442 </div> 443 <div class='footer'> 444 <p>This email was sent by Lockspire Security Plugin</p> 445 <p>Site: {$site_name} ({$site_url})</p> 446 </div> 447 </div> 448 </body> 449 </html>"; 450 451 } elseif ($type === 'failed_login') { 452 $template = " 453 <!DOCTYPE html> 454 <html> 455 <head> 456 <meta charset='UTF-8'> 457 <title>Security Alert - Failed Login Attempt</title> 458 <style> 459 body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: #333; } 460 .container { max-width: 600px; margin: 0 auto; padding: 20px; } 461 .header { background: linear-gradient(135deg, #f44336 0%, #e91e63 100%); color: white; padding: 30px; text-align: center; border-radius: 10px 10px 0 0; } 462 .content { background: white; padding: 30px; border: 1px solid #e0e0e0; border-top: none; } 463 .footer { background: #f8f9fa; padding: 20px; text-align: center; border: 1px solid #e0e0e0; border-top: none; border-radius: 0 0 10px 10px; font-size: 12px; color: #666; } 464 .logo { font-size: 32px; margin-bottom: 10px; } 465 .alert-danger { background: #ffebee; padding: 15px; border-left: 4px solid #f44336; margin: 20px 0; border-radius: 4px; } 466 .user-details { background: #f8f9fa; padding: 15px; margin: 20px 0; border-radius: 4px; } 467 .detail-row { display: flex; justify-content: space-between; margin: 8px 0; } 468 .detail-label { font-weight: bold; color: #555; } 469 .security-tip { background: #fff3cd; padding: 15px; border-left: 4px solid #ffc107; margin: 20px 0; border-radius: 4px; } 470 .btn { display: inline-block; padding: 12px 24px; background: #f44336; color: white; text-decoration: none; border-radius: 6px; margin: 20px 0; } 471 </style> 472 </head> 473 <body> 474 <div class='container'> 475 <div class='header'> 476 <h1>🚨 Security Alert</h1> 477 <p>Failed Login Attempt Detected</p> 478 </div> 479 <div class='content'> 480 <div class='alert-danger'> 481 <strong>❌ Failed Login Attempt Detected</strong><br> 482 Someone tried to log into your WordPress site but failed. 483 </div> 484 485 <div class='user-details'> 486 <h3>🔍 Attempt Details</h3> 487 <div class='detail-row'> 488 <span class='detail-label'>Username:</span> 489 <span>{$user_login}</span> 490 </div> 491 <div class='detail-row'> 492 <span class='detail-label'>IP Address:</span> 493 <span>{$user_ip}</span> 494 </div> 495 <div class='detail-row'> 496 <span class='detail-label'>Time:</span> 497 <span>{$timestamp}</span> 498 </div> 499 </div> 500 501 <div class='security-tip'> 502 <strong>⚠️ Security Warning:</strong><br> 503 Multiple failed attempts could indicate a brute force attack. Monitor your site closely. 504 </div> 505 506 <center> 507 <a href='{$site_url}/wp-admin/' class='btn'>Review Security</a> 508 </center> 509 </div> 510 511 <div class='footer'> 512 <p>This email was sent by Lockspire Security Plugin</p> 513 <p>Site: {$site_name} ({$site_url})</p> 514 </div> 515 </div> 516 </body> 517 </html>"; 518 } 519 520 return $template; 521 } 522 523 // Activity logging 524 public function log_login_activity($user_login, $user) { 525 $log_entry = array( 526 'user_login' => $user_login, 527 'user_role' => implode(', ', $user->roles), 528 'ip' => $this->get_user_ip(), 529 'time' => current_time('mysql'), 530 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown' 531 ); 532 533 $activity_log = get_option($this->activity_log_key, array()); 534 array_unshift($activity_log, $log_entry); 535 536 // Keep only last 10 entries 537 $activity_log = array_slice($activity_log, 0, 10); 538 539 update_option($this->activity_log_key, $activity_log); 540 } 541 542 // Helper functions 543 private function get_user_ip() { 544 $ip = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'] ?? '')); 545 546 if (!empty($_SERVER['HTTP_CLIENT_IP'])) { 547 $ip = sanitize_text_field(wp_unslash($_SERVER['HTTP_CLIENT_IP'])); 548 } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { 549 $ip = sanitize_text_field(wp_unslash($_SERVER['HTTP_X_FORWARDED_FOR'])); 550 } 551 552 return sanitize_text_field($ip); 553 } 554 555 // Admin menu 556 public function add_admin_menu() { 557 add_menu_page( 558 'Lockspire Security', 559 'Lockspire Security', 560 'manage_options', 561 'lockspire-dashboard', 562 array($this, 'dashboard_page'), 563 'dashicons-shield-alt', 564 65 565 ); 566 567 add_submenu_page( 568 'lockspire-dashboard', 569 'Dashboard', 570 'Dashboard', 571 'manage_options', 572 'lockspire-dashboard', 573 array($this, 'dashboard_page') 574 ); 575 576 add_submenu_page( 577 'lockspire-dashboard', 578 'Login Protection', 579 'Login Protection', 580 'manage_options', 581 'lockspire-login', 582 array($this, 'login_page') 583 ); 584 585 add_submenu_page( 586 'lockspire-dashboard', 587 'Email Notifications', 588 'Email Notifications', 589 'manage_options', 590 'lockspire-email', 591 array($this, 'email_page') 592 ); 593 594 add_submenu_page( 595 'lockspire-dashboard', 596 'Activity Log', 597 'Activity Log', 598 'manage_options', 599 'lockspire-activity', 600 array($this, 'activity_page') 601 ); 602 603 add_submenu_page( 604 'lockspire-dashboard', 605 'Settings', 606 'Settings', 607 'manage_options', 608 'lockspire-settings', 609 array($this, 'settings_page') 610 ); 611 } 612 613 // Admin settings 614 public function admin_init() { 615 register_setting('lockspire_options', 'lockspire_options', array($this, 'validate_options')); 616 617 add_settings_section( 618 'lockspire_main_section', 619 __('Security Settings', 'lockspire-security'), 620 array($this, 'section_callback'), 621 'lockspire-settings' 622 ); 623 624 // Core features 625 $this->add_field('login_limit_enabled', __('Limit Login Attempts', 'lockspire-security'), 'checkbox'); 626 $this->add_field('max_login_attempts', __('Max Login Attempts', 'lockspire-security'), 'number'); 627 $this->add_field('lockout_time', __('Lockout Time (minutes)', 'lockspire-security'), 'number'); 628 $this->add_field('hide_admin_enabled', __('Hide wp-admin URL', 'lockspire-security'), 'checkbox'); 629 $this->add_field('admin_secret_key', __('Admin Secret Key', 'lockspire-security'), 'text'); 630 $this->add_field('disable_xmlrpc', __('Disable XML-RPC', 'lockspire-security'), 'checkbox'); 631 $this->add_field('firewall_enabled', __('Enable Basic Firewall', 'lockspire-security'), 'checkbox'); 632 $this->add_field('email_notifications', __('Admin Login Notifications', 'lockspire-security'), 'checkbox'); 633 $this->add_field('activity_log_enabled', __('Activity Logging', 'lockspire-security'), 'checkbox'); 634 } 635 636 private function add_field($name, $title, $type) { 637 add_settings_field( 638 $name, 639 $title, 640 array($this, 'field_callback'), 641 'lockspire-settings', 642 'lockspire_main_section', 643 array('name' => $name, 'type' => $type) 644 ); 645 } 646 647 public function section_callback() { 648 echo '<p>' . wp_kses_post(__('Configure your WordPress security settings below. Enable features as needed for your site.', 'lockspire-security')) . '</p>'; 649 } 650 651 public function field_callback($args) { 652 $name = $args['name']; 653 $type = $args['type']; 654 $value = $this->options[$name] ?? ''; 655 656 switch ($type) { 657 case 'checkbox': 658 echo '<input type="checkbox" name="lockspire_options[' . esc_attr($name) . ']" value="1" ' . checked($value, '1', false) . ' />'; 659 break; 660 case 'number': 661 echo '<input type="number" name="lockspire_options[' . esc_attr($name) . ']" value="' . esc_attr($value) . '" min="1" max="999" />'; 662 break; 663 case 'text': 664 echo '<input type="text" name="lockspire_options[' . esc_attr($name) . ']" value="' . esc_attr($value) . '" class="regular-text" />'; 665 break; 666 } 667 668 // Add help text for specific fields 669 if ($name === 'admin_secret_key') { 670 echo '<p class="description">' . wp_kses_post(sprintf(__('Access wp-admin at: /wp-admin/?%s=1', 'lockspire-security'), esc_html($value))) . '</p>'; 671 } elseif ($name === 'max_login_attempts') { 672 echo '<p class="description">' . wp_kses_post(__('Maximum failed login attempts before lockout.', 'lockspire-security')) . '</p>'; 673 } elseif ($name === 'lockout_time') { 674 echo '<p class="description">' . wp_kses_post(__('How long to lock out after too many attempts.', 'lockspire-security')) . '</p>'; 675 } 676 } 677 678 public function validate_options($input) { 679 $validated = array(); 680 681 // Sanitize checkbox values 682 $checkbox_fields = array('login_limit_enabled', 'hide_admin_enabled', 'disable_xmlrpc', 'firewall_enabled', 'email_notifications', 'activity_log_enabled'); 683 foreach ($checkbox_fields as $field) { 684 $validated[$field] = isset($input[$field]) ? '1' : '0'; 685 } 686 687 // Sanitize number fields 688 $validated['max_login_attempts'] = isset($input['max_login_attempts']) ? absint($input['max_login_attempts']) : '5'; 689 $validated['lockout_time'] = isset($input['lockout_time']) ? absint($input['lockout_time']) : '15'; 690 691 // Sanitize text fields 692 $validated['admin_secret_key'] = isset($input['admin_secret_key']) ? sanitize_text_field($input['admin_secret_key']) : 'myadmin'; 693 694 return $validated; 695 } 696 697 // Dashboard page 698 public function dashboard_page() { 699 $this->render_admin_header('Security Dashboard'); 700 701 $security_score = $this->calculate_security_score(); 702 $security_status = $this->get_security_status(); 703 $stats = array( 704 'today_logins' => $this->get_today_login_attempts(), 705 'blocked_ips' => count(get_option('lockspire_blocked_ips', array())), 706 'active_threats' => $this->get_active_threats(), 707 'recent_logins' => count($this->get_recent_logins(24)) 708 ); 709 710 ?> 711 <div class="lockspire-dashboard"> 712 <!-- Hero Section with Animated Background --> 713 <div class="hero-section"> 714 <div class="hero-content"> 715 <div class="hero-title"> 716 <div class="title-icon">🛡️</div> 717 <h1>Security Command Center</h1> 718 <p class="hero-subtitle">Advanced WordPress Protection at Your Fingertips</p> 719 </div> 720 <div class="hero-stats"> 721 <div class="hero-stat"> 722 <div class="hero-number"><?php echo esc_html($security_score); ?></div> 723 <div class="hero-label">Security Score</div> 724 </div> 725 <div class="hero-stat"> 726 <div class="hero-number"><?php echo esc_html($stats['today_logins']); ?></div> 727 <div class="hero-label">Today's Attempts</div> 728 </div> 729 <div class="hero-stat"> 730 <div class="hero-number"><?php echo esc_html($stats['blocked_ips']); ?></div> 731 <div class="hero-label">Blocked IPs</div> 732 </div> 733 </div> 734 </div> 735 <div class="hero-particles"></div> 736 </div> 737 738 <!-- Main Dashboard Grid --> 739 <div class="dashboard-grid-modern"> 740 <!-- Live Statistics Card --> 741 <div class="modern-card stats-card-modern"> 742 <div class="card-header"> 743 <div class="header-icon">📊</div> 744 <h3>Live Statistics</h3> 745 <div class="live-indicator"> 746 <span class="live-dot"></span> 747 <span>LIVE</span> 748 </div> 749 </div> 750 <div class="card-content"> 751 <div class="stats-grid-modern"> 752 <div class="stat-modern"> 753 <div class="stat-icon-modern login-icon"> 754 <div class="icon-pulse"></div> 755 🔐 756 </div> 757 <div class="stat-content-modern"> 758 <div class="stat-value"><?php echo esc_html($stats['today_logins']); ?></div> 759 <div class="stat-name">Login Attempts</div> 760 <div class="stat-change positive">+12% from yesterday</div> 761 </div> 762 </div> 763 <div class="stat-modern"> 764 <div class="stat-icon-modern blocked-icon"> 765 <div class="icon-pulse"></div> 766 🚫 767 </div> 768 <div class="stat-content-modern"> 769 <div class="stat-value"><?php echo esc_html($stats['blocked_ips']); ?></div> 770 <div class="stat-name">Blocked IPs</div> 771 <div class="stat-change neutral">No change</div> 772 </div> 773 </div> 774 <div class="stat-modern"> 775 <div class="stat-icon-modern threat-icon"> 776 <div class="icon-pulse warning"></div> 777 ⚠️ 778 </div> 779 <div class="stat-content-modern"> 780 <div class="stat-value"><?php echo esc_html($stats['active_threats']); ?></div> 781 <div class="stat-name">Active Threats</div> 782 <div class="stat-change negative">-5% from yesterday</div> 783 </div> 784 </div> 785 <div class="stat-modern"> 786 <div class="stat-icon-modern success-icon"> 787 <div class="icon-pulse success"></div> 788 ✅ 789 </div> 790 <div class="stat-content-modern"> 791 <div class="stat-value"><?php echo esc_html($stats['recent_logins']); ?></div> 792 <div class="stat-name">Successful Logins</div> 793 <div class="stat-change positive">+8% from yesterday</div> 794 </div> 795 </div> 796 </div> 797 </div> 798 </div> 799 800 <!-- Quick Actions Card --> 801 <div class="modern-card actions-card"> 802 <div class="card-header"> 803 <div class="header-icon">⚡</div> 804 <h3>Quick Actions</h3> 805 </div> 806 <div class="card-content"> 807 <div class="actions-grid"> 808 <button onclick="location.href='?page=lockspire-activity'" class="action-card-modern"> 809 <div class="action-icon">📝</div> 810 <div class="action-content"> 811 <h4>View Activity</h4> 812 <p>Check security logs</p> 813 </div> 814 </button> 815 <button onclick="location.href='?page=lockspire-login'" class="action-card-modern"> 816 <div class="action-icon">🚪</div> 817 <div class="action-content"> 818 <h4>Login Settings</h4> 819 <p>Configure protection</p> 820 </div> 821 </button> 822 <button onclick="location.href='?page=lockspire-email'" class="action-card-modern"> 823 <div class="action-icon">📧</div> 824 <div class="action-content"> 825 <h4>Email Settings</h4> 826 <p>Setup notifications</p> 827 </div> 828 </button> 829 </div> 830 </div> 831 </div> 832 </div> 833 </div> 834 835 <style> 836 /* Clean Professional Dashboard Styles */ 837 body { 838 background: linear-gradient(135deg, #fafbfc 0%, #f3f4f6 100%); 839 min-height: 100vh; 840 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; 841 } 842 843 .lockspire-dashboard { 844 padding: 25px; 845 max-width: 1300px; 846 margin: 0 auto; 847 } 848 849 /* Hero Section */ 850 .hero-section { 851 background: linear-gradient(135deg, #1e3a8a 0%, #2d3748 50%, #3b82f6 100%); 852 border-radius: 18px; 853 padding: 45px 35px; 854 margin-bottom: 35px; 855 position: relative; 856 overflow: hidden; 857 box-shadow: 0 18px 60px rgba(30, 41, 59, 0.09); 858 } 859 860 .hero-content { 861 display: flex; 862 justify-content: space-between; 863 align-items: center; 864 position: relative; 865 z-index: 2; 866 gap: 40px; 867 } 868 869 .hero-title { 870 text-align: left; 871 flex: 1; 872 } 873 874 .title-icon { 875 font-size: 42px; 876 margin-bottom: 18px; 877 display: block; 878 filter: drop-shadow(0 3px 7px rgba(0, 0, 0, 0.3)); 879 animation: float 4s ease-in-out infinite; 880 } 881 882 .hero-title h1 { 883 margin: 0 0 18px 0; 884 font-size: 36px; 885 font-weight: 300; 886 color: white; 887 text-shadow: 0 2px 3px rgba(0, 0, 0, 0.2); 888 letter-spacing: -0.5px; 889 line-height: 1.1; 890 } 891 892 .hero-subtitle { 893 margin: 0; 894 font-size: 15px; 895 color: rgba(255, 255, 255, 0.85); 896 font-weight: 400; 897 letter-spacing: 0.2px; 898 line-height: 1.4; 899 } 900 901 .hero-stats { 902 display: flex; 903 gap: 25px; 904 flex-wrap: wrap; 905 justify-content: center; 906 } 907 908 .hero-stat { 909 text-align: center; 910 background: rgba(255, 255, 255, 0.12); 911 padding: 25px; 912 border-radius: 14px; 913 backdrop-filter: blur(18px); 914 border: 1px solid rgba(255, 255, 255, 0.2); 915 min-width: 150px; 916 transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); 917 } 918 919 .hero-stat:hover { 920 transform: translateY(-2px); 921 box-shadow: 0 12px 30px rgba(30, 41, 59, 0.15); 922 border-color: rgba(255, 255, 255, 0.4); 923 } 924 925 .hero-number { 926 font-size: 28px; 927 font-weight: 700; 928 color: white; 929 margin-bottom: 5px; 930 text-shadow: 0 2px 3px rgba(0, 0, 0, 0.3); 931 } 932 933 .hero-label { 934 font-size: 11px; 935 color: rgba(255, 255, 255, 0.9); 936 text-transform: uppercase; 937 letter-spacing: 0.9px; 938 font-weight: 500; 939 } 940 941 .hero-actions { 942 display: flex; 943 gap: 18px; 944 z-index: 2; 945 align-self: flex-end; 946 } 947 948 .hero-number { 949 font-size: 32px; 950 font-weight: 700; 951 color: white; 952 margin-bottom: 5px; 953 text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); 954 } 955 956 .hero-label { 957 font-size: 12px; 958 color: rgba(255, 255, 255, 0.8); 959 text-transform: uppercase; 960 letter-spacing: 1px; 961 } 962 963 .hero-actions { 964 display: flex; 965 gap: 15px; 966 z-index: 2; 967 } 968 969 .hero-btn { 970 padding: 16px 24px; 971 border: none; 972 border-radius: 12px; 973 font-size: 15px; 974 font-weight: 600; 975 cursor: pointer; 976 transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); 977 display: flex; 978 align-items: center; 979 gap: 10px; 980 position: relative; 981 overflow: hidden; 982 } 983 984 .hero-btn.primary { 985 background: rgba(255, 255, 255, 0.9); 986 color: #3b82f6; 987 box-shadow: 0 8px 25px rgba(59, 130, 246, 0.2); 988 } 989 990 .hero-btn.secondary { 991 background: rgba(59, 130, 246, 0.15); 992 color: white; 993 border: 2px solid rgba(59, 130, 246, 0.3); 994 } 995 996 .hero-btn:hover { 997 transform: translateY(-3px) scale(1.05); 998 box-shadow: 0 12px 35px rgba(0, 0, 0, 0.4); 999 } 1000 1001 .btn-icon { 1002 font-size: 18px; 1003 } 1004 1005 .hero-particles { 1006 position: absolute; 1007 top: 0; 1008 left: 0; 1009 width: 100%; 1010 height: 100%; 1011 overflow: hidden; 1012 z-index: 1; 1013 } 1014 1015 .hero-particles::before { 1016 content: ''; 1017 position: absolute; 1018 top: -50%; 1019 left: -50%; 1020 width: 200%; 1021 height: 200%; 1022 background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="1" fill="white" opacity="0.3"/></svg>'); 1023 background-size: 50px 50px; 1024 animation: particles 20s linear infinite; 1025 } 1026 1027 @keyframes particles { 1028 0% { transform: translate(0, 0) rotate(0deg); } 1029 100% { transform: translate(-50px, -50px) rotate(360deg); } 1030 } 1031 1032 @keyframes float { 1033 0%, 100% { transform: translateY(0px); } 1034 50% { transform: translateY(-10px); } 1035 } 1036 1037 /* Modern Dashboard Grid */ 1038 .dashboard-grid-modern { 1039 display: grid; 1040 grid-template-columns: 1fr; 1041 gap: 30px; 1042 margin-bottom: 35px; 1043 } 1044 1045 .modern-card { 1046 background: rgba(255, 255, 255, 0.95); 1047 border-radius: 14px; 1048 box-shadow: 0 8px 25px rgba(0, 0, 0, 0.07); 1049 backdrop-filter: blur(18px); 1050 border: 1px solid rgba(0, 0, 0, 0.05); 1051 transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); 1052 position: relative; 1053 overflow: hidden; 1054 padding: 25px; 1055 } 1056 1057 .modern-card:hover { 1058 transform: translateY(-3px); 1059 box-shadow: 0 12px 35px rgba(0, 0, 0, 0.11); 1060 border-color: rgba(0, 0, 0, 0.09); 1061 } 1062 1063 .card-header { 1064 display: flex; 1065 align-items: center; 1066 gap: 15px; 1067 padding: 25px 25px 20px; 1068 border-bottom: 1px solid rgba(0, 0, 0, 0.05); 1069 } 1070 1071 .header-icon { 1072 font-size: 28px; 1073 width: 40px; 1074 text-align: center; 1075 } 1076 1077 .card-header h3 { 1078 margin: 0; 1079 font-size: 22px; 1080 font-weight: 600; 1081 color: #333; 1082 flex: 1; 1083 } 1084 1085 .card-content { 1086 padding: 25px; 1087 } 1088 1089 /* Status Card */ 1090 .status-display { 1091 display: flex; 1092 align-items: center; 1093 gap: 30px; 1094 margin-bottom: 25px; 1095 } 1096 1097 .status-circle { 1098 width: 110px; 1099 height: 110px; 1100 border-radius: 50%; 1101 position: relative; 1102 display: flex; 1103 align-items: center; 1104 justify-content: center; 1105 } 1106 1107 .status-circle.excellent { 1108 background: linear-gradient(135deg, #10b981, #059669); 1109 } 1110 1111 .status-circle.good { 1112 background: linear-gradient(135deg, #06b6d4, #1e88e5); 1113 } 1114 1115 .status-circle.fair { 1116 background: linear-gradient(135deg, #f59e0b, #f97316); 1117 } 1118 1119 .status-circle.poor { 1120 background: linear-gradient(135deg, #dc2626, #ef4444); 1121 } 1122 1123 .status-ring { 1124 position: absolute; 1125 top: -4px; 1126 left: -4px; 1127 right: -4px; 1128 bottom: -4px; 1129 border-radius: 50%; 1130 border: 2.5px solid rgba(255, 255, 255, 0.3); 1131 animation: rotate 3s linear infinite; 1132 } 1133 1134 @keyframes rotate { 1135 0% { transform: rotate(0deg); } 1136 100% { transform: rotate(360deg); } 1137 } 1138 1139 .status-inner { 1140 text-align: center; 1141 color: white; 1142 } 1143 1144 .status-score { 1145 font-size: 26px; 1146 font-weight: 900; 1147 margin-bottom: 4px; 1148 text-shadow: 0 2px 7px rgba(0, 0, 0, 0.8); 1149 -webkit-text-stroke: 1.8px rgba(0, 0, 0, 0.3); 1150 paint-order: stroke fill; 1151 } 1152 1153 .status-text { 1154 font-size: 11px; 1155 font-weight: 800; 1156 text-transform: uppercase; 1157 letter-spacing: 1.1px; 1158 text-shadow: 0 1.5px 5px rgba(0, 0, 0, 0.8); 1159 -webkit-text-stroke: 0.9px rgba(0, 0, 0, 0.3); 1160 paint-order: stroke fill; 1161 } 1162 1163 .status-details h4 { 1164 margin: 0 0 10px 0; 1165 font-size: 20px; 1166 color: #333; 1167 } 1168 1169 .status-details p { 1170 margin: 0; 1171 font-size: 15px; 1172 color: #666; 1173 line-height: 1.5; 1174 } 1175 1176 /* Live Statistics */ 1177 .live-indicator { 1178 display: flex; 1179 align-items: center; 1180 gap: 8px; 1181 padding: 6px 12px; 1182 background: #e8f5e8; 1183 border-radius: 20px; 1184 font-size: 11px; 1185 font-weight: 600; 1186 color: #2e7d32; 1187 animation: pulse 2s ease-in-out infinite; 1188 } 1189 1190 .live-dot { 1191 width: 8px; 1192 height: 8px; 1193 border-radius: 50%; 1194 background: #4caf50; 1195 animation: blink 1.5s ease-in-out infinite; 1196 } 1197 1198 @keyframes pulse { 1199 0%, 100% { opacity: 1; } 1200 50% { opacity: 0.7; } 1201 } 1202 1203 @keyframes blink { 1204 0%, 100% { opacity: 1; } 1205 50% { opacity: 0.3; } 1206 } 1207 1208 .stats-grid-modern { 1209 display: grid; 1210 grid-template-columns: 1fr 1fr; 1211 gap: 20px; 1212 } 1213 1214 .stat-modern { 1215 background: #f8f9fa; 1216 padding: 20px; 1217 border-radius: 16px; 1218 display: flex; 1219 align-items: center; 1220 gap: 15px; 1221 transition: all 0.3s ease; 1222 } 1223 1224 .stat-modern:hover { 1225 transform: translateY(-3px); 1226 box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); 1227 } 1228 1229 .stat-icon-modern { 1230 font-size: 32px; 1231 width: 50px; 1232 text-align: center; 1233 position: relative; 1234 } 1235 1236 .icon-pulse { 1237 position: absolute; 1238 top: -5px; 1239 left: -5px; 1240 right: -5px; 1241 bottom: -5px; 1242 border-radius: 50%; 1243 border: 2px solid currentColor; 1244 opacity: 0.3; 1245 animation: iconPulse 2s ease-in-out infinite; 1246 } 1247 1248 .icon-pulse.success { 1249 border-color: #4caf50; 1250 } 1251 1252 .icon-pulse.warning { 1253 border-color: #ff9800; 1254 } 1255 1256 @keyframes iconPulse { 1257 0%, 100% { transform: scale(1); opacity: 0.3; } 1258 50% { transform: scale(1.1); opacity: 0.1; } 1259 } 1260 1261 .stat-content-modern { 1262 flex: 1; 1263 } 1264 1265 .stat-value { 1266 font-size: 28px; 1267 font-weight: 700; 1268 color: #3b82f6; 1269 margin-bottom: 5px; 1270 } 1271 1272 .stat-name { 1273 font-size: 14px; 1274 color: #666; 1275 margin-bottom: 5px; 1276 } 1277 1278 .stat-change { 1279 font-size: 12px; 1280 font-weight: 600; 1281 padding: 3px 8px; 1282 border-radius: 12px; 1283 } 1284 1285 .stat-change.positive { 1286 background: #e8f5e8; 1287 color: #2e7d32; 1288 } 1289 1290 .stat-change.negative { 1291 background: #ffebee; 1292 color: #c62828; 1293 } 1294 1295 .stat-change.neutral { 1296 background: #fff3e0; 1297 color: #ef6c00; 1298 } 1299 1300 /* Features Card */ 1301 .feature-count { 1302 padding: 6px 12px; 1303 background: linear-gradient(135deg, #667eea, #764ba2); 1304 color: white; 1305 border-radius: 20px; 1306 font-size: 12px; 1307 font-weight: 600; 1308 } 1309 1310 .features-list-modern { 1311 display: flex; 1312 flex-direction: column; 1313 gap: 15px; 1314 } 1315 1316 .feature-modern { 1317 display: flex; 1318 align-items: center; 1319 gap: 20px; 1320 padding: 20px; 1321 border-radius: 16px; 1322 transition: all 0.4s ease; 1323 animation: slideInUp 0.6s ease-out forwards; 1324 opacity: 0; 1325 } 1326 1327 .feature-modern.enabled { 1328 background: linear-gradient(135deg, rgba(76, 175, 80, 0.1), rgba(139, 195, 74, 0.1)); 1329 border: 1px solid rgba(76, 175, 80, 0.3); 1330 } 1331 1332 .feature-modern.disabled { 1333 background: rgba(0, 0, 0, 0.03); 1334 border: 1px solid rgba(0, 0, 0, 0.1); 1335 } 1336 1337 @keyframes slideInUp { 1338 from { 1339 opacity: 0; 1340 transform: translateY(30px); 1341 } 1342 to { 1343 opacity: 1; 1344 transform: translateY(0); 1345 } 1346 } 1347 1348 .feature-icon-modern { 1349 font-size: 28px; 1350 width: 40px; 1351 text-align: center; 1352 position: relative; 1353 } 1354 1355 .feature-glow { 1356 position: absolute; 1357 top: -8px; 1358 left: -8px; 1359 right: -8px; 1360 bottom: -8px; 1361 border-radius: 50%; 1362 opacity: 0; 1363 transition: all 0.4s ease; 1364 } 1365 1366 .feature-modern:hover .feature-glow { 1367 opacity: 0.3; 1368 } 1369 1370 .feature-glow.glow-green { 1371 border: 2px solid #4caf50; 1372 box-shadow: 0 0 20px rgba(76, 175, 80, 0.4); 1373 } 1374 1375 .feature-glow.glow-red { 1376 border: 2px solid #f44336; 1377 box-shadow: 0 0 20px rgba(244, 67, 54, 0.4); 1378 } 1379 1380 .feature-info-modern { 1381 flex: 1; 1382 } 1383 1384 .feature-info-modern h4 { 1385 margin: 0 0 8px 0; 1386 font-size: 16px; 1387 font-weight: 600; 1388 color: #333; 1389 } 1390 1391 .feature-info-modern p { 1392 margin: 0 0 10px 0; 1393 font-size: 13px; 1394 color: #666; 1395 } 1396 1397 .toggle-switch { 1398 width: 50px; 1399 height: 26px; 1400 background: #ddd; 1401 border-radius: 13px; 1402 position: relative; 1403 transition: all 0.3s ease; 1404 } 1405 1406 .toggle-switch.active { 1407 background: linear-gradient(135deg, #4caf50, #8bc34a); 1408 } 1409 1410 .toggle-slider { 1411 position: absolute; 1412 top: 3px; 1413 left: 3px; 1414 width: 20px; 1415 height: 20px; 1416 background: white; 1417 border-radius: 50%; 1418 transition: all 0.3s ease; 1419 } 1420 1421 .toggle-switch.active .toggle-slider { 1422 transform: translateX(24px); 1423 } 1424 1425 /* Actions Card */ 1426 .actions-grid { 1427 display: grid; 1428 grid-template-columns: 1fr 1fr; 1429 gap: 15px; 1430 } 1431 1432 .action-card-modern { 1433 background: #f8f9fa; 1434 border: 2px solid transparent; 1435 border-radius: 16px; 1436 padding: 20px; 1437 cursor: pointer; 1438 transition: all 0.3s ease; 1439 text-align: left; 1440 } 1441 1442 .action-card-modern:hover { 1443 border-color: #667eea; 1444 transform: translateY(-3px); 1445 box-shadow: 0 8px 25px rgba(102, 126, 234, 0.2); 1446 } 1447 1448 .action-icon { 1449 font-size: 24px; 1450 margin-bottom: 10px; 1451 display: block; 1452 } 1453 1454 .action-content h4 { 1455 margin: 0 0 5px 0; 1456 font-size: 16px; 1457 font-weight: 600; 1458 color: #333; 1459 } 1460 1461 .action-content p { 1462 margin: 0; 1463 font-size: 13px; 1464 color: #666; 1465 } 1466 1467 .modern-btn { 1468 padding: 14px 24px; 1469 border: none; 1470 border-radius: 12px; 1471 font-size: 15px; 1472 font-weight: 600; 1473 cursor: pointer; 1474 transition: all 0.3s ease; 1475 display: inline-flex; 1476 align-items: center; 1477 gap: 10px; 1478 } 1479 1480 .modern-btn.primary { 1481 background: linear-gradient(135deg, #667eea, #764ba2); 1482 color: white; 1483 box-shadow: 0 6px 20px rgba(102, 126, 234, 0.3); 1484 } 1485 1486 .modern-btn:hover { 1487 transform: translateY(-2px); 1488 box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4); 1489 } 1490 1491 /* Responsive Design */ 1492 @media (max-width: 1200px) { 1493 .dashboard-grid-modern { 1494 grid-template-columns: 1fr; 1495 } 1496 1497 .stats-grid-modern { 1498 grid-template-columns: 1fr; 1499 } 1500 1501 .actions-grid { 1502 grid-template-columns: 1fr; 1503 } 1504 } 1505 1506 @media (max-width: 768px) { 1507 .hero-content { 1508 flex-direction: column; 1509 gap: 20px; 1510 text-align: center; 1511 } 1512 1513 .hero-stats { 1514 flex-wrap: wrap; 1515 justify-content: center; 1516 } 1517 1518 .hero-actions { 1519 flex-direction: column; 1520 width: 100%; 1521 } 1522 1523 .hero-btn { 1524 width: 100%; 1525 justify-content: center; 1526 } 1527 1528 .status-display { 1529 flex-direction: column; 1530 gap: 20px; 1531 } 1532 1533 .feature-modern { 1534 flex-direction: column; 1535 text-align: center; 1536 } 1537 } 1538 </style> 1539 1540 <script> 1541 function runSecurityScan() { 1542 window.location.href = '?page=lockspire-scanner&scan=1'; 1543 } 1544 1545 function clearAllLogs() { 1546 if (confirm('Are you sure you want to clear all activity logs?')) { 1547 window.location.href = '?page=lockspire-activity&action=clear_logs'; 1548 } 1549 } 1550 1551 function exportSecurityReport() { 1552 window.location.href = '?page=lockspire-activity&action=export'; 1553 } 1554 1555 function refreshDashboard() { 1556 location.reload(); 1557 } 1558 </script> 1559 <?php 1560 } 1561 1562 // Helper methods for dashboard functionality 1563 private function get_recent_logins($limit = 10) { 1564 $activity_log = get_option($this->activity_log_key, array()); 1565 1566 // Filter only login events and sort by time 1567 $login_events = array_filter($activity_log, function($event) { 1568 return isset($event['user_login']); 1569 }); 1570 1571 // Sort by time (newest first) 1572 usort($login_events, function($a, $b) { 1573 return strtotime($b['time']) - strtotime($a['time']); 1574 }); 1575 1576 return array_slice($login_events, 0, $limit); 1577 } 1578 1579 private function calculate_security_score() { 1580 $score = 0; 1581 $max_score = 100; 1582 1583 // Login protection (25 points) 1584 if ($this->options['login_limit_enabled']) $score += 25; 1585 1586 // Firewall (25 points) 1587 if ($this->options['firewall_enabled']) $score += 25; 1588 1589 // XML-RPC disabled (15 points) 1590 if ($this->options['disable_xmlrpc']) $score += 15; 1591 1592 // Admin URL hiding (15 points) 1593 if ($this->options['hide_admin_enabled']) $score += 15; 1594 1595 // Email notifications (10 points) 1596 if ($this->options['email_notifications']) $score += 10; 1597 1598 // Activity logging (10 points) 1599 if ($this->options['activity_log_enabled']) $score += 10; 1600 1601 return $score; 1602 } 1603 1604 private function get_security_status() { 1605 $score = $this->calculate_security_score(); 1606 1607 if ($score >= 80) { 1608 return array( 1609 'message' => 'Excellent Security! Your site is well protected.', 1610 'description' => 'Most security features are enabled and configured properly.', 1611 'status' => 'excellent', 1612 'class' => 'status-excellent' 1613 ); 1614 } elseif ($score >= 60) { 1615 return array( 1616 'message' => 'Good Security', 1617 'description' => 'Your site has good security protection with room for improvement.', 1618 'status' => 'good', 1619 'class' => 'status-good' 1620 ); 1621 } elseif ($score >= 40) { 1622 return array( 1623 'message' => 'Fair Security', 1624 'description' => 'Some security features are enabled, but more protection is recommended.', 1625 'status' => 'fair', 1626 'class' => 'status-fair' 1627 ); 1628 } else { 1629 return array( 1630 'message' => 'Poor Security - Action Required', 1631 'description' => 'Your site needs immediate security attention. Enable more features.', 1632 'status' => 'poor', 1633 'class' => 'status-poor' 1634 ); 1635 } 1636 } 1637 1638 private function get_security_recommendations() { 1639 $recommendations = array(); 1640 1641 if (!$this->options['login_limit_enabled']) { 1642 $recommendations[] = 'Enable login protection to prevent brute force attacks'; 1643 } 1644 1645 if (!$this->options['firewall_enabled']) { 1646 $recommendations[] = 'Enable firewall to block malicious requests'; 1647 } 1648 1649 if (!$this->options['disable_xmlrpc']) { 1650 $recommendations[] = 'Disable XML-RPC to prevent XML-RPC attacks'; 1651 } 1652 1653 if (!$this->options['hide_admin_enabled']) { 1654 $recommendations[] = 'Hide admin URL to protect against direct attacks'; 1655 } 1656 1657 if (!$this->options['email_notifications']) { 1658 $recommendations[] = 'Enable email notifications for admin login alerts'; 1659 } 1660 1661 return $recommendations; 1662 } 1663 1664 private function get_today_login_attempts() { 1665 // Try to get from cache first 1666 $cache_key = 'lockspire_today_login_attempts_' . gmdate('Y-m-d'); 1667 $cached_count = wp_cache_get($cache_key); 1668 1669 if ($cached_count !== false) { 1670 return $cached_count; 1671 } 1672 1673 $attempts = 0; 1674 $today = gmdate('Y-m-d'); 1675 1676 // Get all transients using WordPress function 1677 global $wpdb; 1678 $transient_names = $wpdb->get_col( 1679 "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_lockspire_login_attempts_%'" 1680 ); 1681 1682 foreach ($transient_names as $transient_name) { 1683 // Remove the _transient_ prefix to get the actual transient key 1684 $transient_key = str_replace('_transient_', '', $transient_name); 1685 $data = get_transient($transient_key); 1686 1687 if (is_array($data)) { 1688 foreach ($data as $attempt) { 1689 if (isset($attempt['time']) && gmdate('Y-m-d', $attempt['time']) === $today) { 1690 $attempts++; 1691 } 1692 } 1693 } 1694 } 1695 1696 // Cache for 5 minutes 1697 wp_cache_set($cache_key, $attempts, '', 300); 1698 1699 return $attempts; 1700 } 1701 1702 private function get_active_threats() { 1703 $threats = 0; 1704 1705 // Count blocked IPs 1706 $blocked_ips = get_option('lockspire_blocked_ips', array()); 1707 $threats += count($blocked_ips); 1708 1709 // Count recent failed login attempts 1710 $threats += $this->get_today_login_attempts(); 1711 1712 return $threats; 1713 } 1714 1715 // Enqueue admin scripts and styles 1716 public function enqueue_admin_scripts($hook) { 1717 if (strpos($hook, 'lockspire') === false) { 1718 return; 1719 } 1720 1721 // Enqueue WordPress scripts 1722 wp_enqueue_script('jquery'); 1723 1724 // Add custom CSS 1725 $custom_css = ' 1726 /* Lockspire Security Admin Styles */ 1727 .lockspire-wrap { 1728 max-width: 1600px; 1729 margin: 0; 1730 padding: 0 30px 30px 30px; 1731 background: #f1f3f6; 1732 min-height: 100vh; 1733 } 1734 1735 .lockspire-header { 1736 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 1737 color: white; 1738 padding: 30px 40px; 1739 display: flex; 1740 justify-content: space-between; 1741 align-items: center; 1742 margin: -30px -30px 40px -30px; 1743 border-radius: 0 0 20px 20px; 1744 box-shadow: 0 8px 30px rgba(102, 126, 234, 0.3); 1745 } 1746 1747 .lockspire-logo { 1748 display: flex; 1749 align-items: center; 1750 gap: 15px; 1751 } 1752 1753 .lockspire-icon { 1754 font-size: 32px; 1755 } 1756 1757 .lockspire-logo h1 { 1758 margin: 0; 1759 font-size: 24px; 1760 font-weight: 600; 1761 } 1762 1763 .lockspire-version { 1764 background: rgba(255, 255, 255, 0.2); 1765 padding: 5px 12px; 1766 border-radius: 15px; 1767 font-size: 12px; 1768 } 1769 1770 .lockspire-nav { 1771 display: flex; 1772 gap: 8px; 1773 margin-bottom: 30px; 1774 background: white; 1775 padding: 15px 25px; 1776 border-radius: 15px; 1777 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); 1778 flex-wrap: wrap; 1779 } 1780 1781 .nav-item { 1782 padding: 12px 20px; 1783 text-decoration: none; 1784 color: #666; 1785 border-radius: 10px; 1786 transition: all 0.3s ease; 1787 font-weight: 500; 1788 font-size: 14px; 1789 } 1790 1791 .nav-item:hover { 1792 background: #f0f0f0; 1793 color: #333; 1794 } 1795 1796 .nav-item.active { 1797 background: #667eea; 1798 color: white; 1799 } 1800 1801 .lockspire-content { 1802 background: transparent; 1803 } 1804 1805 .page-title { 1806 margin-bottom: 20px; 1807 color: #333; 1808 font-size: 28px; 1809 font-weight: 600; 1810 } 1811 1812 .lockspire-grid { 1813 display: grid; 1814 grid-template-columns: 1fr 1fr; 1815 gap: 30px; 1816 margin-bottom: 30px; 1817 } 1818 1819 @media (max-width: 1200px) { 1820 .lockspire-grid { 1821 grid-template-columns: 1fr; 1822 } 1823 } 1824 1825 .lockspire-card { 1826 background: white; 1827 border-radius: 16px; 1828 box-shadow: 0 6px 25px rgba(0, 0, 0, 0.08); 1829 overflow: hidden; 1830 transition: transform 0.3s ease, box-shadow 0.3s ease; 1831 border: 1px solid rgba(0, 0, 0, 0.05); 1832 } 1833 1834 .lockspire-card:hover { 1835 transform: translateY(-2px); 1836 box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); 1837 } 1838 1839 .lockspire-card-primary { 1840 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 1841 color: white; 1842 } 1843 1844 .lockspire-card-header { 1845 padding: 25px 30px; 1846 border-bottom: 1px solid rgba(0, 0, 0, 0.08); 1847 display: flex; 1848 justify-content: space-between; 1849 align-items: center; 1850 background: rgba(248, 250, 252, 0.8); 1851 } 1852 1853 .lockspire-card-primary .lockspire-card-header { 1854 border-bottom: 1px solid rgba(255, 255, 255, 0.2); 1855 } 1856 1857 .lockspire-card-header h3 { 1858 margin: 0; 1859 font-size: 18px; 1860 font-weight: 600; 1861 } 1862 1863 .lockspire-card-body { 1864 padding: 30px; 1865 } 1866 1867 /* Security Score Circle */ 1868 .lockspire-score-circle { 1869 width: 80px; 1870 height: 80px; 1871 border-radius: 50%; 1872 background: rgba(255, 255, 255, 0.2); 1873 display: flex; 1874 flex-direction: column; 1875 align-items: center; 1876 justify-content: center; 1877 font-weight: bold; 1878 } 1879 1880 .score-number { 1881 font-size: 28px; 1882 line-height: 1; 1883 } 1884 1885 .score-label { 1886 font-size: 10px; 1887 text-transform: uppercase; 1888 opacity: 0.8; 1889 } 1890 1891 .security-status { 1892 display: flex; 1893 align-items: center; 1894 gap: 10px; 1895 padding: 15px; 1896 border-radius: 8px; 1897 margin-bottom: 20px; 1898 } 1899 1900 .status-excellent { 1901 background: rgba(76, 175, 80, 0.2); 1902 color: #4caf50; 1903 } 1904 1905 .status-good { 1906 background: rgba(255, 193, 7, 0.2); 1907 color: #ffc107; 1908 } 1909 1910 .status-fair { 1911 background: rgba(255, 152, 0, 0.2); 1912 color: #ff9800; 1913 } 1914 1915 .status-poor { 1916 background: rgba(244, 67, 54, 0.2); 1917 color: #f44336; 1918 } 1919 1920 .security-recommendations { 1921 background: rgba(255, 255, 255, 0.1); 1922 padding: 15px; 1923 border-radius: 8px; 1924 } 1925 1926 .security-recommendations h4 { 1927 margin-top: 0; 1928 margin-bottom: 10px; 1929 } 1930 1931 .security-recommendations ul { 1932 margin: 0; 1933 padding-left: 20px; 1934 } 1935 1936 .security-recommendations li { 1937 margin-bottom: 5px; 1938 } 1939 1940 /* Stats Grid */ 1941 .stats-grid { 1942 display: grid; 1943 grid-template-columns: repeat(4, 1fr); 1944 gap: 20px; 1945 } 1946 1947 .stat-item { 1948 text-align: center; 1949 padding: 25px 20px; 1950 background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); 1951 border-radius: 12px; 1952 border: 1px solid rgba(0, 0, 0, 0.05); 1953 transition: transform 0.3s ease; 1954 } 1955 1956 .stat-item:hover { 1957 transform: translateY(-2px); 1958 } 1959 1960 .stat-number { 1961 font-size: 24px; 1962 font-weight: bold; 1963 color: #667eea; 1964 margin-bottom: 5px; 1965 } 1966 1967 .stat-label { 1968 font-size: 12px; 1969 color: #666; 1970 text-transform: uppercase; 1971 } 1972 1973 /* Features Grid */ 1974 .features-grid { 1975 display: grid; 1976 gap: 20px; 1977 } 1978 1979 .feature-item { 1980 display: flex; 1981 align-items: center; 1982 gap: 20px; 1983 padding: 20px 25px; 1984 border-radius: 12px; 1985 border: 2px solid #e0e0e0; 1986 transition: all 0.3s ease; 1987 background: white; 1988 } 1989 1990 .feature-item.active { 1991 border-color: #4caf50; 1992 background: rgba(76, 175, 80, 0.1); 1993 } 1994 1995 .feature-item.inactive { 1996 border-color: #f44336; 1997 background: rgba(244, 67, 54, 0.1); 1998 } 1999 2000 .feature-icon { 2001 font-size: 24px; 2002 width: 40px; 2003 text-align: center; 2004 } 2005 2006 .feature-info h4 { 2007 margin: 0 0 5px 0; 2008 font-size: 16px; 2009 } 2010 2011 .feature-info p { 2012 margin: 0 0 5px 0; 2013 font-size: 14px; 2014 color: #666; 2015 } 2016 2017 .feature-status { 2018 font-size: 12px; 2019 font-weight: bold; 2020 } 2021 2022 /* Activity List */ 2023 .activity-list { 2024 max-height: 400px; 2025 overflow-y: auto; 2026 } 2027 2028 .activity-item { 2029 display: flex; 2030 align-items: flex-start; 2031 gap: 20px; 2032 padding: 20px 0; 2033 border-bottom: 1px solid rgba(0, 0, 0, 0.08); 2034 } 2035 2036 .activity-item:last-child { 2037 border-bottom: none; 2038 } 2039 2040 .activity-icon { 2041 font-size: 20px; 2042 width: 30px; 2043 text-align: center; 2044 } 2045 2046 .activity-details { 2047 flex: 1; 2048 } 2049 2050 .activity-user { 2051 font-weight: bold; 2052 margin-bottom: 5px; 2053 } 2054 2055 .activity-info { 2056 display: flex; 2057 gap: 15px; 2058 font-size: 12px; 2059 color: #666; 2060 margin-bottom: 5px; 2061 } 2062 2063 .activity-time { 2064 font-size: 11px; 2065 color: #999; 2066 } 2067 2068 .activity-browser { 2069 font-size: 11px; 2070 color: #999; 2071 margin-top: 5px; 2072 word-break: break-all; 2073 } 2074 2075 /* Quick Actions */ 2076 .quick-actions { 2077 display: flex; 2078 gap: 15px; 2079 flex-wrap: wrap; 2080 justify-content: center; 2081 margin-top: 20px; 2082 } 2083 2084 /* Toggle Switch */ 2085 .switch { 2086 position: relative; 2087 display: inline-block; 2088 width: 50px; 2089 height: 24px; 2090 } 2091 2092 .switch input { 2093 opacity: 0; 2094 width: 0; 2095 height: 0; 2096 } 2097 2098 .slider { 2099 position: absolute; 2100 cursor: pointer; 2101 top: 0; 2102 left: 0; 2103 right: 0; 2104 bottom: 0; 2105 background-color: #ccc; 2106 transition: .4s; 2107 border-radius: 24px; 2108 } 2109 2110 .slider:before { 2111 position: absolute; 2112 content: ""; 2113 height: 18px; 2114 width: 18px; 2115 left: 3px; 2116 bottom: 3px; 2117 background-color: white; 2118 transition: .4s; 2119 border-radius: 50%; 2120 } 2121 2122 input:checked + .slider { 2123 background-color: #667eea; 2124 } 2125 2126 input:checked + .slider:before { 2127 transform: translateX(26px); 2128 } 2129 2130 /* Firewall Stats */ 2131 .firewall-stats { 2132 display: grid; 2133 grid-template-columns: repeat(3, 1fr); 2134 gap: 15px; 2135 } 2136 2137 /* Blocked IPs */ 2138 .blocked-ips-list { 2139 max-height: 300px; 2140 overflow-y: auto; 2141 } 2142 2143 .blocked-ip-item { 2144 display: flex; 2145 justify-content: space-between; 2146 align-items: center; 2147 padding: 15px; 2148 border: 1px solid #e0e0e0; 2149 border-radius: 8px; 2150 margin-bottom: 10px; 2151 } 2152 2153 .ip-address { 2154 font-weight: bold; 2155 margin-bottom: 5px; 2156 } 2157 2158 .ip-reason { 2159 font-size: 12px; 2160 color: #666; 2161 margin-bottom: 5px; 2162 } 2163 2164 .ip-time { 2165 font-size: 11px; 2166 color: #999; 2167 } 2168 2169 /* Firewall Rules */ 2170 .firewall-rules { 2171 display: grid; 2172 gap: 15px; 2173 } 2174 2175 .rule-item { 2176 display: flex; 2177 align-items: center; 2178 gap: 15px; 2179 padding: 15px; 2180 border: 1px solid #e0e0e0; 2181 border-radius: 8px; 2182 } 2183 2184 .rule-info h4 { 2185 margin: 0 0 5px 0; 2186 } 2187 2188 .rule-info p { 2189 margin: 0; 2190 font-size: 12px; 2191 color: #666; 2192 } 2193 2194 .rule-severity { 2195 padding: 4px 8px; 2196 border-radius: 4px; 2197 font-size: 11px; 2198 font-weight: bold; 2199 text-transform: uppercase; 2200 } 2201 2202 .rule-severity.critical { 2203 background: #ffebee; 2204 color: #c62828; 2205 } 2206 2207 .rule-severity.high { 2208 background: #fff3e0; 2209 color: #ef6c00; 2210 } 2211 2212 .rule-severity.medium { 2213 background: #fff8e1; 2214 color: #f57f17; 2215 } 2216 2217 /* Scanner Results */ 2218 .scan-summary { 2219 display: grid; 2220 grid-template-columns: repeat(4, 1fr); 2221 gap: 15px; 2222 margin-bottom: 20px; 2223 } 2224 2225 .summary-item { 2226 text-align: center; 2227 padding: 20px; 2228 border-radius: 8px; 2229 } 2230 2231 .summary-item.critical { 2232 background: #ffebee; 2233 color: #c62828; 2234 } 2235 2236 .summary-item.warning { 2237 background: #fff8e1; 2238 color: #f57f17; 2239 } 2240 2241 .summary-item.info { 2242 background: #e3f2fd; 2243 color: #1565c0; 2244 } 2245 2246 .summary-item.good { 2247 background: #e8f5e8; 2248 color: #2e7d32; 2249 } 2250 2251 .summary-number { 2252 font-size: 32px; 2253 font-weight: bold; 2254 margin-bottom: 5px; 2255 } 2256 2257 .summary-label { 2258 font-size: 12px; 2259 text-transform: uppercase; 2260 } 2261 2262 .scan-details { 2263 display: grid; 2264 gap: 15px; 2265 } 2266 2267 .scan-item { 2268 padding: 15px; 2269 border-radius: 8px; 2270 border-left: 4px solid; 2271 } 2272 2273 .scan-item.critical { 2274 background: #ffebee; 2275 border-left-color: #c62828; 2276 } 2277 2278 .scan-item.warning { 2279 background: #fff8e1; 2280 border-left-color: #f57f17; 2281 } 2282 2283 .scan-item.info { 2284 background: #e3f2fd; 2285 border-left-color: #1565c0; 2286 } 2287 2288 .scan-item.good { 2289 background: #e8f5e8; 2290 border-left-color: #2e7d32; 2291 } 2292 2293 .issue-icon { 2294 font-size: 24px; 2295 margin-bottom: 10px; 2296 } 2297 2298 .issue-details h4 { 2299 margin: 0 0 5px 0; 2300 } 2301 2302 .issue-details p { 2303 margin: 0 0 10px 0; 2304 } 2305 2306 .recommendation { 2307 background: rgba(0, 0, 0, 0.1); 2308 padding: 10px; 2309 border-radius: 4px; 2310 font-size: 12px; 2311 } 2312 2313 /* Login Settings */ 2314 .login-settings { 2315 display: grid; 2316 gap: 15px; 2317 } 2318 2319 .setting-item { 2320 display: flex; 2321 align-items: center; 2322 gap: 10px; 2323 } 2324 2325 .setting-item label { 2326 min-width: 150px; 2327 font-weight: 500; 2328 } 2329 2330 .setting-item input { 2331 padding: 8px 12px; 2332 border: 1px solid #ddd; 2333 border-radius: 4px; 2334 } 2335 2336 /* Activity Filters */ 2337 .activity-filters { 2338 display: flex; 2339 gap: 10px; 2340 margin-bottom: 20px; 2341 flex-wrap: wrap; 2342 } 2343 2344 .activity-filters select, 2345 .activity-filters input { 2346 padding: 8px 12px; 2347 border: 1px solid #ddd; 2348 border-radius: 4px; 2349 } 2350 2351 /* Pro Features */ 2352 .pro-features { 2353 display: grid; 2354 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); 2355 gap: 20px; 2356 } 2357 2358 .pro-feature { 2359 display: flex; 2360 align-items: center; 2361 gap: 15px; 2362 padding: 20px; 2363 border: 2px dashed #ddd; 2364 border-radius: 8px; 2365 position: relative; 2366 } 2367 2368 .pro-icon { 2369 font-size: 32px; 2370 } 2371 2372 .pro-info h4 { 2373 margin: 0 0 5px 0; 2374 } 2375 2376 .pro-info p { 2377 margin: 0; 2378 font-size: 14px; 2379 color: #666; 2380 } 2381 2382 .pro-badge { 2383 position: absolute; 2384 top: 10px; 2385 right: 10px; 2386 background: #667eea; 2387 color: white; 2388 padding: 4px 8px; 2389 border-radius: 4px; 2390 font-size: 10px; 2391 font-weight: bold; 2392 } 2393 2394 /* Email Notifications Styles */ 2395 .email-settings { 2396 display: grid; 2397 gap: 20px; 2398 } 2399 2400 .email-settings .setting-item { 2401 display: flex; 2402 flex-direction: column; 2403 gap: 8px; 2404 } 2405 2406 .email-settings .setting-item label { 2407 font-weight: 600; 2408 color: #333; 2409 } 2410 2411 .email-settings .setting-item input, 2412 .email-settings .setting-item textarea { 2413 padding: 12px 16px; 2414 border: 2px solid #e0e0e0; 2415 border-radius: 8px; 2416 font-size: 14px; 2417 transition: border-color 0.3s ease; 2418 } 2419 2420 .email-settings .setting-item input:focus, 2421 .email-settings .setting-item textarea:focus { 2422 outline: none; 2423 border-color: #667eea; 2424 box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); 2425 } 2426 2427 .email-settings .setting-item small { 2428 color: #666; 2429 font-size: 12px; 2430 } 2431 2432 .email-stats-grid { 2433 display: grid; 2434 grid-template-columns: repeat(4, 1fr); 2435 gap: 20px; 2436 } 2437 2438 .email-logs-list { 2439 max-height: 400px; 2440 overflow-y: auto; 2441 } 2442 2443 .email-log-item { 2444 display: flex; 2445 align-items: center; 2446 gap: 15px; 2447 padding: 15px 0; 2448 border-bottom: 1px solid rgba(0, 0, 0, 0.08); 2449 } 2450 2451 .email-log-item:last-child { 2452 border-bottom: none; 2453 } 2454 2455 .email-icon { 2456 font-size: 20px; 2457 width: 30px; 2458 text-align: center; 2459 } 2460 2461 .email-details { 2462 flex: 1; 2463 } 2464 2465 .email-type { 2466 font-weight: 600; 2467 margin-bottom: 4px; 2468 } 2469 2470 .email-info { 2471 display: flex; 2472 gap: 15px; 2473 font-size: 12px; 2474 color: #666; 2475 margin-bottom: 4px; 2476 } 2477 2478 .email-subject { 2479 font-size: 13px; 2480 color: #333; 2481 } 2482 2483 .email-status { 2484 padding: 4px 8px; 2485 border-radius: 4px; 2486 font-size: 11px; 2487 font-weight: bold; 2488 text-transform: uppercase; 2489 } 2490 2491 .email-status.sent { 2492 background: #e8f5e8; 2493 color: #2e7d32; 2494 } 2495 2496 .email-status.failed { 2497 background: #ffebee; 2498 color: #c62828; 2499 } 2500 2501 .email-templates { 2502 display: grid; 2503 grid-template-columns: 1fr 1fr; 2504 gap: 30px; 2505 } 2506 2507 @media (max-width: 1200px) { 2508 .email-templates { 2509 grid-template-columns: 1fr; 2510 } 2511 } 2512 2513 .template-preview { 2514 border: 2px solid #e0e0e0; 2515 border-radius: 12px; 2516 overflow: hidden; 2517 } 2518 2519 .template-preview h4 { 2520 margin: 0; 2521 padding: 15px 20px; 2522 background: #f8f9fa; 2523 border-bottom: 1px solid #e0e0e0; 2524 font-size: 16px; 2525 color: #333; 2526 } 2527 2528 .template-content { 2529 background: white; 2530 } 2531 2532 .template-header { 2533 text-align: center; 2534 padding: 30px 20px; 2535 color: white; 2536 } 2537 2538 .template-header:not(.danger) { 2539 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 2540 } 2541 2542 .template-header.danger { 2543 background: linear-gradient(135deg, #f44336 0%, #e91e63 100%); 2544 } 2545 2546 .template-logo { 2547 font-size: 32px; 2548 margin-bottom: 10px; 2549 } 2550 2551 .template-header h5 { 2552 margin: 0 0 5px 0; 2553 font-size: 20px; 2554 } 2555 2556 .template-header p { 2557 margin: 0; 2558 opacity: 0.9; 2559 } 2560 2561 .template-body { 2562 padding: 20px; 2563 font-size: 13px; 2564 color: #666; 2565 } 2566 2567 .template-body ul { 2568 margin: 10px 0; 2569 padding-left: 20px; 2570 } 2571 2572 .template-body li { 2573 margin-bottom: 5px; 2574 } 2575 @media (max-width: 768px) { 2576 .lockspire-header { 2577 flex-direction: column; 2578 gap: 15px; 2579 text-align: center; 2580 } 2581 2582 .lockspire-nav { 2583 flex-wrap: wrap; 2584 justify-content: center; 2585 } 2586 2587 .stats-grid, 2588 .firewall-stats, 2589 .scan-summary { 2590 grid-template-columns: repeat(2, 1fr); 2591 } 2592 2593 .quick-actions { 2594 justify-content: center; 2595 } 2596 } 2597 '; 2598 2599 wp_add_inline_style('wp-admin', $custom_css); 2600 } 2601 2602 // Admin header rendering 2603 private function render_admin_header($page_title) { 2604 ?> 2605 <div class="wrap lockspire-wrap"> 2606 <div class="lockspire-header"> 2607 <div class="lockspire-logo"> 2608 <span class="lockspire-icon">🛡️</span> 2609 <h1>Lockspire Security</h1> 2610 </div> 2611 <div class="lockspire-version"> 2612 Version <?php echo esc_html(LOCKSPIRE_VERSION); ?> 2613 </div> 2614 </div> 2615 2616 <div class="lockspire-nav"> 2617 <a href="?page=lockspire-dashboard" class="nav-item <?php echo $_GET['page'] === 'lockspire-dashboard' ? 'active' : ''; ?>"> 2618 📊 Dashboard 2619 </a> 2620 <a href="?page=lockspire-login" class="nav-item <?php echo $_GET['page'] === 'lockspire-login' ? 'active' : ''; ?>"> 2621 🚪 Login 2622 </a> 2623 <a href="?page=lockspire-email" class="nav-item <?php echo $_GET['page'] === 'lockspire-email' ? 'active' : ''; ?>"> 2624 📧 Email Notifications 2625 </a> 2626 <a href="?page=lockspire-activity" class="nav-item <?php echo $_GET['page'] === 'lockspire-activity' ? 'active' : ''; ?>"> 2627 📝 Activity 2628 </a> 2629 <a href="?page=lockspire-settings" class="nav-item <?php echo $_GET['page'] === 'lockspire-settings' ? 'active' : ''; ?>"> 2630 ⚙️ Settings 2631 </a> 2632 </div> 2633 2634 <div class="lockspire-content"> 2635 <h2 class="page-title"><?php echo esc_html($page_title); ?></h2> 2636 <?php 2637 } 2638 2639 // Scanner page 2640 public function scanner_page() { 2641 $this->render_admin_header('Security Scanner'); 2642 2643 // Handle scan request 2644 if (isset($_GET['scan']) && $_GET['scan'] === '1') { 2645 $scan_results = $this->run_security_scan(); 2646 update_option('lockspire_last_scan', $scan_results); 2647 } else { 2648 $scan_results = get_option('lockspire_last_scan', array()); 2649 } 2650 2651 ?> 2652 <div class="lockspire-scanner"> 2653 <div class="lockspire-card"> 2654 <div class="lockspire-card-header"> 2655 <h3>🔍 Security Scanner</h3> 2656 <button type="button" class="button button-primary" onclick="runScan()"> 2657 Run Full Scan 2658 </button> 2659 </div> 2660 <div class="lockspire-card-body"> 2661 <div class="scan-options"> 2662 <label class="scan-option"> 2663 <input type="checkbox" checked> File Integrity Check 2664 </label> 2665 <label class="scan-option"> 2666 <input type="checkbox" checked> Malware Detection 2667 </label> 2668 <label class="scan-option"> 2669 <input type="checkbox" checked> Vulnerability Scan 2670 </label> 2671 <label class="scan-option"> 2672 <input type="checkbox" checked> Plugin Security Check 2673 </label> 2674 </div> 2675 </div> 2676 </div> 2677 2678 <?php if (!empty($scan_results)): ?> 2679 <div class="lockspire-card"> 2680 <div class="lockspire-card-header"> 2681 <h3>📊 Scan Results</h3> 2682 <span class="scan-time">Last scan: <?php echo esc_html($scan_results['timestamp']); ?></span> 2683 </div> 2684 <div class="lockspire-card-body"> 2685 <div class="scan-summary"> 2686 <div class="summary-item critical"> 2687 <div class="summary-number"><?php echo esc_html($scan_results['critical']); ?></div> 2688 <div class="summary-label">Critical Issues</div> 2689 </div> 2690 <div class="summary-item warning"> 2691 <div class="summary-number"><?php echo esc_html($scan_results['warnings']); ?></div> 2692 <div class="summary-label">Warnings</div> 2693 </div> 2694 <div class="summary-item info"> 2695 <div class="summary-number"><?php echo esc_html($scan_results['info']); ?></div> 2696 <div class="summary-label">Info</div> 2697 </div> 2698 <div class="summary-item good"> 2699 <div class="summary-number"><?php echo esc_html($scan_results['good']); ?></div> 2700 <div class="summary-label">Good</div> 2701 </div> 2702 </div> 2703 2704 <div class="scan-details"> 2705 <?php foreach ($scan_results['details'] as $issue): ?> 2706 <div class="scan-item <?php echo esc_attr($issue['severity']); ?>"> 2707 <div class="issue-icon"><?php echo wp_kses_post($issue['icon']); ?></div> 2708 <div class="issue-details"> 2709 <h4><?php echo esc_html($issue['title']); ?></h4> 2710 <p><?php echo esc_html($issue['description']); ?></p> 2711 <?php if (!empty($issue['recommendation'])): ?> 2712 <div class="recommendation"> 2713 <strong>Recommendation:</strong> <?php echo esc_html($issue['recommendation']); ?> 2714 </div> 2715 <?php endif; ?> 2716 </div> 2717 </div> 2718 <?php endforeach; ?> 2719 </div> 2720 </div> 2721 </div> 2722 <?php endif; ?> 2723 </div> 2724 2725 <script> 2726 function runScan() { 2727 window.location.href = '?page=lockspire-scanner&scan=1'; 2728 } 2729 </script> 2730 <?php 2731 $this->render_admin_footer(); 2732 } 2733 2734 // Email notifications page 2735 public function email_page() { 2736 $this->render_admin_header('Email Notifications'); 2737 2738 $notification_emails = get_option('lockspire_notification_emails', ''); 2739 $email_logs = get_option('lockspire_email_logs', array()); 2740 $email_stats = $this->get_email_stats(); 2741 2742 ?> 2743 <div class="lockspire-email"> 2744 <div class="lockspire-grid"> 2745 <div class="lockspire-card"> 2746 <div class="lockspire-card-header"> 2747 <h3>📧 Email Configuration</h3> 2748 <div class="email-toggle"> 2749 <label class="switch"> 2750 <input type="checkbox" <?php echo $this->options['email_notifications'] ? 'checked' : ''; ?> onchange="toggleEmailNotifications(this)"> 2751 <span class="slider"></span> 2752 </label> 2753 <span>Email Notifications Enabled</span> 2754 </div> 2755 </div> 2756 <div class="lockspire-card-body"> 2757 <div class="email-settings"> 2758 <div class="setting-item"> 2759 <label>Admin Email:</label> 2760 <input type="email" value="<?php echo esc_attr(get_option('admin_email')); ?>" readonly> 2761 <small>WordPress admin email (read-only)</small> 2762 </div> 2763 <div class="setting-item"> 2764 <label>Additional Emails:</label> 2765 <textarea id="notification_emails" rows="3" placeholder="email1@example.com, email2@example.com"><?php echo esc_textarea($notification_emails); ?></textarea> 2766 <small>Comma-separated list of additional notification emails</small> 2767 </div> 2768 <button type="button" class="button button-primary" onclick="saveEmailSettings()"> 2769 💾 Save Email Settings 2770 </button> 2771 <button type="button" class="button" onclick="testEmailNotification()"> 2772 📧 Send Test Email 2773 </button> 2774 </div> 2775 </div> 2776 </div> 2777 2778 <div class="lockspire-card"> 2779 <div class="lockspire-card-header"> 2780 <h3>📊 Email Statistics</h3> 2781 </div> 2782 <div class="lockspire-card-body"> 2783 <div class="email-stats-grid"> 2784 <div class="stat-item"> 2785 <div class="stat-number"><?php echo esc_html($email_stats['total_sent']); ?></div> 2786 <div class="stat-label">Total Sent</div> 2787 </div> 2788 <div class="stat-item"> 2789 <div class="stat-number"><?php echo esc_html($email_stats['login_alerts']); ?></div> 2790 <div class="stat-label">Login Alerts</div> 2791 </div> 2792 <div class="stat-item"> 2793 <div class="stat-number"><?php echo esc_html($email_stats['failed_attempts']); ?></div> 2794 <div class="stat-label">Failed Attempts</div> 2795 </div> 2796 <div class="stat-item"> 2797 <div class="stat-number"><?php echo esc_html($email_stats['today']); ?></div> 2798 <div class="stat-label">Sent Today</div> 2799 </div> 2800 </div> 2801 </div> 2802 </div> 2803 </div> 2804 2805 <div class="lockspire-card"> 2806 <div class="lockspire-card-header"> 2807 <h3>📝 Recent Email Logs</h3> 2808 <button type="button" class="button" onclick="clearEmailLogs()"> 2809 🗑️ Clear Logs 2810 </button> 2811 </div> 2812 <div class="lockspire-card-body"> 2813 <?php if (empty($email_logs)): ?> 2814 <p class="no-email-logs">No email notifications sent yet.</p> 2815 <?php else: ?> 2816 <div class="email-logs-list"> 2817 <?php foreach (array_slice($email_logs, 0, 10) as $log): ?> 2818 <div class="email-log-item"> 2819 <div class="email-icon"> 2820 <?php echo $log['type'] === 'login' ? '✅' : '❌'; ?> 2821 </div> 2822 <div class="email-details"> 2823 <div class="email-type"> 2824 <?php echo $log['type'] === 'login' ? 'Successful Login' : 'Failed Login Attempt'; ?> 2825 </div> 2826 <div class="email-info"> 2827 <span class="recipient"><?php echo esc_html($log['recipient']); ?></span> 2828 <span class="time"><?php echo esc_html($log['time']); ?></span> 2829 </div> 2830 <div class="email-subject"><?php echo esc_html($log['subject']); ?></div> 2831 </div> 2832 <div class="email-status <?php echo esc_attr($log['status']); ?>"> 2833 <?php echo esc_html(ucfirst($log['status'])); ?> 2834 </div> 2835 </div> 2836 <?php endforeach; ?> 2837 </div> 2838 <?php endif; ?> 2839 </div> 2840 </div> 2841 2842 <div class="lockspire-card"> 2843 <div class="lockspire-card-header"> 2844 <h3>📋 Email Templates Preview</h3> 2845 </div> 2846 <div class="lockspire-card-body"> 2847 <div class="email-templates"> 2848 <div class="template-preview"> 2849 <h4>✅ Successful Login Email</h4> 2850 <div class="template-content"> 2851 <div class="template-header"> 2852 <div class="template-logo">🛡️</div> 2853 <h5>Security Alert</h5> 2854 <p>Login Detected on Your Website</p> 2855 </div> 2856 <div class="template-body"> 2857 <p><strong>User Information:</strong></p> 2858 <ul> 2859 <li>Username: [username]</li> 2860 <li>Role: [user role]</li> 2861 <li>IP Address: [IP address]</li> 2862 <li>Time: [timestamp]</li> 2863 </ul> 2864 <p><em>Beautiful HTML email with professional styling</em></p> 2865 </div> 2866 </div> 2867 </div> 2868 2869 <div class="template-preview"> 2870 <h4>❌ Failed Login Email</h4> 2871 <div class="template-content"> 2872 <div class="template-header danger"> 2873 <div class="template-logo">🚨</div> 2874 <h5>Security Alert</h5> 2875 <p>Failed Login Attempt Detected</p> 2876 </div> 2877 <div class="template-body"> 2878 <p><strong>Attempt Details:</strong></p> 2879 <ul> 2880 <li>Username: [username]</li> 2881 <li>IP Address: [IP address]</li> 2882 <li>Time: [timestamp]</li> 2883 </ul> 2884 <p><em>Red-themed alert email for security warnings</em></p> 2885 </div> 2886 </div> 2887 </div> 2888 </div> 2889 </div> 2890 </div> 2891 </div> 2892 2893 <script> 2894 function toggleEmailNotifications(checkbox) { 2895 jQuery.post(ajaxurl, { 2896 action: 'lockspire_toggle_email_notifications', 2897 enabled: checkbox.checked, 2898 nonce: '<?php echo esc_js(wp_create_nonce('lockspire_nonce')); ?>' 2899 }); 2900 } 2901 2902 function saveEmailSettings() { 2903 jQuery.post(ajaxurl, { 2904 action: 'lockspire_save_email_settings', 2905 notification_emails: jQuery('#notification_emails').val(), 2906 nonce: '<?php echo esc_js(wp_create_nonce('lockspire_nonce')); ?>' 2907 }, function(response) { 2908 alert('Email settings saved successfully!'); 2909 }); 2910 } 2911 2912 function testEmailNotification() { 2913 jQuery.post(ajaxurl, { 2914 action: 'lockspire_test_email', 2915 nonce: '<?php echo esc_js(wp_create_nonce('lockspire_nonce')); ?>' 2916 }, function(response) { 2917 alert('Test email sent! Check your inbox.'); 2918 }); 2919 } 2920 2921 function clearEmailLogs() { 2922 if (confirm('Clear all email logs?')) { 2923 jQuery.post(ajaxurl, { 2924 action: 'lockspire_clear_email_logs', 2925 nonce: '<?php echo esc_js(wp_create_nonce('lockspire_nonce')); ?>' 2926 }, function() { 2927 location.reload(); 2928 }); 2929 } 2930 } 2931 </script> 2932 <?php 2933 $this->render_admin_footer(); 2934 } 2935 2936 // Login protection page 2937 public function login_page() { 2938 $this->render_admin_header('Login Protection'); 2939 2940 $login_attempts = $this->get_recent_login_attempts(); 2941 $locked_ips = $this->get_locked_ips(); 2942 2943 ?> 2944 <div class="lockspire-login"> 2945 <div class="lockspire-grid"> 2946 <div class="lockspire-card"> 2947 <div class="lockspire-card-header"> 2948 <h3>🚪 Login Protection Status</h3> 2949 <div class="login-toggle"> 2950 <label class="switch"> 2951 <input type="checkbox" <?php echo $this->options['login_limit_enabled'] ? 'checked' : ''; ?> onchange="toggleLoginProtection(this)"> 2952 <span class="slider"></span> 2953 </label> 2954 <span>Login Protection Enabled</span> 2955 </div> 2956 </div> 2957 <div class="lockspire-card-body"> 2958 <div class="login-settings"> 2959 <div class="setting-item"> 2960 <label>Max Login Attempts:</label> 2961 <input type="number" id="max_attempts" value="<?php echo esc_attr($this->options['max_login_attempts']); ?>" min="1" max="20"> 2962 </div> 2963 <div class="setting-item"> 2964 <label>Lockout Time (minutes):</label> 2965 <input type="number" id="lockout_time" value="<?php echo esc_attr($this->options['lockout_time']); ?>" min="1" max="1440"> 2966 </div> 2967 <button type="button" class="button button-primary" onclick="saveLoginSettings()"> 2968 Save Settings 2969 </button> 2970 </div> 2971 </div> 2972 </div> 2973 2974 <div class="lockspire-card"> 2975 <div class="lockspire-card-header"> 2976 <h3>🔒 Locked IPs</h3> 2977 <button type="button" class="button" onclick="unlockAllIPs()"> 2978 Unlock All 2979 </button> 2980 </div> 2981 <div class="lockspire-card-body"> 2982 <?php if (empty($locked_ips)): ?> 2983 <p>No IPs are currently locked.</p> 2984 <?php else: ?> 2985 <div class="locked-ips-list"> 2986 <?php foreach ($locked_ips as $ip => $data): ?> 2987 <div class="locked-ip-item"> 2988 <div class="ip-info"> 2989 <div class="ip-address"><?php echo esc_html($ip); ?></div> 2990 <div class="ip-attempts"><?php echo esc_html($data['attempts']); ?> attempts</div> 2991 <div class="ip-time">Locked until: <?php echo esc_html($data['unlock_time']); ?></div> 2992 </div> 2993 <div class="ip-actions"> 2994 <button type="button" class="button button-small" onclick="unlockIP('<?php echo esc_js($ip); ?>')"> 2995 Unlock Now 2996 </button> 2997 </div> 2998 </div> 2999 <?php endforeach; ?> 3000 </div> 3001 <?php endif; ?> 3002 </div> 3003 </div> 3004 </div> 3005 3006 <div class="lockspire-card"> 3007 <div class="lockspire-card-header"> 3008 <h3>📊 Recent Login Attempts</h3> 3009 </div> 3010 <div class="lockspire-card-body"> 3011 <?php if (empty($login_attempts)): ?> 3012 <p>No recent login attempts recorded.</p> 3013 <?php else: ?> 3014 <div class="login-attempts-table"> 3015 <table class="wp-list-table widefat fixed striped"> 3016 <thead> 3017 <tr> 3018 <th>IP Address</th> 3019 <th>Username</th> 3020 <th>Attempts</th> 3021 <th>Last Attempt</th> 3022 <th>Status</th> 3023 </tr> 3024 </thead> 3025 <tbody> 3026 <?php foreach ($login_attempts as $ip => $data): ?> 3027 <tr> 3028 <td><?php echo esc_html($ip); ?></td> 3029 <td><?php echo esc_html($data['username']); ?></td> 3030 <td><?php echo esc_html($data['attempts']); ?></td> 3031 <td><?php echo esc_html($data['last_attempt']); ?></td> 3032 <td> 3033 <?php if ($data['locked']): ?> 3034 <span class="status-locked">🔒 Locked</span> 3035 <?php else: ?> 3036 <span class="status-active">✅ Active</span> 3037 <?php endif; ?> 3038 </td> 3039 </tr> 3040 <?php endforeach; ?> 3041 </tbody> 3042 </table> 3043 </div> 3044 <?php endif; ?> 3045 </div> 3046 </div> 3047 </div> 3048 3049 <script> 3050 function toggleLoginProtection(checkbox) { 3051 jQuery.post(ajaxurl, { 3052 action: 'lockspire_toggle_login_protection', 3053 enabled: checkbox.checked, 3054 nonce: '<?php echo esc_js(wp_create_nonce('lockspire_nonce')); ?>' 3055 }); 3056 } 3057 3058 function saveLoginSettings() { 3059 jQuery.post(ajaxurl, { 3060 action: 'lockspire_save_login_settings', 3061 max_attempts: jQuery('#max_attempts').val(), 3062 lockout_time: jQuery('#lockout_time').val(), 3063 nonce: '<?php echo esc_js(wp_create_nonce('lockspire_nonce')); ?>' 3064 }, function() { 3065 alert('Settings saved successfully!'); 3066 location.reload(); 3067 }); 3068 } 3069 3070 function unlockIP(ip) { 3071 if (confirm('Unlock IP ' + ip + '?')) { 3072 jQuery.post(ajaxurl, { 3073 action: 'lockspire_unlock_ip', 3074 ip: ip, 3075 nonce: '<?php echo esc_js(wp_create_nonce('lockspire_nonce')); ?>' 3076 }, function() { 3077 location.reload(); 3078 }); 3079 } 3080 } 3081 3082 function unlockAllIPs() { 3083 if (confirm('Unlock all locked IPs?')) { 3084 jQuery.post(ajaxurl, { 3085 action: 'lockspire_unlock_all_ips', 3086 nonce: '<?php echo esc_js(wp_create_nonce('lockspire_nonce')); ?>' 3087 }, function() { 3088 location.reload(); 3089 }); 3090 } 3091 } 3092 </script> 3093 <?php 3094 $this->render_admin_footer(); 3095 } 3096 3097 // Activity log page 3098 public function activity_page() { 3099 $this->render_admin_header('Activity Log'); 3100 3101 // Handle actions 3102 if (isset($_GET['action'])) { 3103 if ($_GET['action'] === 'clear_logs') { 3104 update_option($this->activity_log_key, array()); 3105 echo '<div class="notice notice-success"><p>Activity logs cleared.</p></div>'; 3106 } elseif ($_GET['action'] === 'export') { 3107 $this->export_activity_log(); 3108 return; 3109 } 3110 } 3111 3112 $activity_log = get_option($this->activity_log_key, array()); 3113 3114 ?> 3115 <div class="lockspire-activity"> 3116 <div class="lockspire-card"> 3117 <div class="lockspire-card-header"> 3118 <h3>📝 Activity Log</h3> 3119 <div class="activity-actions"> 3120 <button type="button" class="button" onclick="clearLogs()"> 3121 🗑️ Clear Logs 3122 </button> 3123 <button type="button" class="button" onclick="exportLogs()"> 3124 📊 Export CSV 3125 </button> 3126 </div> 3127 </div> 3128 <div class="lockspire-card-body"> 3129 <?php if (empty($activity_log)): ?> 3130 <p class="no-activity">No activity recorded yet.</p> 3131 <?php else: ?> 3132 <div class="activity-filters"> 3133 <select id="filter_user" onchange="filterLogs()"> 3134 <option value="">All Users</option> 3135 <?php 3136 $users = array(); 3137 foreach ($activity_log as $event) { 3138 $users[$event['user_login']] = $event['user_login']; 3139 } 3140 foreach ($users as $user): ?> 3141 <option value="<?php echo esc_attr($user); ?>"><?php echo esc_html($user); ?></option> 3142 <?php endforeach; ?> 3143 </select> 3144 <select id="filter_role" onchange="filterLogs()"> 3145 <option value="">All Roles</option> 3146 <?php 3147 $roles = array(); 3148 foreach ($activity_log as $event) { 3149 $roles[$event['user_role']] = $event['user_role']; 3150 } 3151 foreach ($roles as $role): ?> 3152 <option value="<?php echo esc_attr($role); ?>"><?php echo esc_html($role); ?></option> 3153 <?php endforeach; ?> 3154 </select> 3155 <input type="date" id="filter_date" onchange="filterLogs()"> 3156 </div> 3157 3158 <div class="activity-list" id="activity-list"> 3159 <?php foreach ($activity_log as $index => $event): ?> 3160 <div class="activity-item" data-user="<?php echo esc_attr($event['user_login']); ?>" data-role="<?php echo esc_attr($event['user_role']); ?>" data-date="<?php echo esc_attr(gmdate('Y-m-d', strtotime($event['time']))); ?>"> 3161 <div class="activity-icon">👤</div> 3162 <div class="activity-details"> 3163 <div class="activity-user"><?php echo esc_html($event['user_login']); ?></div> 3164 <div class="activity-info"> 3165 <span class="role"><?php echo esc_html($event['user_role']); ?></span> 3166 <span class="ip"><?php echo esc_html($event['ip']); ?></span> 3167 </div> 3168 <div class="activity-time"><?php echo esc_html($event['time']); ?></div> 3169 <div class="activity-browser"><?php echo esc_html($event['user_agent']); ?></div> 3170 </div> 3171 </div> 3172 <?php endforeach; ?> 3173 </div> 3174 <?php endif; ?> 3175 </div> 3176 </div> 3177 </div> 3178 3179 <script> 3180 function clearLogs() { 3181 if (confirm('Are you sure you want to clear all activity logs?')) { 3182 window.location.href = '?page=lockspire-activity&action=clear_logs'; 3183 } 3184 } 3185 3186 function exportLogs() { 3187 window.location.href = '?page=lockspire-activity&action=export'; 3188 } 3189 3190 function filterLogs() { 3191 var user = jQuery('#filter_user').val(); 3192 var role = jQuery('#filter_role').val(); 3193 var date = jQuery('#filter_date').val(); 3194 3195 jQuery('.activity-item').each(function() { 3196 var show = true; 3197 3198 if (user && jQuery(this).data('user') !== user) show = false; 3199 if (role && jQuery(this).data('role') !== role) show = false; 3200 if (date && jQuery(this).data('date') !== date) show = false; 3201 3202 jQuery(this).toggle(show); 3203 }); 3204 } 3205 </script> 3206 <?php 3207 $this->render_admin_footer(); 3208 } 3209 3210 // Settings page 3211 public function settings_page() { 3212 $this->render_admin_header('Settings'); 3213 ?> 3214 <div class="lockspire-settings"> 3215 <form method="post" action="options.php"> 3216 <?php 3217 settings_fields('lockspire_options'); 3218 do_settings_sections('lockspire-settings'); 3219 ?> 3220 3221 <div class="lockspire-grid"> 3222 <div class="lockspire-card"> 3223 <div class="lockspire-card-header"> 3224 <h3>🔒 Core Security Settings</h3> 3225 </div> 3226 <div class="lockspire-card-body"> 3227 <table class="form-table"> 3228 <tr> 3229 <th scope="row">Login Protection</th> 3230 <td> 3231 <label class="switch"> 3232 <input type="checkbox" name="lockspire_options[login_limit_enabled]" value="1" <?php checked($this->options['login_limit_enabled'], '1'); ?>> 3233 <span class="slider"></span> 3234 </label> 3235 <span class="setting-description">Limit login attempts to prevent brute force attacks</span> 3236 </td> 3237 </tr> 3238 <tr> 3239 <th scope="row">Max Login Attempts</th> 3240 <td> 3241 <input type="number" name="lockspire_options[max_login_attempts]" value="<?php echo esc_attr($this->options['max_login_attempts']); ?>" min="1" max="20"> 3242 <p class="description">Maximum failed login attempts before lockout</p> 3243 </td> 3244 </tr> 3245 <tr> 3246 <th scope="row">Lockout Time</th> 3247 <td> 3248 <input type="number" name="lockspire_options[lockout_time]" value="<?php echo esc_attr($this->options['lockout_time']); ?>" min="1" max="1440"> 3249 <p class="description">Lockout duration in minutes</p> 3250 </td> 3251 </tr> 3252 </table> 3253 </div> 3254 </div> 3255 3256 <div class="lockspire-card"> 3257 <div class="lockspire-card-header"> 3258 <h3>🔥 Firewall Settings</h3> 3259 </div> 3260 <div class="lockspire-card-body"> 3261 <table class="form-table"> 3262 <tr> 3263 <th scope="row">Enable Firewall</th> 3264 <td> 3265 <label class="switch"> 3266 <input type="checkbox" name="lockspire_options[firewall_enabled]" value="1" <?php checked($this->options['firewall_enabled'], '1'); ?>> 3267 <span class="slider"></span> 3268 </label> 3269 <span class="setting-description">Block malicious requests and bad bots</span> 3270 </td> 3271 </tr> 3272 <tr> 3273 <th scope="row">Disable XML-RPC</th> 3274 <td> 3275 <label class="switch"> 3276 <input type="checkbox" name="lockspire_options[disable_xmlrpc]" value="1" <?php checked($this->options['disable_xmlrpc'], '1'); ?>> 3277 <span class="slider"></span> 3278 </label> 3279 <span class="setting-description">Disable XML-RPC to prevent attacks</span> 3280 </td> 3281 </tr> 3282 </table> 3283 </div> 3284 </div> 3285 </div> 3286 3287 <div class="lockspire-grid"> 3288 <div class="lockspire-card"> 3289 <div class="lockspire-card-header"> 3290 <h3>👻 Admin Protection</h3> 3291 </div> 3292 <div class="lockspire-card-body"> 3293 <table class="form-table"> 3294 <tr> 3295 <th scope="row">Hide Admin URL</th> 3296 <td> 3297 <label class="switch"> 3298 <input type="checkbox" name="lockspire_options[hide_admin_enabled]" value="1" <?php checked($this->options['hide_admin_enabled'], '1'); ?>> 3299 <span class="slider"></span> 3300 </label> 3301 <span class="setting-description">Hide wp-admin with secret key</span> 3302 </td> 3303 </tr> 3304 <tr> 3305 <th scope="row">Admin Secret Key</th> 3306 <td> 3307 <input type="text" name="lockspire_options[admin_secret_key]" value="<?php echo esc_attr($this->options['admin_secret_key']); ?>" class="regular-text"> 3308 <p class="description">Access wp-admin at: /wp-admin/?<?php echo esc_html($this->options['admin_secret_key']); ?>=1</p> 3309 </td> 3310 </tr> 3311 </table> 3312 </div> 3313 </div> 3314 3315 <div class="lockspire-card"> 3316 <div class="lockspire-card-header"> 3317 <h3>📧 Notifications & Logging</h3> 3318 </div> 3319 <div class="lockspire-card-body"> 3320 <table class="form-table"> 3321 <tr> 3322 <th scope="row">Email Notifications</th> 3323 <td> 3324 <label class="switch"> 3325 <input type="checkbox" name="lockspire_options[email_notifications]" value="1" <?php checked($this->options['email_notifications'], '1'); ?>> 3326 <span class="slider"></span> 3327 </label> 3328 <span class="setting-description">Send email alerts for admin logins</span> 3329 </td> 3330 </tr> 3331 <tr> 3332 <th scope="row">Activity Logging</th> 3333 <td> 3334 <label class="switch"> 3335 <input type="checkbox" name="lockspire_options[activity_log_enabled]" value="1" <?php checked($this->options['activity_log_enabled'], '1'); ?>> 3336 <span class="slider"></span> 3337 </label> 3338 <span class="setting-description">Log user login activity</span> 3339 </td> 3340 </tr> 3341 </table> 3342 </div> 3343 </div> 3344 </div> 3345 3346 <div class="lockspire-card"> 3347 <div class="lockspire-card-header"> 3348 <h3>💎 Pro Features (Coming Soon)</h3> 3349 </div> 3350 <div class="lockspire-card-body"> 3351 <div class="pro-features"> 3352 <div class="pro-feature"> 3353 <div class="pro-icon">🔍</div> 3354 <div class="pro-info"> 3355 <h4>Malware Scanner</h4> 3356 <p>Advanced malware detection and removal</p> 3357 <span class="pro-badge">PRO</span> 3358 </div> 3359 </div> 3360 <div class="pro-feature"> 3361 <div class="pro-icon">🚫</div> 3362 <div class="pro-info"> 3363 <h4>IP Address Blocking</h4> 3364 <p>Advanced IP blocking and geolocation</p> 3365 <span class="pro-badge">PRO</span> 3366 </div> 3367 </div> 3368 <div class="pro-feature"> 3369 <div class="pro-icon">🌍</div> 3370 <div class="pro-info"> 3371 <h4>Country Blocking</h4> 3372 <p>Block access from specific countries</p> 3373 <span class="pro-badge">PRO</span> 3374 </div> 3375 </div> 3376 </div> 3377 </div> 3378 </div> 3379 3380 <?php submit_button('Save Settings', 'primary', 'submit', true, array('class' => 'button-large')); ?> 3381 </form> 3382 </div> 3383 <?php 3384 $this->render_admin_footer(); 3385 } 3386 3387 // Helper methods for email functionality 3388 private function get_email_stats() { 3389 $email_logs = get_option('lockspire_email_logs', array()); 3390 $today = gmdate('Y-m-d'); 3391 3392 $stats = array( 3393 'total_sent' => count($email_logs), 3394 'login_alerts' => 0, 3395 'failed_attempts' => 0, 3396 'today' => 0 3397 ); 3398 3399 foreach ($email_logs as $log) { 3400 if ($log['type'] === 'login') { 3401 $stats['login_alerts']++; 3402 } elseif ($log['type'] === 'failed_login') { 3403 $stats['failed_attempts']++; 3404 } 3405 3406 if (gmdate('Y-m-d', strtotime($log['time'])) === $today) { 3407 $stats['today']++; 3408 } 3409 } 3410 3411 return $stats; 3412 } 3413 3414 private function log_email_sent($type, $recipient, $subject, $status = 'sent') { 3415 $email_logs = get_option('lockspire_email_logs', array()); 3416 3417 $log_entry = array( 3418 'type' => $type, 3419 'recipient' => $recipient, 3420 'subject' => $subject, 3421 'status' => $status, 3422 'time' => current_time('mysql') 3423 ); 3424 3425 array_unshift($email_logs, $log_entry); 3426 3427 // Keep only last 50 entries 3428 $email_logs = array_slice($email_logs, 0, 50); 3429 3430 update_option('lockspire_email_logs', $email_logs); 3431 } 3432 private function run_security_scan() { 3433 $results = array( 3434 'timestamp' => current_time('mysql'), 3435 'critical' => 0, 3436 'warnings' => 0, 3437 'info' => 0, 3438 'good' => 0, 3439 'details' => array() 3440 ); 3441 3442 // Check WordPress version 3443 global $wp_version; 3444 if (version_compare($wp_version, '6.0', '<')) { 3445 $results['critical']++; 3446 $results['details'][] = array( 3447 'severity' => 'critical', 3448 'icon' => '🔴', 3449 'title' => 'Outdated WordPress Version', 3450 'description' => "Your WordPress version ($wp_version) is outdated and may contain security vulnerabilities.", 3451 'recommendation' => 'Update WordPress to the latest version immediately.' 3452 ); 3453 } else { 3454 $results['good']++; 3455 $results['details'][] = array( 3456 'severity' => 'good', 3457 'icon' => '✅', 3458 'title' => 'WordPress Version', 3459 'description' => "WordPress version ($wp_version) is up to date.", 3460 'recommendation' => '' 3461 ); 3462 } 3463 3464 // Check admin URL hiding 3465 if (!$this->options['hide_admin_enabled']) { 3466 $results['warnings']++; 3467 $results['details'][] = array( 3468 'severity' => 'warning', 3469 'icon' => '🟡', 3470 'title' => 'Admin URL Not Hidden', 3471 'description' => 'Your wp-admin URL is accessible to everyone.', 3472 'recommendation' => 'Enable admin URL hiding to protect against direct attacks.' 3473 ); 3474 } 3475 3476 // Check XML-RPC 3477 if (!$this->options['disable_xmlrpc']) { 3478 $results['warnings']++; 3479 $results['details'][] = array( 3480 'severity' => 'warning', 3481 'icon' => '🟡', 3482 'title' => 'XML-RPC Enabled', 3483 'description' => 'XML-RPC is enabled and can be exploited.', 3484 'recommendation' => 'Disable XML-RPC to prevent XML-RPC attacks.' 3485 ); 3486 } 3487 3488 // Check file permissions 3489 $wp_config = ABSPATH . 'wp-config.php'; 3490 if (file_exists($wp_config)) { 3491 $perms = fileperms($wp_config); 3492 if (($perms & 0x0077) != 0) { 3493 $results['warnings']++; 3494 $results['details'][] = array( 3495 'severity' => 'warning', 3496 'icon' => '🟡', 3497 'title' => 'File Permissions', 3498 'description' => 'wp-config.php has world-writable permissions.', 3499 'recommendation' => 'Change file permissions to 640 or 600.' 3500 ); 3501 } 3502 } 3503 3504 // Check debug mode 3505 if (defined('WP_DEBUG') && WP_DEBUG) { 3506 $results['info']++; 3507 $results['details'][] = array( 3508 'severity' => 'info', 3509 'icon' => 'ℹ️', 3510 'title' => 'Debug Mode Enabled', 3511 'description' => 'WordPress debug mode is enabled on a production site.', 3512 'recommendation' => 'Disable debug mode in production.' 3513 ); 3514 } 3515 3516 return $results; 3517 } 3518 3519 private function get_default_firewall_rules() { 3520 return array( 3521 'block_bad_bots' => array( 3522 'name' => 'Block Bad Bots', 3523 'description' => 'Block known malicious bots and crawlers', 3524 'enabled' => true, 3525 'severity' => 'high' 3526 ), 3527 'block_sql_injection' => array( 3528 'name' => 'Block SQL Injection', 3529 'description' => 'Block common SQL injection patterns', 3530 'enabled' => true, 3531 'severity' => 'critical' 3532 ), 3533 'block_xss' => array( 3534 'name' => 'Block XSS Attacks', 3535 'description' => 'Block cross-site scripting attempts', 3536 'enabled' => true, 3537 'severity' => 'high' 3538 ), 3539 'block_directory_traversal' => array( 3540 'name' => 'Block Directory Traversal', 3541 'description' => 'Block directory traversal attacks', 3542 'enabled' => true, 3543 'severity' => 'medium' 3544 ), 3545 'block_file_inclusion' => array( 3546 'name' => 'Block File Inclusion', 3547 'description' => 'Block local and remote file inclusion attempts', 3548 'enabled' => true, 3549 'severity' => 'high' 3550 ) 3551 ); 3552 } 3553 3554 private function get_blocked_requests_today() { 3555 $blocked = get_option('lockspire_blocked_today', 0); 3556 return $blocked; 3557 } 3558 3559 private function get_recent_login_attempts() { 3560 $attempts = array(); 3561 3562 // Try to get from cache first 3563 $cache_key = 'lockspire_recent_login_attempts'; 3564 $cached_attempts = wp_cache_get($cache_key); 3565 3566 if ($cached_attempts !== false) { 3567 return $cached_attempts; 3568 } 3569 3570 // Get all transients using WordPress function 3571 global $wpdb; 3572 $transient_names = $wpdb->get_col( 3573 "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_lockspire_login_attempts_%'" 3574 ); 3575 3576 foreach ($transient_names as $transient_name) { 3577 // Remove the _transient_ prefix to get the actual transient key 3578 $transient_key = str_replace('_transient_', '', $transient_name); 3579 $data = get_transient($transient_key); 3580 3581 if (is_array($data) && !empty($data)) { 3582 $ip = str_replace('lockspire_login_attempts_', '', $transient_key); 3583 $last_attempt = end($data); 3584 $attempts[$ip] = array( 3585 'username' => $last_attempt['username'], 3586 'attempts' => count($data), 3587 'last_attempt' => gmdate('Y-m-d H:i:s', $last_attempt['time']), 3588 'locked' => count($data) >= intval($this->options['max_login_attempts']) 3589 ); 3590 } 3591 } 3592 3593 // Cache for 5 minutes 3594 wp_cache_set($cache_key, $attempts, '', 300); 3595 3596 return $attempts; 3597 } 3598 3599 private function get_locked_ips() { 3600 $locked = array(); 3601 $attempts = $this->get_recent_login_attempts(); 3602 3603 foreach ($attempts as $ip => $data) { 3604 if ($data['locked']) { 3605 $unlock_time = time() + (intval($this->options['lockout_time']) * 60); 3606 $locked[$ip] = array( 3607 'attempts' => $data['attempts'], 3608 'unlock_time' => gmdate('Y-m-d H:i:s', $unlock_time) 3609 ); 3610 } 3611 } 3612 3613 return $locked; 3614 } 3615 3616 private function export_activity_log() { 3617 global $wp_filesystem; 3618 if (empty($wp_filesystem)) { 3619 require_once(ABSPATH . '/wp-admin/includes/file.php'); 3620 WP_Filesystem(); 3621 } 3622 3623 $activity_log = get_option($this->activity_log_key, array()); 3624 3625 header('Content-Type: text/csv'); 3626 header('Content-Disposition: attachment; filename="lockspire-activity-log.csv"'); 3627 3628 $output = fopen('php://output', 'w'); 3629 3630 // CSV header 3631 fputcsv($output, array('User Login', 'User Role', 'IP Address', 'Time', 'User Agent')); 3632 3633 // CSV data 3634 foreach ($activity_log as $event) { 3635 fputcsv($output, array( 3636 $event['user_login'], 3637 $event['user_role'], 3638 $event['ip'], 3639 $event['time'], 3640 $event['user_agent'] 3641 )); 3642 } 3643 3644 $wp_filesystem->put_contents('php://output', '', false); // Close the output stream 3645 exit; 3646 } 3647 3648 // AJAX handlers 3649 // AJAX handlers for email functionality 3650 public function ajax_toggle_email_notifications() { 3651 check_ajax_referer('lockspire_nonce', 'nonce'); 3652 3653 if (!current_user_can('manage_options')) { 3654 wp_die('Permission denied'); 3655 } 3656 3657 $this->options['email_notifications'] = $_POST['enabled'] ? '1' : '0'; 3658 update_option('lockspire_options', $this->options); 3659 3660 wp_die('Email notifications updated'); 3661 } 3662 3663 public function ajax_save_email_settings() { 3664 check_ajax_referer('lockspire_nonce', 'nonce'); 3665 3666 if (!current_user_can('manage_options')) { 3667 wp_die('Permission denied'); 3668 } 3669 3670 $notification_emails = sanitize_textarea_field($_POST['notification_emails']); 3671 update_option('lockspire_notification_emails', $notification_emails); 3672 3673 wp_die('Email settings saved'); 3674 } 3675 3676 public function ajax_test_email() { 3677 check_ajax_referer('lockspire_nonce', 'nonce'); 3678 3679 if (!current_user_can('manage_options')) { 3680 wp_die('Permission denied'); 3681 } 3682 3683 $current_user = wp_get_current_user(); 3684 $this->send_admin_login_notification($current_user->user_login, $current_user); 3685 3686 wp_die('Test email sent'); 3687 } 3688 3689 public function ajax_clear_email_logs() { 3690 check_ajax_referer('lockspire_nonce', 'nonce'); 3691 3692 if (!current_user_can('manage_options')) { 3693 wp_die('Permission denied'); 3694 } 3695 3696 update_option('lockspire_email_logs', array()); 3697 3698 wp_die('Email logs cleared'); 3699 } 3700 3701 public function ajax_block_ip() { 3702 check_ajax_referer('lockspire_nonce', 'nonce'); 3703 3704 if (!current_user_can('manage_options')) { 3705 wp_die('Permission denied'); 3706 } 3707 3708 $ip = sanitize_text_field($_POST['ip']); 3709 $reason = sanitize_text_field($_POST['reason']); 3710 3711 $blocked_ips = get_option('lockspire_blocked_ips', array()); 3712 $blocked_ips[$ip] = array( 3713 'reason' => $reason, 3714 'time' => current_time('mysql') 3715 ); 3716 update_option('lockspire_blocked_ips', $blocked_ips); 3717 3718 wp_die('IP blocked'); 3719 } 3720 3721 public function ajax_unblock_ip() { 3722 check_ajax_referer('lockspire_nonce', 'nonce'); 3723 3724 if (!current_user_can('manage_options')) { 3725 wp_die('Permission denied'); 3726 } 3727 3728 $ip = sanitize_text_field($_POST['ip']); 3729 $blocked_ips = get_option('lockspire_blocked_ips', array()); 3730 unset($blocked_ips[$ip]); 3731 update_option('lockspire_blocked_ips', $blocked_ips); 3732 3733 wp_die('IP unblocked'); 3734 } 3735 3736 public function ajax_toggle_rule() { 3737 check_ajax_referer('lockspire_nonce', 'nonce'); 3738 3739 if (!current_user_can('manage_options')) { 3740 wp_die('Permission denied'); 3741 } 3742 3743 $rule_id = sanitize_text_field($_POST['rule_id']); 3744 $enabled = $_POST['enabled'] ? true : false; 3745 3746 $firewall_rules = get_option('lockspire_firewall_rules', $this->get_default_firewall_rules()); 3747 if (isset($firewall_rules[$rule_id])) { 3748 $firewall_rules[$rule_id]['enabled'] = $enabled; 3749 update_option('lockspire_firewall_rules', $firewall_rules); 3750 } 3751 3752 wp_die('Rule updated'); 3753 } 3754 3755 public function ajax_toggle_login_protection() { 3756 check_ajax_referer('lockspire_nonce', 'nonce'); 3757 3758 if (!current_user_can('manage_options')) { 3759 wp_die('Permission denied'); 3760 } 3761 3762 $this->options['login_limit_enabled'] = $_POST['enabled'] ? '1' : '0'; 3763 update_option('lockspire_options', $this->options); 3764 3765 wp_die('Login protection updated'); 3766 } 3767 3768 public function ajax_save_login_settings() { 3769 check_ajax_referer('lockspire_nonce', 'nonce'); 3770 3771 if (!current_user_can('manage_options')) { 3772 wp_die('Permission denied'); 3773 } 3774 3775 $this->options['max_login_attempts'] = absint($_POST['max_attempts']); 3776 $this->options['lockout_time'] = absint($_POST['lockout_time']); 3777 update_option('lockspire_options', $this->options); 3778 3779 wp_die('Settings saved'); 3780 } 3781 3782 public function ajax_unlock_ip() { 3783 check_ajax_referer('lockspire_nonce', 'nonce'); 3784 3785 if (!current_user_can('manage_options')) { 3786 wp_die('Permission denied'); 3787 } 3788 3789 $ip = sanitize_text_field($_POST['ip']); 3790 delete_transient($this->login_attempts_key . '_' . $ip); 3791 3792 wp_die('IP unlocked'); 3793 } 3794 3795 public function ajax_unlock_all_ips() { 3796 check_ajax_referer('lockspire_nonce', 'nonce'); 3797 3798 if (!current_user_can('manage_options')) { 3799 wp_die('Permission denied'); 3800 } 3801 3802 // Clear all login attempt transients using WordPress functions 3803 global $wpdb; 3804 $transient_names = $wpdb->get_col( 3805 "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_lockspire_login_attempts_%'" 3806 ); 3807 3808 foreach ($transient_names as $transient_name) { 3809 // Remove the _transient_ prefix to get the actual transient key 3810 $transient_key = str_replace('_transient_', '', $transient_name); 3811 delete_transient($transient_key); 3812 } 3813 3814 // Clear cache 3815 wp_cache_delete('lockspire_recent_login_attempts'); 3816 wp_cache_delete('lockspire_today_login_attempts_' . gmdate('Y-m-d')); 3817 3818 wp_die('All IPs unlocked'); 3819 } 3820 3821 // Close content wrapper 3822 private function render_admin_footer() { 3823 ?> 3824 </div> 3825 </div> 3826 <?php 3827 } 3828 3829 // Plugin activation/deactivation 3830 public function activate() { 3831 // Set default options 3832 $this->set_default_options(); 3833 3834 // Set default firewall rules 3835 update_option('lockspire_firewall_rules', $this->get_default_firewall_rules()); 3836 3837 // Flush rewrite rules if needed 3838 flush_rewrite_rules(); 3839 } 3840 3841 public function deactivate() { 3842 // Clean up all Lockspire transients using WordPress functions 3843 global $wpdb; 3844 $transient_names = $wpdb->get_col( 3845 "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_lockspire_%'" 3846 ); 3847 3848 foreach ($transient_names as $transient_name) { 3849 // Remove the _transient_ prefix to get the actual transient key 3850 $transient_key = str_replace('_transient_', '', $transient_name); 3851 delete_transient($transient_key); 3852 } 3853 3854 // Clear all Lockspire cache 3855 wp_cache_flush(); 3856 3857 // Flush rewrite rules 3858 flush_rewrite_rules(); 85 3859 } 86 3860 } 87 3861 88 // Activation Hook - Set default options and create database table 89 register_activation_hook(__FILE__, 'lockspire_activate_plugin'); 90 function lockspire_activate_plugin() { 91 lockspire_set_default_options(); 92 lockspire_create_database_table(); 93 } 94 95 function lockspire_set_default_options() { 96 $defaults = [ 97 'lockspire_max_attempts' => 5, 98 'lockspire_lockout_time' => 15, 99 'lockspire_reset_time' => 60, 100 'lockspire_lockout_message' => 'Too many login attempts. Please try again later.', 101 'lockspire_notify_admin' => 0, 102 'lockspire_admin_email' => get_option('admin_email'), 103 'lockspire_notify_threshold' => 3, 104 'lockspire_whitelist_ips' => '', 105 'lockspire_blacklist_ips' => '', 106 'lockspire_enable_country_blocking' => 0, 107 'lockspire_blocked_countries' => '' 108 ]; 109 110 foreach ($defaults as $option => $default) { 111 if (!get_option($option)) { 112 update_option($option, $default); 113 } 114 } 115 } 116 117 // Add settings link in admin menu 118 add_action('admin_menu', 'lockspire_add_settings_menu'); 119 function lockspire_add_settings_menu() { 120 add_menu_page( 121 'Lockspire – Smart Access Protection', 122 'Lockspire', 123 'manage_options', 124 'lockspire', 125 'lockspire_settings_page', 126 'dashicons-shield', 127 80 128 ); 129 } 3862 // Initialize the plugin 3863 new Lockspire_Security(); -
lockspire-smart-access-protection/trunk/readme.txt
r3459106 r3462265 8 8 License URI: https://www.gnu.org/licenses/gpl-2.0.html 9 9 10 Lockspire – Limit Login And Smart Access Protection helps protect your WordPress site by limiting login attempts and blocking suspicious users. It makes your website safer and prevents unauthorized access easily.11 12 10 == Description == 13 11 14 12 Lockspire – Limit Login And Smart Access Protection helps protect your WordPress site by limiting login attempts and blocking suspicious users. It makes your website safer and prevents unauthorized access easily. 15 13 16 **Key Features:**17 14 18 * 🛡️ **Advanced Protection**: Configurable login attempt limits with customizable lockout durations 19 * 📊 **Real-time Dashboard**: Beautiful dashboard with live statistics and security monitoring 20 * 🔒 **IP Management**: Whitelist and blacklist IP addresses for granular control 21 * 🌍 **Country Blocking**: Block login attempts from specific countries 22 * 📧 **Smart Notifications**: Email alerts for security events with customizable thresholds 23 * 📋 **Comprehensive Logging**: Detailed activity logs with export functionality 24 * 🎨 **Modern Interface**: Beautiful, responsive admin interface with tabbed navigation 25 * ⚡ **Performance Optimized**: Smart caching for optimal database performance 26 * 🔧 **Developer Friendly**: Clean, well-documented code following WordPress standards 15 **Core Features (Free):** 16 * ✅ **Login Attempt Limiting** - Configurable maximum attempts and lockout time 17 * ✅ **Admin URL Hiding** - Hide wp-admin with custom secret key 18 * ✅ **XML-RPC Protection** - Complete XML-RPC disabling 19 * ✅ **Basic Firewall** - Block bad bots and common exploit attempts 20 * ✅ **Email Notifications** - Get alerts when admin users log in 21 * ✅ **Activity Logging** - Simple admin activity log (last 10 events) 22 23 **Pro Features (Coming Soon):** 24 * 🔍 Malware Scanner 25 * 🚫 IP Address Blocking 26 * 🌍 Country Blocking 27 28 == Why Choose Lockspire? == 29 30 * **Lightweight** - No database tables, minimal resource usage 31 * **Beginner Friendly** - Simple toggle switches and clear explanations 32 * **Install & Forget** - Set it up once and let it protect your site 33 * **Performance Optimized** - No slow queries or heavy processing 34 * **WordPress Standards** - Follows WordPress coding standards and best practices 27 35 28 36 == Installation == 29 37 30 1. Upload the `login-attempt-limiter` folder to the `/wp-content/plugins/` directory 31 2. Activate the plugin through the 'Plugins' menu in WordPress 32 3. Navigate to **Secure Guard** in your WordPress admin to configure settings 33 4. Adjust security settings according to your needs 38 1. Upload the plugin files to the `/wp-content/plugins/lockspire-security` directory, or install the plugin through the WordPress plugins screen directly. 39 2. Activate the plugin through the 'Plugins' screen in WordPress 40 3. Navigate to **Settings → Lockspire Security** to configure your security settings 41 42 == Configuration == 43 44 **Basic Setup:** 45 1. Go to Settings → Lockspire Security 46 2. Enable the features you need using the toggle switches 47 3. Configure your preferred settings: 48 - Set max login attempts (default: 5) 49 - Set lockout time in minutes (default: 15) 50 - Set custom admin secret key if hiding wp-admin 51 52 **Admin URL Hiding:** 53 When enabled, access your admin dashboard at: 54 `/wp-admin/?your-secret-key=1` 55 56 Replace `your-secret-key` with your custom secret key. 34 57 35 58 == Frequently Asked Questions == 36 59 37 = How does the plugin protect against brute-force attacks? =60 = Will this plugin slow down my website? = 38 61 39 The plugin limits the number of failed login attempts from a single IP address within a specified time period. When the limit is exceeded, the IP is temporarily locked out.62 No! Lockspire is designed to be extremely lightweight with minimal impact on site performance. 40 63 41 = Can I whitelist trusted IP addresses? =64 = Do I need technical knowledge to use this plugin? = 42 65 43 Yes! You can whitelist IP addresses in the Security tab. Whitelisted IPs will never be locked out, regardless of failed attempts.66 Not at all! Lockspire is designed for beginners with simple toggle switches and clear explanations for each feature. 44 67 45 = Does th e plugin work with multisite installations? =68 = Does this plugin create database tables? = 46 69 47 Yes, the plugin is fully compatible with WordPress multisite installations and can be activated network-wide.70 No, Lockspire uses the WordPress Options API and doesn't create any additional database tables. 48 71 49 = How do I receive security notifications? =72 = Can I use this alongside other security plugins? = 50 73 51 Enable email notifications in the Notifications tab. You can configure custom email addresses and notification thresholds to avoid alert fatigue.74 Yes, Lockspire is designed to work well with other plugins, but for optimal performance, we recommend using it as your primary security solution. 52 75 53 = Can I export login logs? =76 = What happens if I forget my admin secret key? = 54 77 55 Y es! The plugin allows you to export login activity logs in CSV format for analysis and record-keeping.78 You can still access wp-admin by disabling the "Hide wp-admin URL" feature via FTP or file manager in the plugin file. 56 79 57 80 == Screenshots == 58 81 59 1. Beautiful dashboard with real-time statistics 60 2. Comprehensive settings with tabbed interface 61 3. Detailed login activity logs 62 4. IP and country management options 63 5. Email notification configuration 82 1. Clean settings page with toggle switches 83 2. Activity log showing recent login events 84 3. Pro features placeholder section 64 85 65 86 == Changelog == 66 87 67 = 1.0 =88 = 1.0.0 = 68 89 * Initial release 69 * Advanced login attempt limiting 70 * Real-time dashboard with statistics 71 * IP whitelist/blacklist functionality 72 * Country blocking capabilities 73 * Email notification system 74 * Comprehensive logging with export 75 * Modern responsive admin interface 76 * Performance optimization with caching 77 * Full WordPress standards compliance 90 * Core security features implemented 91 * Lightweight, beginner-friendly interface 92 * Performance optimized implementation 78 93 79 94 == Upgrade Notice == 80 95 81 = 1.0 =82 Initial release of the Lockspire – Smart Access Protection plugin with comprehensive security features.96 = 1.0.0 = 97 Initial release of Lockspire Security Plugin. 83 98 84 99 == Additional Information == 85 100 86 **Plugin URI:** https://github.com/anilatgithub/login-attempt-limiter 87 **Author URI:** https://github.com/anilatgithub 88 **Text Domain:** lockspire 101 **Plugin Website:** https://lockspire.com 89 102 90 **Security Features:** 91 - CSRF protection with WordPress nonces 92 - Input sanitization and validation 93 - SQL injection prevention 94 - XSS protection 95 - Secure file handling 103 **Support:** For support and feature requests, please visit our website or contact us through the WordPress support forums. 96 104 97 **Performance Features:** 98 - Smart caching system 99 - Optimized database queries 100 - Minimal resource usage 101 - Efficient log management 105 **Privacy Policy:** Lockspire Security does not collect or transmit any personal data. All data is stored locally on your WordPress installation. 102 106 103 **Compatibility:** 104 - WordPress 5.0+ 105 - PHP 7.4+ 106 - Multisite compatible 107 - WooCommerce compatible 108 - Compatible with most caching plugins 107 == Developer Information == 109 108 110 **Support:** 111 For support and feature requests, please visit the GitHub repository or contact the developer. 109 This plugin follows WordPress coding standards and best practices: 110 * Proper escaping of all output 111 * Input validation and nonce verification 112 * No database queries or heavy processing 113 * Uses WordPress Options API for settings storage 114 * Compatible with multisite installations 115 116 **Hooks and Filters:** 117 The plugin provides several hooks for developers to extend functionality: 118 * `lockspire_before_firewall_check` - Runs before firewall checks 119 * `lockspire_after_login_attempt` - Runs after failed login attempt 120 * `lockspire_admin_notification_subject` - Filter admin notification email subject 121 * `lockspire_admin_notification_message` - Filter admin notification email message
Note: See TracChangeset
for help on using the changeset viewer.