Plugin Directory

Changeset 3462265


Ignore:
Timestamp:
02/16/2026 07:47:36 AM (6 weeks ago)
Author:
ghimireanil
Message:

Update plugin

Location:
lockspire-smart-access-protection/trunk
Files:
2 added
9 edited

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 {
    1211    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    1312    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 {
    1933    margin: 0;
    2034    font-size: 24px;
     
    2236}
    2337
    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;
    2763    font-size: 14px;
    2864}
    2965
    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;
    5268    color: #333;
    5369}
    5470
    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 {
    20572    background: #667eea;
    20673    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;
    210158    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
     180input:checked + .toggle-slider {
     181    background-color: #667eea;
     182}
     183
     184input: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;
    266224    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
     4function runSecurityScan() {
     5    window.location.href = '?page=lockspire-scanner&scan=1';
     6}
     7
     8function 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
     14function exportSecurityReport() {
     15    window.location.href = '?page=lockspire-activity&action=export';
     16}
     17
     18function refreshDashboard() {
     19    location.reload();
     20}
     21
     22// Login Protection Functions
     23function toggleLoginProtection(checkbox) {
     24    jQuery.post(ajaxurl, {
     25        action: 'lockspire_toggle_login_protection',
     26        enabled: checkbox.checked,
     27        nonce: lockspire_nonce
    1228    });
    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}
    2430
    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.')) {
     31function 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
     43function unlockIP(ip) {
     44    if (confirm('Unlock IP ' + ip + '?')) {
    2845        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();
    3851        });
    3952    }
    4053}
    4154
    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.')) {
     55function unlockAllIPs() {
     56    if (confirm('Unlock all locked IPs?')) {
    4857        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();
    5862        });
    5963    }
    6064}
    6165
    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
     67function toggleEmailNotifications(checkbox) {
     68    jQuery.post(ajaxurl, {
     69        action: 'lockspire_toggle_email_notifications',
     70        enabled: checkbox.checked,
     71        nonce: lockspire_nonce
     72    });
     73}
     74
     75function 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
     86function 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
     95function clearEmailLogs() {
     96    if (confirm('Clear all email logs?')) {
    7397        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();
    90102        });
    91103    }
    92104}
    93105
    94 function lockspireDownloadLogs() {
    95     window.location.href = ajaxurl + '?action=lockspire_download_logs&nonce=' + lockspire_admin.nonce;
     106// Activity Log Functions
     107function 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    }
    96111}
    97112
    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         }
     113function exportLogs() {
     114    window.location.href = '?page=lockspire-activity&action=export';
     115}
     116
     117function 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);
    110130    });
    111131}
    112132
    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
     134function blockIP(ip) {
     135    var reason = prompt('Enter reason for blocking:');
     136    if (reason) {
    151137        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();
    163145        });
    164146    }
    165147}
    166148
    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');
     149function 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
     162function 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  
    11<?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
    311
    4 // Register AJAX handlers
    5 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 API
    33     $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 clearing
    78     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 construction
    91     $wpdb->query("TRUNCATE TABLE `" . esc_sql($table_name) . "`");
    92    
    93     // Clear all related caches
    94     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 first
    112     $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 queries
    117         // Direct database query is necessary here for CSV export - no WordPress core function provides this data
    118         // Query is properly prepared and cached to minimize database load
    119         // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
    120         $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 load
    123         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 headers
    133     fputcsv($output, ['Time', 'IP Address', 'Username', 'Status', 'Attempts']);
    134    
    135     // CSV data
    136     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 operations
    147     if (function_exists('WP_Filesystem')) {
    148         WP_Filesystem();
    149         global $wp_filesystem;
    150         // For CSV export, we need to handle the output stream properly
    151         // Since we're dealing with php://output, we'll let PHP handle the stream closing
    152         // The exit() call will properly close the stream
    153     }
    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 filename
    165     lockspire_ajax_export_logs();
    166 }
  • lockspire-smart-access-protection/trunk/includes/attempt-handler.php

    r3455145 r3462265  
    11<?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 */
    312
    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  
    11<?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
     9if (!defined('ABSPATH')) {
     10    exit;
     11}
     12
     13// Get recent login attempts
     14function 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
    1926    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
     55function 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
     73function 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
     100function 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
     106function 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
    47119    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);
    84128       
    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++;
    92133                }
    93134            }
    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
     145function 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
     159function 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
     178function 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']
    162197        ));
    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
     210function 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
     223function 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  
    11<?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
     15if (!defined('ABSPATH')) {
     16    exit;
     17}
     18
     19// Example implementation with proper naming conventions
     20function 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        }
    1044    }
    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
     48function 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        }
    1971    }
    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
     75function 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>
    59156            </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>
    89219            </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>";
    122222    }
    123223   
    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
     228function 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  
    11<?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 */
    310
    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
     12if (!defined('ABSPATH')) {
     13    exit;
    7014}
    7115
    72 function lockspire_save_all_settings() {
    73     // Verify nonce
    74     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 settings
    79     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 settings
    93     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 settings
    109     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() {
    12316    ?>
    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            ?>
    12923           
    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>
    13787                </div>
    13888            </div>
    13989           
    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>
    147146                </div>
    148147            </div>
    149148           
    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>
    157152                </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>
    193180                </div>
    194181            </div>
    195182           
    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>
    311185    </div>
    312186    <?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  
    11<?php
    2 /*
    3 Plugin Name: Lockspire – Smart Access Protection
    4 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 later
    10 License URI: https://www.gnu.org/licenses/gpl-2.0.html
    11 */
     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 */
    1212
    13 if ( ! defined( 'ABSPATH' ) ) exit;
     13// Prevent direct access
     14if (!defined('ABSPATH')) {
     15    exit;
     16}
    1417
    1518// Define plugin constants
    16 define( 'LOCKSPIRE_PLUGIN_PATH', plugin_dir_path( __FILE__ ) );
    17 define( 'LOCKSPIRE_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
     19define('LOCKSPIRE_VERSION', '1.0.0');
     20define('LOCKSPIRE_PLUGIN_DIR', plugin_dir_path(__FILE__));
     21define('LOCKSPIRE_PLUGIN_URL', plugin_dir_url(__FILE__));
    1822
    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');
     23class 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();
    853859    }
    863860}
    873861
    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
     3863new Lockspire_Security();
  • lockspire-smart-access-protection/trunk/readme.txt

    r3459106 r3462265  
    88License URI: https://www.gnu.org/licenses/gpl-2.0.html
    99
    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 
    1210== Description ==
    1311
    1412Lockspire – 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.
    1513
    16 **Key Features:**
    1714
    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
    2735
    2836== Installation ==
    2937
    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
     381. Upload the plugin files to the `/wp-content/plugins/lockspire-security` directory, or install the plugin through the WordPress plugins screen directly.
     392. Activate the plugin through the 'Plugins' screen in WordPress
     403. Navigate to **Settings → Lockspire Security** to configure your security settings
     41
     42== Configuration ==
     43
     44**Basic Setup:**
     451. Go to Settings → Lockspire Security
     462. Enable the features you need using the toggle switches
     473. 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:**
     53When enabled, access your admin dashboard at:
     54`/wp-admin/?your-secret-key=1`
     55
     56Replace `your-secret-key` with your custom secret key.
    3457
    3558== Frequently Asked Questions ==
    3659
    37 = How does the plugin protect against brute-force attacks? =
     60= Will this plugin slow down my website? =
    3861
    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.
     62No! Lockspire is designed to be extremely lightweight with minimal impact on site performance.
    4063
    41 = Can I whitelist trusted IP addresses? =
     64= Do I need technical knowledge to use this plugin? =
    4265
    43 Yes! You can whitelist IP addresses in the Security tab. Whitelisted IPs will never be locked out, regardless of failed attempts.
     66Not at all! Lockspire is designed for beginners with simple toggle switches and clear explanations for each feature.
    4467
    45 = Does the plugin work with multisite installations? =
     68= Does this plugin create database tables? =
    4669
    47 Yes, the plugin is fully compatible with WordPress multisite installations and can be activated network-wide.
     70No, Lockspire uses the WordPress Options API and doesn't create any additional database tables.
    4871
    49 = How do I receive security notifications? =
     72= Can I use this alongside other security plugins? =
    5073
    51 Enable email notifications in the Notifications tab. You can configure custom email addresses and notification thresholds to avoid alert fatigue.
     74Yes, Lockspire is designed to work well with other plugins, but for optimal performance, we recommend using it as your primary security solution.
    5275
    53 = Can I export login logs? =
     76= What happens if I forget my admin secret key? =
    5477
    55 Yes! The plugin allows you to export login activity logs in CSV format for analysis and record-keeping.
     78You can still access wp-admin by disabling the "Hide wp-admin URL" feature via FTP or file manager in the plugin file.
    5679
    5780== Screenshots ==
    5881
    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
     821. Clean settings page with toggle switches
     832. Activity log showing recent login events
     843. Pro features placeholder section
    6485
    6586== Changelog ==
    6687
    67 = 1.0 =
     88= 1.0.0 =
    6889* 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
    7893
    7994== Upgrade Notice ==
    8095
    81 = 1.0 =
    82 Initial release of the Lockspire – Smart Access Protection plugin with comprehensive security features.
     96= 1.0.0 =
     97Initial release of Lockspire Security Plugin.
    8398
    8499== Additional Information ==
    85100
    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
    89102
    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.
    96104
    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.
    102106
    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 ==
    109108
    110 **Support:**
    111 For support and feature requests, please visit the GitHub repository or contact the developer.
     109This 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:**
     117The 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.