Files
WP-Security-Pack/wp-security-pack/includes/class-wpsp-admin.php
T
2026-01-26 11:23:23 +01:00

3192 lines
125 KiB
PHP

<?php
/**
* Admin interface for WP Security Pack.
*
* @package WP_Security_Pack
*/
// Prevent direct access.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Admin class for settings page and UI.
*/
class WPSP_Admin {
/**
* Constructor.
*/
public function __construct() {
add_action( 'admin_menu', array( $this, 'add_admin_menu' ) );
add_action( 'admin_init', array( $this, 'register_settings' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
add_action( 'wp_dashboard_setup', array( $this, 'add_dashboard_widget' ) );
add_filter( 'plugin_row_meta', array( $this, 'plugin_row_meta' ), 10, 2 );
add_filter( 'plugin_action_links_' . WPSP_PLUGIN_BASENAME, array( $this, 'plugin_action_links' ) );
add_filter( 'submenu_file', array( $this, 'highlight_submenu' ) );
// AJAX handlers.
add_action( 'wp_ajax_wpsp_clear_logs', array( $this, 'ajax_clear_logs' ) );
add_action( 'wp_ajax_wpsp_export_logs', array( $this, 'ajax_export_logs' ) );
add_action( 'wp_ajax_wpsp_whitelist_ip', array( $this, 'ajax_whitelist_ip' ) );
add_action( 'wp_ajax_wpsp_unblock_ip', array( $this, 'ajax_unblock_ip' ) );
add_action( 'wp_ajax_wpsp_clear_lockouts', array( $this, 'ajax_clear_lockouts' ) );
add_action( 'wp_ajax_wpsp_run_file_scan', array( $this, 'ajax_run_file_scan' ) );
add_action( 'wp_ajax_wpsp_run_malware_scan', array( $this, 'ajax_run_malware_scan' ) );
add_action( 'wp_ajax_wpsp_reset_file_baseline', array( $this, 'ajax_reset_file_baseline' ) );
add_action( 'wp_ajax_wpsp_clear_malware_results', array( $this, 'ajax_clear_malware_results' ) );
add_action( 'wp_ajax_wpsp_download_geo_db', array( $this, 'ajax_download_geo_db' ) );
add_action( 'wp_ajax_wpsp_export_settings', array( $this, 'ajax_export_settings' ) );
add_action( 'wp_ajax_wpsp_import_settings', array( $this, 'ajax_import_settings' ) );
add_action( 'wp_ajax_wpsp_reset_settings', array( $this, 'ajax_reset_settings' ) );
add_action( 'wp_ajax_wpsp_test_email', array( $this, 'ajax_test_email' ) );
add_action( 'wp_ajax_wpsp_force_logout_all', array( $this, 'ajax_force_logout_all' ) );
add_action( 'wp_ajax_wpsp_quarantine_file', array( $this, 'ajax_quarantine_file' ) );
add_action( 'wp_ajax_wpsp_restore_file', array( $this, 'ajax_restore_file' ) );
add_action( 'wp_ajax_wpsp_delete_quarantined', array( $this, 'ajax_delete_quarantined' ) );
add_action( 'wp_ajax_wpsp_delete_wp_file', array( $this, 'ajax_delete_wp_file' ) );
}
/**
* Add admin menu.
*/
public function add_admin_menu() {
// Main menu item.
add_menu_page(
__( 'WP Security Pack', 'wp-security-pack' ),
__( 'Security', 'wp-security-pack' ),
'manage_options',
'wp-security-pack',
array( $this, 'render_settings_page' ),
'dashicons-shield-alt',
80
);
// Submenus for each tab.
$submenus = array(
'status' => __( 'Status', 'wp-security-pack' ),
'login' => __( 'Login Protection', 'wp-security-pack' ),
'ip' => __( 'IP Control', 'wp-security-pack' ),
'hardening' => __( 'Hardening', 'wp-security-pack' ),
'headers' => __( 'Security Headers', 'wp-security-pack' ),
'2fa' => __( '2FA', 'wp-security-pack' ),
'scanner' => __( 'Scanner', 'wp-security-pack' ),
'logs' => __( 'Activity Log', 'wp-security-pack' ),
'tools' => __( 'Tools', 'wp-security-pack' ),
);
foreach ( $submenus as $slug => $title ) {
add_submenu_page(
'wp-security-pack',
$title . ' - ' . __( 'WP Security Pack', 'wp-security-pack' ),
$title,
'manage_options',
'status' === $slug ? 'wp-security-pack' : 'wp-security-pack&tab=' . $slug,
array( $this, 'render_settings_page' )
);
}
}
/**
* Highlight the correct submenu based on current tab.
*
* @param string $submenu_file The current submenu file.
* @return string
*/
public function highlight_submenu( $submenu_file ) {
global $pagenow;
if ( 'admin.php' !== $pagenow ) {
return $submenu_file;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
if ( 'wp-security-pack' !== $page ) {
return $submenu_file;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$tab = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : 'status';
if ( 'status' === $tab ) {
return 'wp-security-pack';
}
return 'wp-security-pack&tab=' . $tab;
}
/**
* Add plugin action links (left side - Settings, Deactivate, etc.).
*
* @param array $links Existing links.
* @return array
*/
public function plugin_action_links( $links ) {
$settings_link = sprintf(
'<a href="%s">%s</a>',
admin_url( 'admin.php?page=wp-security-pack' ),
__( 'Settings', 'wp-security-pack' )
);
array_unshift( $links, $settings_link );
return $links;
}
/**
* Add plugin row meta links (right side - after version).
*
* @param array $links Existing meta links.
* @param string $file Plugin file.
* @return array
*/
public function plugin_row_meta( $links, $file ) {
if ( WPSP_PLUGIN_BASENAME !== $file ) {
return $links;
}
$links[] = sprintf(
'<a href="%s" target="_blank">%s</a>',
'https://arkhost.com/products-menu.php',
__( 'Get Hosting', 'wp-security-pack' )
);
return $links;
}
/**
* Add dashboard widget.
*/
public function add_dashboard_widget() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
wp_add_dashboard_widget(
'wpsp_security_widget',
__( 'Security Status', 'wp-security-pack' ),
array( $this, 'render_dashboard_widget' )
);
}
/**
* Render dashboard widget.
*/
public function render_dashboard_widget() {
$checks = $this->get_security_checks();
$enabled_count = 0;
$total_count = count( $checks );
foreach ( $checks as $check ) {
if ( $check['status'] ) {
$enabled_count++;
}
}
$score = $total_count > 0 ? round( ( $enabled_count / $total_count ) * 100 ) : 0;
// Get recent activity stats.
$stats = WPSP_Activity_Log::get_stats( 7 );
?>
<style>
.wpsp-widget-score {
text-align: center;
padding: 15px 0;
border-bottom: 1px solid #eee;
margin-bottom: 15px;
}
.wpsp-widget-score-value {
font-size: 42px;
font-weight: 700;
line-height: 1;
}
.wpsp-widget-score-label {
color: #666;
font-size: 12px;
margin-top: 5px;
}
.wpsp-widget-score.good .wpsp-widget-score-value { color: #00a32a; }
.wpsp-widget-score.warning .wpsp-widget-score-value { color: #dba617; }
.wpsp-widget-score.bad .wpsp-widget-score-value { color: #d63638; }
.wpsp-widget-stats {
display: flex;
justify-content: space-around;
text-align: center;
margin-bottom: 15px;
}
.wpsp-widget-stat-value {
font-size: 24px;
font-weight: 600;
}
.wpsp-widget-stat-label {
font-size: 11px;
color: #666;
}
.wpsp-widget-links {
border-top: 1px solid #eee;
padding-top: 12px;
text-align: center;
}
.wpsp-widget-links a {
margin: 0 8px;
}
</style>
<div class="wpsp-widget-score <?php echo $score >= 80 ? 'good' : ( $score >= 50 ? 'warning' : 'bad' ); ?>">
<div class="wpsp-widget-score-value"><?php echo esc_html( $score ); ?>%</div>
<div class="wpsp-widget-score-label"><?php esc_html_e( 'Security Score', 'wp-security-pack' ); ?></div>
</div>
<div class="wpsp-widget-stats">
<div>
<div class="wpsp-widget-stat-value"><?php echo esc_html( $stats['login_success'] ?? 0 ); ?></div>
<div class="wpsp-widget-stat-label"><?php esc_html_e( 'Logins (7d)', 'wp-security-pack' ); ?></div>
</div>
<div>
<div class="wpsp-widget-stat-value" style="color: #dba617;"><?php echo esc_html( $stats['login_failed'] ?? 0 ); ?></div>
<div class="wpsp-widget-stat-label"><?php esc_html_e( 'Failed (7d)', 'wp-security-pack' ); ?></div>
</div>
<div>
<div class="wpsp-widget-stat-value" style="color: #d63638;"><?php echo esc_html( $stats['lockout'] ?? 0 ); ?></div>
<div class="wpsp-widget-stat-label"><?php esc_html_e( 'Lockouts (7d)', 'wp-security-pack' ); ?></div>
</div>
</div>
<div class="wpsp-widget-links">
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-security-pack' ) ); ?>"><?php esc_html_e( 'Dashboard', 'wp-security-pack' ); ?></a>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-security-pack&tab=logs' ) ); ?>"><?php esc_html_e( 'Activity Log', 'wp-security-pack' ); ?></a>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-security-pack&tab=scanner' ) ); ?>"><?php esc_html_e( 'Scanner', 'wp-security-pack' ); ?></a>
</div>
<?php
}
/**
* Register settings.
*/
public function register_settings() {
register_setting( 'wpsp_settings', 'wpsp_settings', array( $this, 'sanitize_settings' ) );
}
/**
* Sanitize settings.
*
* @param array $input Input settings.
* @return array
*/
public function sanitize_settings( $input ) {
// Start with existing settings to preserve values from other tabs.
$existing = get_option( 'wpsp_settings', array() );
$sanitized = is_array( $existing ) ? $existing : array();
$defaults = WP_Security_Pack::get_default_settings();
// Determine which tab is being saved (from hidden field).
$current_tab = isset( $input['wpsp_current_tab'] ) ? sanitize_text_field( $input['wpsp_current_tab'] ) : '';
// Only sanitize settings for the current tab to avoid resetting other tabs.
// If no tab specified (e.g., import), sanitize all provided settings.
if ( '' === $current_tab || 'login' === $current_tab ) {
// Login Protection - only update if this tab is submitted or field is present.
$sanitized['login_limit_enabled'] = ! empty( $input['login_limit_enabled'] );
$sanitized['login_max_attempts'] = isset( $input['login_max_attempts'] ) ? absint( $input['login_max_attempts'] ) : ( $sanitized['login_max_attempts'] ?? 5 );
$sanitized['login_lockout_duration'] = isset( $input['login_lockout_duration'] ) ? absint( $input['login_lockout_duration'] ) : ( $sanitized['login_lockout_duration'] ?? 15 );
$sanitized['login_rename_enabled'] = ! empty( $input['login_rename_enabled'] );
$sanitized['login_custom_url'] = isset( $input['login_custom_url'] ) ? WPSP_Helper::sanitize_login_slug( $input['login_custom_url'] ) : ( $sanitized['login_custom_url'] ?? '' );
$sanitized['hide_wp_admin'] = ! empty( $input['hide_wp_admin'] );
$sanitized['honeypot_enabled'] = ! empty( $input['honeypot_enabled'] );
$sanitized['honeypot_ban_duration'] = isset( $input['honeypot_ban_duration'] ) ? absint( $input['honeypot_ban_duration'] ) : ( $sanitized['honeypot_ban_duration'] ?? 60 );
$sanitized['hide_login_errors'] = ! empty( $input['hide_login_errors'] );
$sanitized['admin_login_notify'] = ! empty( $input['admin_login_notify'] );
// Login Access Restriction.
$sanitized['admin_access_restriction'] = ! empty( $input['admin_access_restriction'] );
if ( 'login' === $current_tab ) {
$sanitized['admin_allowed_countries'] = isset( $input['admin_allowed_countries'] ) && is_array( $input['admin_allowed_countries'] )
? array_map( 'sanitize_text_field', $input['admin_allowed_countries'] )
: array();
} elseif ( isset( $input['admin_allowed_countries'] ) ) {
$sanitized['admin_allowed_countries'] = is_array( $input['admin_allowed_countries'] )
? array_map( 'sanitize_text_field', $input['admin_allowed_countries'] )
: array();
}
$sanitized['admin_allowed_ips'] = isset( $input['admin_allowed_ips'] ) ? sanitize_textarea_field( $input['admin_allowed_ips'] ) : ( $sanitized['admin_allowed_ips'] ?? '' );
// Email Alerts (also on login tab).
$sanitized['email_alerts_enabled'] = ! empty( $input['email_alerts_enabled'] );
$sanitized['email_alerts_address'] = isset( $input['email_alerts_address'] ) ? sanitize_email( $input['email_alerts_address'] ) : ( $sanitized['email_alerts_address'] ?? '' );
$sanitized['email_alert_threshold'] = isset( $input['email_alert_threshold'] ) ? absint( $input['email_alert_threshold'] ) : ( $sanitized['email_alert_threshold'] ?? 3 );
}
if ( '' === $current_tab || 'ip' === $current_tab ) {
// IP Control.
$sanitized['ip_whitelist'] = isset( $input['ip_whitelist'] ) ? sanitize_textarea_field( $input['ip_whitelist'] ) : ( $sanitized['ip_whitelist'] ?? '' );
$sanitized['ip_blacklist'] = isset( $input['ip_blacklist'] ) ? sanitize_textarea_field( $input['ip_blacklist'] ) : ( $sanitized['ip_blacklist'] ?? '' );
$sanitized['auto_blacklist_enabled'] = ! empty( $input['auto_blacklist_enabled'] );
$sanitized['auto_blacklist_threshold'] = isset( $input['auto_blacklist_threshold'] ) ? max( 1, absint( $input['auto_blacklist_threshold'] ) ) : ( $sanitized['auto_blacklist_threshold'] ?? 3 );
// Geo Blocking.
$sanitized['geo_blocking_enabled'] = ! empty( $input['geo_blocking_enabled'] );
// For geo_blocked_countries, if the tab is IP and field not set, it means nothing selected.
if ( 'ip' === $current_tab ) {
$sanitized['geo_blocked_countries'] = isset( $input['geo_blocked_countries'] ) && is_array( $input['geo_blocked_countries'] )
? array_map( 'sanitize_text_field', $input['geo_blocked_countries'] )
: array();
} elseif ( isset( $input['geo_blocked_countries'] ) ) {
$sanitized['geo_blocked_countries'] = is_array( $input['geo_blocked_countries'] )
? array_map( 'sanitize_text_field', $input['geo_blocked_countries'] )
: array();
}
$sanitized['geo_database_path'] = isset( $input['geo_database_path'] ) ? sanitize_text_field( $input['geo_database_path'] ) : ( $sanitized['geo_database_path'] ?? '' );
}
if ( '' === $current_tab || 'hardening' === $current_tab ) {
// Hardening.
$sanitized['disable_xmlrpc'] = ! empty( $input['disable_xmlrpc'] );
$sanitized['disable_file_editing'] = ! empty( $input['disable_file_editing'] );
$sanitized['disable_application_passwords'] = ! empty( $input['disable_application_passwords'] );
$sanitized['restrict_rest_api'] = ! empty( $input['restrict_rest_api'] );
$sanitized['remove_wp_version'] = ! empty( $input['remove_wp_version'] );
$sanitized['remove_feed_links'] = ! empty( $input['remove_feed_links'] );
$sanitized['add_security_headers'] = ! empty( $input['add_security_headers'] );
$sanitized['disable_user_enumeration'] = ! empty( $input['disable_user_enumeration'] );
$sanitized['disable_pingbacks'] = ! empty( $input['disable_pingbacks'] );
}
if ( '' === $current_tab || 'headers' === $current_tab ) {
// Security Headers.
if ( isset( $input['security_headers'] ) && is_array( $input['security_headers'] ) ) {
$sanitized['security_headers'] = array_map( 'sanitize_text_field', $input['security_headers'] );
}
}
if ( '' === $current_tab || '2fa' === $current_tab ) {
// Two-Factor.
$sanitized['two_factor_enabled'] = ! empty( $input['two_factor_enabled'] );
$sanitized['two_factor_enforce_admin'] = ! empty( $input['two_factor_enforce_admin'] );
}
if ( '' === $current_tab || 'scanner' === $current_tab ) {
// File Integrity.
$sanitized['file_integrity_enabled'] = ! empty( $input['file_integrity_enabled'] );
// Malware Scanner.
$sanitized['malware_scan_enabled'] = ! empty( $input['malware_scan_enabled'] );
}
if ( '' === $current_tab || 'logs' === $current_tab ) {
// Activity Log.
$sanitized['log_retention_days'] = isset( $input['log_retention_days'] ) ? absint( $input['log_retention_days'] ) : ( $sanitized['log_retention_days'] ?? 30 );
}
// Flush rewrite rules if login URL changed.
if (
( isset( $existing['login_custom_url'] ) && $existing['login_custom_url'] !== ( $sanitized['login_custom_url'] ?? '' ) ) ||
( isset( $existing['login_rename_enabled'] ) && $existing['login_rename_enabled'] !== ( $sanitized['login_rename_enabled'] ?? false ) )
) {
flush_rewrite_rules();
}
return $sanitized;
}
/**
* Enqueue admin assets.
*
* @param string $hook Admin page hook.
*/
public function enqueue_admin_assets( $hook ) {
// Check for top-level page and all subpages.
if ( 'toplevel_page_wp-security-pack' !== $hook && strpos( $hook, 'security_page_wp-security-pack' ) === false ) {
return;
}
wp_enqueue_style(
'wpsp-admin',
WPSP_PLUGIN_URL . 'assets/css/admin.css',
array(),
WPSP_VERSION
);
wp_enqueue_script( 'jquery' );
}
/**
* Render settings page.
*/
public function render_settings_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
// Get current tab.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$current_tab = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : 'status';
$tabs = array(
'status' => __( 'Status', 'wp-security-pack' ),
'login' => __( 'Login Protection', 'wp-security-pack' ),
'ip' => __( 'IP Control', 'wp-security-pack' ),
'hardening' => __( 'Hardening', 'wp-security-pack' ),
'headers' => __( 'Security Headers', 'wp-security-pack' ),
'2fa' => __( '2FA', 'wp-security-pack' ),
'scanner' => __( 'Scanner', 'wp-security-pack' ),
'logs' => __( 'Activity Log', 'wp-security-pack' ),
'tools' => __( 'Tools', 'wp-security-pack' ),
);
?>
<div class="wrap wpsp-wrap">
<h1><?php esc_html_e( 'WP Security Pack', 'wp-security-pack' ); ?></h1>
<nav class="nav-tab-wrapper wpsp-tabs">
<?php foreach ( $tabs as $tab => $label ) : ?>
<a href="<?php echo esc_url( add_query_arg( 'tab', $tab ) ); ?>"
class="nav-tab <?php echo $tab === $current_tab ? 'nav-tab-active' : ''; ?>">
<?php echo esc_html( $label ); ?>
</a>
<?php endforeach; ?>
</nav>
<form method="post" action="options.php" class="wpsp-form">
<?php
settings_fields( 'wpsp_settings' );
switch ( $current_tab ) {
case 'status':
$this->render_status_tab();
break;
case 'login':
$this->render_login_tab();
break;
case 'ip':
$this->render_ip_tab();
break;
case 'hardening':
$this->render_hardening_tab();
break;
case 'headers':
$this->render_headers_tab();
break;
case '2fa':
$this->render_2fa_tab();
break;
case 'scanner':
$this->render_scanner_tab();
break;
case 'logs':
$this->render_logs_tab();
break;
case 'tools':
$this->render_tools_tab();
break;
}
if ( ! in_array( $current_tab, array( 'status', 'logs', 'tools' ), true ) ) {
submit_button();
}
?>
</form>
<p class="wpsp-footer">
<?php
printf(
/* translators: %s: ArkHost link */
esc_html__( 'WP Security Pack by %s - Free forever, no bullshit.', 'wp-security-pack' ),
'<a href="https://arkhost.com" target="_blank">ArkHost</a>'
);
?>
</p>
</div>
<?php
}
/**
* Render status/checklist tab.
*/
private function render_status_tab() {
// Gather all security status information.
$checks = $this->get_security_checks();
$enabled_count = 0;
$total_count = count( $checks );
foreach ( $checks as $check ) {
if ( $check['status'] ) {
$enabled_count++;
}
}
$score = $total_count > 0 ? round( ( $enabled_count / $total_count ) * 100 ) : 0;
?>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Security Status', 'wp-security-pack' ); ?></h2>
<div class="wpsp-stats">
<div class="wpsp-stat <?php echo $score >= 80 ? 'wpsp-stat-success' : ( $score >= 50 ? 'wpsp-stat-warning' : 'wpsp-stat-danger' ); ?>">
<span class="wpsp-stat-value"><?php echo esc_html( $score ); ?>%</span>
<span class="wpsp-stat-label"><?php esc_html_e( 'Security Score', 'wp-security-pack' ); ?></span>
</div>
<div class="wpsp-stat wpsp-stat-success">
<span class="wpsp-stat-value"><?php echo esc_html( $enabled_count ); ?></span>
<span class="wpsp-stat-label"><?php esc_html_e( 'Protections Active', 'wp-security-pack' ); ?></span>
</div>
<div class="wpsp-stat <?php echo ( $total_count - $enabled_count ) > 0 ? 'wpsp-stat-warning' : 'wpsp-stat-info'; ?>">
<span class="wpsp-stat-value"><?php echo esc_html( $total_count - $enabled_count ); ?></span>
<span class="wpsp-stat-label"><?php esc_html_e( 'Not Enabled', 'wp-security-pack' ); ?></span>
</div>
</div>
<table class="widefat striped" style="margin-top: 20px;">
<thead>
<tr>
<th style="width: 40px;"><?php esc_html_e( 'Status', 'wp-security-pack' ); ?></th>
<th><?php esc_html_e( 'Security Feature', 'wp-security-pack' ); ?></th>
<th><?php esc_html_e( 'Description', 'wp-security-pack' ); ?></th>
<th style="width: 100px;"><?php esc_html_e( 'Action', 'wp-security-pack' ); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ( $checks as $key => $check ) : ?>
<tr>
<td>
<?php if ( $check['status'] ) : ?>
<span style="color: #46b450; font-size: 18px;">&#10003;</span>
<?php else : ?>
<span style="color: #dc3232; font-size: 18px;">&#10007;</span>
<?php endif; ?>
</td>
<td><strong><?php echo esc_html( $check['name'] ); ?></strong></td>
<td><span class="description"><?php echo esc_html( $check['description'] ); ?></span></td>
<td>
<?php if ( ! empty( $check['tab'] ) ) : ?>
<a href="<?php echo esc_url( add_query_arg( 'tab', $check['tab'] ) ); ?>" class="button button-small">
<?php echo $check['status'] ? esc_html__( 'Configure', 'wp-security-pack' ) : esc_html__( 'Enable', 'wp-security-pack' ); ?>
</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Quick Info', 'wp-security-pack' ); ?></h2>
<table class="form-table">
<tr>
<th><?php esc_html_e( 'Your IP Address', 'wp-security-pack' ); ?></th>
<td><code><?php echo esc_html( WPSP_Helper::get_client_ip() ); ?></code></td>
</tr>
<tr>
<th><?php esc_html_e( 'WordPress Version', 'wp-security-pack' ); ?></th>
<td><?php echo esc_html( get_bloginfo( 'version' ) ); ?></td>
</tr>
<tr>
<th><?php esc_html_e( 'PHP Version', 'wp-security-pack' ); ?></th>
<td>
<?php echo esc_html( PHP_VERSION ); ?>
<?php if ( version_compare( PHP_VERSION, '8.0', '<' ) ) : ?>
<span class="wpsp-status wpsp-status-warning"><?php esc_html_e( 'Update recommended', 'wp-security-pack' ); ?></span>
<?php endif; ?>
</td>
</tr>
<tr>
<th><?php esc_html_e( 'HTTPS', 'wp-security-pack' ); ?></th>
<td>
<?php if ( is_ssl() ) : ?>
<span class="wpsp-status wpsp-status-ok"><?php esc_html_e( 'Active', 'wp-security-pack' ); ?></span>
<?php else : ?>
<span class="wpsp-status wpsp-status-error"><?php esc_html_e( 'Not active', 'wp-security-pack' ); ?></span>
<span class="description"><?php esc_html_e( 'HTTPS is strongly recommended', 'wp-security-pack' ); ?></span>
<?php endif; ?>
</td>
</tr>
<tr>
<th><?php esc_html_e( 'Debug Mode', 'wp-security-pack' ); ?></th>
<td>
<?php if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) : ?>
<span class="wpsp-status wpsp-status-warning"><?php esc_html_e( 'Enabled', 'wp-security-pack' ); ?></span>
<span class="description"><?php esc_html_e( 'Disable WP_DEBUG in production', 'wp-security-pack' ); ?></span>
<?php else : ?>
<span class="wpsp-status wpsp-status-ok"><?php esc_html_e( 'Disabled', 'wp-security-pack' ); ?></span>
<?php endif; ?>
</td>
</tr>
<tr>
<th><?php esc_html_e( 'Database Prefix', 'wp-security-pack' ); ?></th>
<td>
<?php global $wpdb; ?>
<?php if ( 'wp_' === $wpdb->prefix ) : ?>
<span class="wpsp-status wpsp-status-warning"><code>wp_</code></span>
<span class="description"><?php esc_html_e( 'Default prefix - consider changing for new installs', 'wp-security-pack' ); ?></span>
<?php else : ?>
<span class="wpsp-status wpsp-status-ok"><code><?php echo esc_html( $wpdb->prefix ); ?></code></span>
<?php endif; ?>
</td>
</tr>
<tr>
<th><?php esc_html_e( 'Admin Username', 'wp-security-pack' ); ?></th>
<td>
<?php if ( username_exists( 'admin' ) ) : ?>
<span class="wpsp-status wpsp-status-warning"><?php esc_html_e( 'Exists', 'wp-security-pack' ); ?></span>
<span class="description"><?php esc_html_e( '"admin" username is commonly targeted', 'wp-security-pack' ); ?></span>
<?php else : ?>
<span class="wpsp-status wpsp-status-ok"><?php esc_html_e( 'Not found', 'wp-security-pack' ); ?></span>
<?php endif; ?>
</td>
</tr>
<?php
$custom_login = WP_Security_Pack::get_setting( 'custom_login_url', '' );
if ( ! empty( $custom_login ) ) :
?>
<tr>
<th><?php esc_html_e( 'Custom Login URL', 'wp-security-pack' ); ?></th>
<td>
<code><?php echo esc_url( home_url( '/' . $custom_login . '/' ) ); ?></code>
<span class="description"><?php esc_html_e( 'Bookmark this!', 'wp-security-pack' ); ?></span>
</td>
</tr>
<?php endif; ?>
</table>
</div>
<?php
// Check for files that expose WordPress version.
$readme_exists = file_exists( ABSPATH . 'readme.html' );
$license_exists = file_exists( ABSPATH . 'license.txt' );
if ( $readme_exists || $license_exists ) :
?>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Recommended Actions', 'wp-security-pack' ); ?></h2>
<div class="notice notice-warning inline" style="margin: 0 0 15px;">
<p><?php esc_html_e( 'These files expose your WordPress version and can be safely removed.', 'wp-security-pack' ); ?></p>
</div>
<table class="widefat striped">
<thead>
<tr>
<th><?php esc_html_e( 'File', 'wp-security-pack' ); ?></th>
<th><?php esc_html_e( 'Status', 'wp-security-pack' ); ?></th>
<th style="width: 100px;"><?php esc_html_e( 'Action', 'wp-security-pack' ); ?></th>
</tr>
</thead>
<tbody>
<?php if ( $readme_exists ) : ?>
<tr>
<td><code>readme.html</code></td>
<td><span class="wpsp-status wpsp-status-warning"><?php esc_html_e( 'Exists', 'wp-security-pack' ); ?></span></td>
<td>
<button type="button" class="button button-small wpsp-delete-file" data-file="readme.html">
<?php esc_html_e( 'Delete', 'wp-security-pack' ); ?>
</button>
</td>
</tr>
<?php endif; ?>
<?php if ( $license_exists ) : ?>
<tr>
<td><code>license.txt</code></td>
<td><span class="wpsp-status wpsp-status-warning"><?php esc_html_e( 'Exists', 'wp-security-pack' ); ?></span></td>
<td>
<button type="button" class="button button-small wpsp-delete-file" data-file="license.txt">
<?php esc_html_e( 'Delete', 'wp-security-pack' ); ?>
</button>
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
<script>
jQuery(function($) {
$('.wpsp-delete-file').on('click', function() {
var $btn = $(this);
var file = $btn.data('file');
if (!confirm('<?php echo esc_js( __( 'Delete this file?', 'wp-security-pack' ) ); ?>')) {
return;
}
$btn.prop('disabled', true).text('<?php echo esc_js( __( 'Deleting...', 'wp-security-pack' ) ); ?>');
$.post(ajaxurl, {
action: 'wpsp_delete_wp_file',
file: file,
_ajax_nonce: '<?php echo esc_js( wp_create_nonce( 'wpsp_admin' ) ); ?>'
}, function(response) {
if (response.success) {
$btn.closest('tr').fadeOut();
} else {
alert(response.data.message || '<?php echo esc_js( __( 'Failed to delete file.', 'wp-security-pack' ) ); ?>');
$btn.prop('disabled', false).text('<?php echo esc_js( __( 'Delete', 'wp-security-pack' ) ); ?>');
}
});
});
});
</script>
<?php endif; ?>
<?php
}
/**
* Get security checks for status tab.
*
* @return array
*/
private function get_security_checks() {
$checks = array();
// Login Protection.
$checks['login_limit'] = array(
'name' => __( 'Login Attempt Limiting', 'wp-security-pack' ),
'description' => __( 'Limits failed login attempts to prevent brute force attacks', 'wp-security-pack' ),
'status' => WP_Security_Pack::get_setting( 'login_limit_enabled', true ),
'tab' => 'login',
);
$checks['honeypot'] = array(
'name' => __( 'Login Honeypot', 'wp-security-pack' ),
'description' => __( 'Hidden field that traps automated bots', 'wp-security-pack' ),
'status' => WP_Security_Pack::get_setting( 'honeypot_enabled', true ),
'tab' => 'login',
);
$checks['hide_login_errors'] = array(
'name' => __( 'Login Errors Hidden', 'wp-security-pack' ),
'description' => __( 'Generic error message prevents username enumeration', 'wp-security-pack' ),
'status' => WP_Security_Pack::get_setting( 'hide_login_errors', true ),
'tab' => 'login',
);
// Hardening.
$checks['xmlrpc'] = array(
'name' => __( 'XML-RPC Disabled', 'wp-security-pack' ),
'description' => __( 'Blocks XML-RPC endpoint commonly used in attacks', 'wp-security-pack' ),
'status' => WP_Security_Pack::get_setting( 'disable_xmlrpc', true ),
'tab' => 'hardening',
);
$checks['file_editing'] = array(
'name' => __( 'File Editor Disabled', 'wp-security-pack' ),
'description' => __( 'Prevents editing theme/plugin files from dashboard', 'wp-security-pack' ),
'status' => WP_Security_Pack::get_setting( 'disable_file_editing', true ),
'tab' => 'hardening',
);
$checks['rest_api'] = array(
'name' => __( 'REST API Restricted', 'wp-security-pack' ),
'description' => __( 'Requires authentication for REST API access', 'wp-security-pack' ),
'status' => WP_Security_Pack::get_setting( 'restrict_rest_api', true ),
'tab' => 'hardening',
);
$checks['user_enum'] = array(
'name' => __( 'User Enumeration Blocked', 'wp-security-pack' ),
'description' => __( 'Prevents attackers from discovering usernames', 'wp-security-pack' ),
'status' => WP_Security_Pack::get_setting( 'disable_user_enumeration', true ),
'tab' => 'hardening',
);
$checks['pingbacks'] = array(
'name' => __( 'Pingbacks Disabled', 'wp-security-pack' ),
'description' => __( 'Prevents pingback-based DDoS amplification', 'wp-security-pack' ),
'status' => WP_Security_Pack::get_setting( 'disable_pingbacks', true ),
'tab' => 'hardening',
);
$checks['wp_version'] = array(
'name' => __( 'WP Version Hidden', 'wp-security-pack' ),
'description' => __( 'Hides WordPress version from public view', 'wp-security-pack' ),
'status' => WP_Security_Pack::get_setting( 'remove_wp_version', true ),
'tab' => 'hardening',
);
$checks['security_headers'] = array(
'name' => __( 'Security Headers', 'wp-security-pack' ),
'description' => __( 'HTTP headers that enable browser security features', 'wp-security-pack' ),
'status' => WP_Security_Pack::get_setting( 'add_security_headers', true ),
'tab' => 'headers',
);
// 2FA - only green if enforced for admins (just "enabled" means users CAN set it up, not that they have).
$two_fa_enabled = WP_Security_Pack::get_setting( 'two_factor_enabled', false );
$two_fa_enforced = WP_Security_Pack::get_setting( 'two_factor_enforce_admin', false );
$checks['two_factor'] = array(
'name' => __( 'Two-Factor Authentication', 'wp-security-pack' ),
'description' => $two_fa_enabled && ! $two_fa_enforced
? __( 'Available but not enforced for admins', 'wp-security-pack' )
: __( 'Enforced for administrator accounts', 'wp-security-pack' ),
'status' => $two_fa_enabled && $two_fa_enforced,
'tab' => '2fa',
);
// Monitoring.
$checks['file_integrity'] = array(
'name' => __( 'File Integrity Monitoring', 'wp-security-pack' ),
'description' => __( 'Detects unauthorized changes to WordPress core', 'wp-security-pack' ),
'status' => WP_Security_Pack::get_setting( 'file_integrity_enabled', true ),
'tab' => 'scanner',
);
$checks['malware_scan'] = array(
'name' => __( 'Malware Scanning', 'wp-security-pack' ),
'description' => __( 'Scans files for malicious code patterns', 'wp-security-pack' ),
'status' => WP_Security_Pack::get_setting( 'malware_scan_enabled', true ),
'tab' => 'scanner',
);
// Optional features (not counted negatively if disabled).
$checks['custom_login'] = array(
'name' => __( 'Custom Login URL', 'wp-security-pack' ),
'description' => __( 'Hides wp-login.php from automated scanners', 'wp-security-pack' ),
'status' => WP_Security_Pack::get_setting( 'login_rename_enabled', false ),
'tab' => 'login',
);
$checks['geo_blocking'] = array(
'name' => __( 'Country Blocking', 'wp-security-pack' ),
'description' => __( 'Blocks access from specific countries', 'wp-security-pack' ),
'status' => WP_Security_Pack::get_setting( 'geo_blocking_enabled', false ),
'tab' => 'ip',
);
$checks['auto_blacklist'] = array(
'name' => __( 'Auto-Blacklist Repeat Offenders', 'wp-security-pack' ),
'description' => __( 'Permanently blocks IPs with repeated lockouts', 'wp-security-pack' ),
'status' => WP_Security_Pack::get_setting( 'auto_blacklist_enabled', false ),
'tab' => 'ip',
);
// Environment checks (no tab - these are recommendations).
global $wpdb;
$checks['ssl'] = array(
'name' => __( 'SSL/HTTPS', 'wp-security-pack' ),
'description' => is_ssl()
? __( 'Site is served over HTTPS', 'wp-security-pack' )
: __( 'Site should use HTTPS for security', 'wp-security-pack' ),
'status' => is_ssl(),
'tab' => '',
);
$checks['debug_mode'] = array(
'name' => __( 'Debug Mode Disabled', 'wp-security-pack' ),
'description' => defined( 'WP_DEBUG' ) && WP_DEBUG
? __( 'WP_DEBUG is enabled - disable in production', 'wp-security-pack' )
: __( 'Debug mode is properly disabled', 'wp-security-pack' ),
'status' => ! ( defined( 'WP_DEBUG' ) && WP_DEBUG ),
'tab' => '',
);
$checks['db_prefix'] = array(
'name' => __( 'Database Prefix Changed', 'wp-security-pack' ),
'description' => 'wp_' === $wpdb->prefix
? __( 'Using default wp_ prefix - consider changing', 'wp-security-pack' )
: __( 'Using custom database prefix', 'wp-security-pack' ),
'status' => 'wp_' !== $wpdb->prefix,
'tab' => '',
);
$admin_user = get_user_by( 'login', 'admin' );
$checks['admin_username'] = array(
'name' => __( 'No "admin" Username', 'wp-security-pack' ),
'description' => $admin_user
? __( 'Default "admin" username exists - consider renaming', 'wp-security-pack' )
: __( 'No user with "admin" username', 'wp-security-pack' ),
'status' => ! $admin_user,
'tab' => '',
);
$checks['php_version'] = array(
'name' => __( 'PHP Version', 'wp-security-pack' ),
'description' => version_compare( PHP_VERSION, '8.0', '<' )
? sprintf(
/* translators: %s: PHP version */
__( 'PHP %s is outdated - update recommended', 'wp-security-pack' ),
PHP_VERSION
)
: sprintf(
/* translators: %s: PHP version */
__( 'Running PHP %s', 'wp-security-pack' ),
PHP_VERSION
),
'status' => version_compare( PHP_VERSION, '8.0', '>=' ),
'tab' => '',
);
// Check if auto-updates are enabled for core.
$auto_updates_enabled = defined( 'WP_AUTO_UPDATE_CORE' ) && WP_AUTO_UPDATE_CORE;
// Also check the database option.
if ( ! $auto_updates_enabled ) {
$auto_updates_enabled = 'true' === get_site_option( 'auto_update_core_major' ) ||
'true' === get_site_option( 'auto_update_core_minor' );
}
$checks['auto_updates'] = array(
'name' => __( 'Auto-Updates Enabled', 'wp-security-pack' ),
'description' => $auto_updates_enabled
? __( 'WordPress core auto-updates are enabled', 'wp-security-pack' )
: __( 'Enable auto-updates for security patches', 'wp-security-pack' ),
'status' => $auto_updates_enabled,
'tab' => '',
);
return $checks;
}
/**
* Render login protection tab.
*/
private function render_login_tab() {
?>
<input type="hidden" name="wpsp_settings[wpsp_current_tab]" value="login" />
<div class="wpsp-section">
<h2><?php esc_html_e( 'Login Access Restriction', 'wp-security-pack' ); ?></h2>
<p class="description">
<?php esc_html_e( 'Only listed IPs/countries can access login. Different from global whitelist (which bypasses everything).', 'wp-security-pack' ); ?>
</p>
<?php
$geo_blocking = wpsp()->get_component( 'geo_blocking' );
$db_exists = $geo_blocking && $geo_blocking->database_exists();
if ( ! $db_exists ) :
?>
<div class="notice notice-warning inline" style="margin: 10px 0;">
<p>
<strong><?php esc_html_e( 'GeoIP Database Required', 'wp-security-pack' ); ?></strong><br>
<?php
printf(
/* translators: %s: Link to IP Control tab */
esc_html__( 'Country detection requires the GeoIP database. Please download it from the %s tab.', 'wp-security-pack' ),
'<a href="' . esc_url( admin_url( 'admin.php?page=wp-security-pack&tab=ip' ) ) . '"><strong>' . esc_html__( 'IP Control', 'wp-security-pack' ) . '</strong></a>'
);
?>
</p>
</div>
<?php else : ?>
<div class="notice notice-success inline" style="margin: 10px 0;">
<p><?php esc_html_e( 'GeoIP database installed.', 'wp-security-pack' ); ?></p>
</div>
<?php endif; ?>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Enable', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[admin_access_restriction]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'admin_access_restriction', false ) ); ?> />
<?php esc_html_e( 'Restrict login page access by country/IP', 'wp-security-pack' ); ?>
</label>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Allowed Countries', 'wp-security-pack' ); ?></th>
<td>
<?php
$countries_with_flags = WPSP_Helper::get_countries_with_flags();
$admin_countries = WP_Security_Pack::get_setting( 'admin_allowed_countries', array() );
?>
<select name="wpsp_settings[admin_allowed_countries][]" multiple="multiple" size="8" class="wpsp-country-select" style="min-width: 300px;">
<?php foreach ( $countries_with_flags as $code => $name ) : ?>
<option value="<?php echo esc_attr( $code ); ?>"
<?php selected( in_array( $code, $admin_countries, true ) ); ?>>
<?php echo esc_html( $name ); ?>
</option>
<?php endforeach; ?>
</select>
<p class="description"><?php esc_html_e( 'Ctrl/Cmd to select multiple. Empty = all countries.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Allowed IPs', 'wp-security-pack' ); ?></th>
<td>
<textarea name="wpsp_settings[admin_allowed_ips]" rows="4" class="large-text code"><?php
echo esc_textarea( WP_Security_Pack::get_setting( 'admin_allowed_ips', '' ) );
?></textarea>
<p class="description">
<?php esc_html_e( 'One IP or CIDR per line.', 'wp-security-pack' ); ?>
<?php printf( esc_html__( 'Your IP: %s', 'wp-security-pack' ), '<code>' . esc_html( WPSP_Helper::get_client_ip() ) . '</code>' ); ?>
</p>
</td>
</tr>
</table>
</div>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Email Alerts', 'wp-security-pack' ); ?></h2>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Enable', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[email_alerts_enabled]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'email_alerts_enabled', false ) ); ?> />
<?php esc_html_e( 'Send email alerts for security events', 'wp-security-pack' ); ?>
</label>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Email Address', 'wp-security-pack' ); ?></th>
<td>
<input type="email" name="wpsp_settings[email_alerts_address]" id="wpsp-alert-email"
value="<?php echo esc_attr( WP_Security_Pack::get_setting( 'email_alerts_address', get_option( 'admin_email' ) ) ); ?>" class="regular-text" />
<button type="button" class="button" id="wpsp-test-email">
<?php esc_html_e( 'Send Test Email', 'wp-security-pack' ); ?>
</button>
<span id="wpsp-test-email-result" style="margin-left: 10px;"></span>
<p class="description"><?php esc_html_e( 'Leave empty to use the site admin email.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Alert Threshold', 'wp-security-pack' ); ?></th>
<td>
<input type="number" name="wpsp_settings[email_alert_threshold]" min="1" max="100"
value="<?php echo esc_attr( WP_Security_Pack::get_setting( 'email_alert_threshold', 3 ) ); ?>" class="small-text" />
</td>
</tr>
</table>
</div>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Login Attempt Limiting', 'wp-security-pack' ); ?></h2>
<p class="description"><?php esc_html_e( 'Blocks IPs that fail too many login attempts. Stops brute force attacks.', 'wp-security-pack' ); ?></p>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Enable', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[login_limit_enabled]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'login_limit_enabled', true ) ); ?> />
<?php esc_html_e( 'Limit failed login attempts', 'wp-security-pack' ); ?>
</label>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Max Attempts', 'wp-security-pack' ); ?></th>
<td>
<input type="number" name="wpsp_settings[login_max_attempts]" min="1" max="100"
value="<?php echo esc_attr( WP_Security_Pack::get_setting( 'login_max_attempts', 5 ) ); ?>" class="small-text" />
<p class="description"><?php esc_html_e( 'Number of failed attempts before lockout.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Lockout Duration', 'wp-security-pack' ); ?></th>
<td>
<input type="number" name="wpsp_settings[login_lockout_duration]" min="1" max="1440"
value="<?php echo esc_attr( WP_Security_Pack::get_setting( 'login_lockout_duration', 15 ) ); ?>" class="small-text" />
<?php esc_html_e( 'minutes', 'wp-security-pack' ); ?>
</td>
</tr>
</table>
</div>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Custom Login URL', 'wp-security-pack' ); ?></h2>
<p class="description"><?php esc_html_e( 'Hides wp-login.php. Bots can\'t attack what they can\'t find.', 'wp-security-pack' ); ?></p>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Enable', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[login_rename_enabled]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'login_rename_enabled', false ) ); ?> />
<?php esc_html_e( 'Use custom login URL', 'wp-security-pack' ); ?>
</label>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Custom URL', 'wp-security-pack' ); ?></th>
<td>
<code><?php echo esc_url( home_url( '/' ) ); ?></code>
<input type="text" name="wpsp_settings[login_custom_url]"
value="<?php echo esc_attr( WP_Security_Pack::get_setting( 'login_custom_url', '' ) ); ?>" class="regular-text" />
<p class="description"><?php esc_html_e( 'Letters, numbers, and hyphens only. Example: my-secret-login', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Hide wp-admin', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[hide_wp_admin]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'hide_wp_admin', false ) ); ?> />
<?php esc_html_e( 'Return 404 for /wp-admin when not logged in', 'wp-security-pack' ); ?>
</label>
<p class="description"><?php esc_html_e( 'Also blocks /wp-login.php with 404.', 'wp-security-pack' ); ?></p>
</td>
</tr>
</table>
</div>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Honeypot Protection', 'wp-security-pack' ); ?></h2>
<p class="description"><?php esc_html_e( 'Hidden field that catches bots. Humans can\'t see it, bots fill it out and get blocked.', 'wp-security-pack' ); ?></p>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Enable', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[honeypot_enabled]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'honeypot_enabled', true ) ); ?> />
<?php esc_html_e( 'Enable honeypot bot detection', 'wp-security-pack' ); ?>
</label>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Ban Duration', 'wp-security-pack' ); ?></th>
<td>
<input type="number" name="wpsp_settings[honeypot_ban_duration]" min="1" max="1440"
value="<?php echo esc_attr( WP_Security_Pack::get_setting( 'honeypot_ban_duration', 60 ) ); ?>" class="small-text" />
<?php esc_html_e( 'minutes', 'wp-security-pack' ); ?>
</td>
</tr>
</table>
</div>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Additional Login Security', 'wp-security-pack' ); ?></h2>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Hide Login Errors', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[hide_login_errors]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'hide_login_errors', true ) ); ?> />
<?php esc_html_e( 'Show generic error message on failed login', 'wp-security-pack' ); ?>
</label>
<p class="description"><?php esc_html_e( 'Stops username enumeration.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Admin Login Alerts', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[admin_login_notify]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'admin_login_notify', false ) ); ?> />
<?php esc_html_e( 'Email notification when admin logs in from new IP', 'wp-security-pack' ); ?>
</label>
</td>
</tr>
</table>
</div>
<script>
jQuery(document).ready(function($) {
$('#wpsp-test-email').on('click', function() {
var $btn = $(this);
var $result = $('#wpsp-test-email-result');
var email = $('#wpsp-alert-email').val() || '<?php echo esc_js( get_option( 'admin_email' ) ); ?>';
$btn.prop('disabled', true);
$result.html('<span style="color: #666;"><?php echo esc_js( __( 'Sending...', 'wp-security-pack' ) ); ?></span>');
$.post(ajaxurl, {
action: 'wpsp_test_email',
email: email,
_ajax_nonce: '<?php echo esc_js( wp_create_nonce( 'wpsp_admin' ) ); ?>'
}, function(response) {
$btn.prop('disabled', false);
if (response.success) {
$result.html('<span style="color: #46b450;"><?php echo esc_js( __( 'Test email sent!', 'wp-security-pack' ) ); ?></span>');
} else {
$result.html('<span style="color: #dc3232;">' + response.data.message + '</span>');
}
}).fail(function() {
$btn.prop('disabled', false);
$result.html('<span style="color: #dc3232;"><?php echo esc_js( __( 'Request failed.', 'wp-security-pack' ) ); ?></span>');
});
});
});
</script>
<?php
}
/**
* Render IP control tab.
*/
private function render_ip_tab() {
$blocked_ips = array();
$ip_control = wpsp()->get_component( 'ip_control' );
if ( $ip_control ) {
$blocked_ips = $ip_control->get_blocked_ips();
}
$geo_blocking = wpsp()->get_component( 'geo_blocking' );
$db_info = $geo_blocking ? $geo_blocking->get_database_info() : array( 'exists' => false );
$countries = WPSP_Helper::get_countries_with_flags();
?>
<input type="hidden" name="wpsp_settings[wpsp_current_tab]" value="ip" />
<div class="wpsp-section">
<h2><?php esc_html_e( 'Country Blocking', 'wp-security-pack' ); ?></h2>
<p class="wpsp-description">
<strong><?php esc_html_e( 'Warning:', 'wp-security-pack' ); ?></strong>
<?php esc_html_e( 'Blocks the ENTIRE site, not just login.', 'wp-security-pack' ); ?>
</p>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Enable', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[geo_blocking_enabled]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'geo_blocking_enabled', false ) ); ?> />
<?php esc_html_e( 'Enable country-based blocking', 'wp-security-pack' ); ?>
</label>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'GeoIP Database', 'wp-security-pack' ); ?></th>
<td>
<?php if ( $db_info['exists'] ) : ?>
<span class="wpsp-status wpsp-status-ok">
<?php esc_html_e( 'Installed', 'wp-security-pack' ); ?>
</span>
<span class="description">
<?php echo esc_html( $db_info['type'] ); ?> -
<?php echo esc_html( size_format( $db_info['size'] ) ); ?>
<?php if ( ! empty( $db_info['modified'] ) ) : ?>
- <?php printf( esc_html__( 'Updated: %s', 'wp-security-pack' ), esc_html( date_i18n( get_option( 'date_format' ), $db_info['modified'] ) ) ); ?>
<?php endif; ?>
</span>
<br><br>
<button type="button" class="button" id="wpsp-download-geo-db">
<?php esc_html_e( 'Re-download Database', 'wp-security-pack' ); ?>
</button>
<p class="description">
<?php esc_html_e( 'Download the latest version of the IP2Location LITE database.', 'wp-security-pack' ); ?>
</p>
<?php else : ?>
<span class="wpsp-status wpsp-status-warning">
<?php esc_html_e( 'Not installed', 'wp-security-pack' ); ?>
</span>
<button type="button" class="button" id="wpsp-download-geo-db">
<?php esc_html_e( 'Download IP2Location Lite DB', 'wp-security-pack' ); ?>
</button>
<p class="description">
<?php esc_html_e( 'Free IP2Location LITE database. No account required.', 'wp-security-pack' ); ?>
</p>
<?php endif; ?>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Blocked Countries', 'wp-security-pack' ); ?></th>
<td>
<select name="wpsp_settings[geo_blocked_countries][]" multiple="multiple" size="10" class="wpsp-country-select">
<?php
$blocked = WP_Security_Pack::get_setting( 'geo_blocked_countries', array() );
foreach ( $countries as $code => $name ) :
?>
<option value="<?php echo esc_attr( $code ); ?>"
<?php selected( in_array( $code, $blocked, true ) ); ?>>
<?php echo esc_html( $name ); ?>
</option>
<?php endforeach; ?>
</select>
<p class="description"><?php esc_html_e( 'Ctrl/Cmd to select multiple.', 'wp-security-pack' ); ?></p>
</td>
</tr>
</table>
</div>
<div class="wpsp-section">
<h2><?php esc_html_e( 'IP Whitelist (Global Bypass)', 'wp-security-pack' ); ?></h2>
<p class="wpsp-description"><?php esc_html_e( 'These IPs bypass all security checks.', 'wp-security-pack' ); ?></p>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Whitelisted IPs', 'wp-security-pack' ); ?></th>
<td>
<textarea name="wpsp_settings[ip_whitelist]" rows="6" class="large-text code"><?php
echo esc_textarea( WP_Security_Pack::get_setting( 'ip_whitelist', '' ) );
?></textarea>
<p class="description">
<?php esc_html_e( 'One IP or CIDR per line.', 'wp-security-pack' ); ?>
<?php printf( esc_html__( 'Your IP: %s', 'wp-security-pack' ), '<code>' . esc_html( WPSP_Helper::get_client_ip() ) . '</code>' ); ?>
</p>
</td>
</tr>
</table>
</div>
<div class="wpsp-section">
<h2><?php esc_html_e( 'IP Blacklist', 'wp-security-pack' ); ?></h2>
<p class="description"><?php esc_html_e( 'Permanently blocked IPs. They can\'t access anything.', 'wp-security-pack' ); ?></p>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Blacklisted IPs', 'wp-security-pack' ); ?></th>
<td>
<textarea name="wpsp_settings[ip_blacklist]" rows="6" class="large-text code"><?php
echo esc_textarea( WP_Security_Pack::get_setting( 'ip_blacklist', '' ) );
?></textarea>
<p class="description"><?php esc_html_e( 'One IP or CIDR per line. # for comments.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Auto-Blacklist', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[auto_blacklist_enabled]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'auto_blacklist_enabled', false ) ); ?> />
<?php esc_html_e( 'Automatically blacklist repeat offenders', 'wp-security-pack' ); ?>
</label>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Auto-Blacklist Threshold', 'wp-security-pack' ); ?></th>
<td>
<input type="number" name="wpsp_settings[auto_blacklist_threshold]" min="1" max="20"
value="<?php echo esc_attr( WP_Security_Pack::get_setting( 'auto_blacklist_threshold', 3 ) ); ?>" class="small-text" />
<?php esc_html_e( 'lockouts', 'wp-security-pack' ); ?>
</td>
</tr>
</table>
</div>
<?php if ( ! empty( $blocked_ips ) ) : ?>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Currently Locked Out IPs', 'wp-security-pack' ); ?></h2>
<table class="widefat striped">
<thead>
<tr>
<th><?php esc_html_e( 'IP Address', 'wp-security-pack' ); ?></th>
<th><?php esc_html_e( 'Failed Attempts', 'wp-security-pack' ); ?></th>
<th><?php esc_html_e( 'Locked Until', 'wp-security-pack' ); ?></th>
<th><?php esc_html_e( 'Actions', 'wp-security-pack' ); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ( $blocked_ips as $lockout ) : ?>
<tr>
<td><code><?php echo esc_html( $lockout->ip_address ); ?></code></td>
<td><?php echo esc_html( $lockout->failed_attempts ); ?></td>
<td><?php echo esc_html( $lockout->lockout_until ); ?></td>
<td>
<button type="button" class="button button-small wpsp-unblock-ip"
data-ip="<?php echo esc_attr( $lockout->ip_address ); ?>">
<?php esc_html_e( 'Unblock', 'wp-security-pack' ); ?>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<script>
jQuery(document).ready(function($) {
$('.wpsp-unblock-ip').on('click', function() {
var ip = $(this).data('ip');
var $row = $(this).closest('tr');
$.post(ajaxurl, {
action: 'wpsp_unblock_ip',
ip: ip,
_ajax_nonce: '<?php echo esc_js( wp_create_nonce( 'wpsp_admin' ) ); ?>'
}, function(response) {
if (response.success) {
$row.fadeOut();
}
});
});
$('#wpsp-download-geo-db').on('click', function() {
var $btn = $(this);
$btn.prop('disabled', true).text('<?php echo esc_js( __( 'Downloading...', 'wp-security-pack' ) ); ?>');
$.post(ajaxurl, {
action: 'wpsp_download_geo_db',
_ajax_nonce: '<?php echo esc_js( wp_create_nonce( 'wpsp_admin' ) ); ?>'
}, function(response) {
if (response.success) {
location.reload();
} else {
alert(response.data.message);
$btn.prop('disabled', false).text('<?php echo esc_js( __( 'Download IP2Location Lite DB', 'wp-security-pack' ) ); ?>');
}
});
});
});
</script>
<?php
}
/**
* Render hardening tab.
*/
private function render_hardening_tab() {
?>
<input type="hidden" name="wpsp_settings[wpsp_current_tab]" value="hardening" />
<div class="wpsp-section">
<h2><?php esc_html_e( 'Security Hardening', 'wp-security-pack' ); ?></h2>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Disable XML-RPC', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[disable_xmlrpc]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'disable_xmlrpc', true ) ); ?> />
<?php esc_html_e( 'Disable XML-RPC endpoint', 'wp-security-pack' ); ?>
</label>
<p class="description"><?php esc_html_e( 'Disable unless needed for Jetpack or WordPress mobile app.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Disable File Editing', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[disable_file_editing]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'disable_file_editing', true ) ); ?> />
<?php esc_html_e( 'Disable plugin and theme file editor', 'wp-security-pack' ); ?>
</label>
<p class="description"><?php esc_html_e( 'Removes Theme Editor and Plugin Editor from dashboard.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Disable Application Passwords', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[disable_application_passwords]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'disable_application_passwords', false ) ); ?> />
<?php esc_html_e( 'Disable WordPress application passwords', 'wp-security-pack' ); ?>
</label>
<p class="description"><?php esc_html_e( 'Disable if you don\'t use third-party apps.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Restrict REST API', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[restrict_rest_api]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'restrict_rest_api', true ) ); ?> />
<?php esc_html_e( 'Require authentication for REST API access', 'wp-security-pack' ); ?>
</label>
<p class="description"><?php esc_html_e( 'Blocks anonymous REST API access. oEmbed still works.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Remove WP Version', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[remove_wp_version]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'remove_wp_version', true ) ); ?> />
<?php esc_html_e( 'Hide WordPress version information', 'wp-security-pack' ); ?>
</label>
<p class="description"><?php esc_html_e( 'Removes version from source, feeds, and script URLs.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Remove Feed Links', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[remove_feed_links]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'remove_feed_links', false ) ); ?> />
<?php esc_html_e( 'Remove RSS feed links from HTML head', 'wp-security-pack' ); ?>
</label>
<p class="description"><?php esc_html_e( 'Only enable if you don\'t use RSS.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Security Headers', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[add_security_headers]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'add_security_headers', true ) ); ?> />
<?php esc_html_e( 'Add HTTP security headers', 'wp-security-pack' ); ?>
</label>
<p class="description"><?php esc_html_e( 'Configure in Security Headers tab.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Block User Enumeration', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[disable_user_enumeration]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'disable_user_enumeration', true ) ); ?> />
<?php esc_html_e( 'Prevent username discovery', 'wp-security-pack' ); ?>
</label>
<p class="description"><?php esc_html_e( 'Blocks ?author=N scans and hides usernames from REST API.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Disable Pingbacks', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[disable_pingbacks]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'disable_pingbacks', true ) ); ?> />
<?php esc_html_e( 'Disable pingbacks and trackbacks', 'wp-security-pack' ); ?>
</label>
<p class="description"><?php esc_html_e( 'Rarely used legitimately, often abused for DDoS.', 'wp-security-pack' ); ?></p>
</td>
</tr>
</table>
</div>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Always Active', 'wp-security-pack' ); ?></h2>
<p class="description">
<?php esc_html_e( 'The following security measures are automatically applied:', 'wp-security-pack' ); ?>
</p>
<ul style="list-style: disc; margin-left: 20px; color: #666;">
<li><?php esc_html_e( 'RSD (Really Simple Discovery) link removed from HTML head', 'wp-security-pack' ); ?></li>
<li><?php esc_html_e( 'Windows Live Writer manifest link removed', 'wp-security-pack' ); ?></li>
<li><?php esc_html_e( 'Shortlink removed from HTML head', 'wp-security-pack' ); ?></li>
</ul>
</div>
<?php
}
/**
* Render security headers tab.
*/
private function render_headers_tab() {
$hardening = new WPSP_Hardening();
$default_headers = $hardening->get_default_headers();
$headers = WP_Security_Pack::get_setting( 'security_headers', $default_headers );
?>
<input type="hidden" name="wpsp_settings[wpsp_current_tab]" value="headers" />
<div class="wpsp-section">
<h2><?php esc_html_e( 'Security Headers Configuration', 'wp-security-pack' ); ?></h2>
<p class="description"><?php esc_html_e( 'HTTP headers that tell browsers to enable security features. Leave empty to disable.', 'wp-security-pack' ); ?></p>
<div class="notice notice-info inline" style="margin: 10px 0;">
<p>
<?php
printf(
/* translators: %s: Link to securityheaders.com */
esc_html__( 'Test your headers at %s after saving.', 'wp-security-pack' ),
'<a href="https://securityheaders.com/" target="_blank" rel="noopener">securityheaders.com</a>'
);
?>
<button type="button" class="button button-small" id="wpsp-reset-headers" style="margin-left: 15px;">
<?php esc_html_e( 'Reset to Defaults', 'wp-security-pack' ); ?>
</button>
</p>
</div>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'X-Content-Type-Options', 'wp-security-pack' ); ?></th>
<td>
<input type="text" name="wpsp_settings[security_headers][x_content_type_options]" id="header-x_content_type_options"
value="<?php echo esc_attr( $headers['x_content_type_options'] ?? 'nosniff' ); ?>" class="regular-text"
data-default="nosniff" />
<p class="description"><?php esc_html_e( 'Prevents MIME-sniffing attacks.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'X-Frame-Options', 'wp-security-pack' ); ?></th>
<td>
<select name="wpsp_settings[security_headers][x_frame_options]" id="header-x_frame_options" data-default="SAMEORIGIN">
<option value="" <?php selected( empty( $headers['x_frame_options'] ) ); ?>><?php esc_html_e( 'Disabled', 'wp-security-pack' ); ?></option>
<option value="DENY" <?php selected( $headers['x_frame_options'] ?? '', 'DENY' ); ?>>DENY</option>
<option value="SAMEORIGIN" <?php selected( $headers['x_frame_options'] ?? '', 'SAMEORIGIN' ); ?>>SAMEORIGIN</option>
</select>
<p class="description"><?php esc_html_e( 'Clickjacking protection. DENY = no iframes, SAMEORIGIN = same domain only.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'X-XSS-Protection', 'wp-security-pack' ); ?></th>
<td>
<input type="text" name="wpsp_settings[security_headers][x_xss_protection]" id="header-x_xss_protection"
value="<?php echo esc_attr( $headers['x_xss_protection'] ?? '1; mode=block' ); ?>" class="regular-text"
data-default="1; mode=block" />
<p class="description"><?php esc_html_e( 'Legacy XSS filter. Only matters for old browsers.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Referrer-Policy', 'wp-security-pack' ); ?></th>
<td>
<select name="wpsp_settings[security_headers][referrer_policy]" id="header-referrer_policy" data-default="strict-origin-when-cross-origin">
<option value="" <?php selected( empty( $headers['referrer_policy'] ) ); ?>><?php esc_html_e( 'Disabled', 'wp-security-pack' ); ?></option>
<option value="no-referrer" <?php selected( $headers['referrer_policy'] ?? '', 'no-referrer' ); ?>>no-referrer</option>
<option value="no-referrer-when-downgrade" <?php selected( $headers['referrer_policy'] ?? '', 'no-referrer-when-downgrade' ); ?>>no-referrer-when-downgrade</option>
<option value="origin" <?php selected( $headers['referrer_policy'] ?? '', 'origin' ); ?>>origin</option>
<option value="origin-when-cross-origin" <?php selected( $headers['referrer_policy'] ?? '', 'origin-when-cross-origin' ); ?>>origin-when-cross-origin</option>
<option value="same-origin" <?php selected( $headers['referrer_policy'] ?? '', 'same-origin' ); ?>>same-origin</option>
<option value="strict-origin" <?php selected( $headers['referrer_policy'] ?? '', 'strict-origin' ); ?>>strict-origin</option>
<option value="strict-origin-when-cross-origin" <?php selected( $headers['referrer_policy'] ?? '', 'strict-origin-when-cross-origin' ); ?>>strict-origin-when-cross-origin</option>
</select>
<p class="description"><?php esc_html_e( 'Controls referrer info sent to other sites.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Permissions-Policy', 'wp-security-pack' ); ?></th>
<td>
<input type="text" name="wpsp_settings[security_headers][permissions_policy]" id="header-permissions_policy"
value="<?php echo esc_attr( $headers['permissions_policy'] ?? 'geolocation=(), microphone=(), camera=()' ); ?>" class="large-text"
data-default="geolocation=(), microphone=(), camera=()" />
<p class="description"><?php esc_html_e( 'Empty () disables the feature. Controls browser API access.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Content-Security-Policy', 'wp-security-pack' ); ?></th>
<td>
<textarea name="wpsp_settings[security_headers][content_security_policy]" id="header-content_security_policy" rows="4" class="large-text code" data-default=""><?php
echo esc_textarea( $headers['content_security_policy'] ?? '' );
?></textarea>
<p style="margin-top: 8px;">
<button type="button" class="button button-small" id="wpsp-load-example-csp">
<?php esc_html_e( 'Load Example CSP', 'wp-security-pack' ); ?>
</button>
<span class="description" style="margin-left: 10px;"><?php esc_html_e( 'Loads a basic WordPress-compatible CSP as a starting point.', 'wp-security-pack' ); ?></span>
</p>
<p class="description">
<strong><?php esc_html_e( 'Warning:', 'wp-security-pack' ); ?></strong> <?php esc_html_e( 'Incorrect CSP can break your site. Test thoroughly. Leave empty to disable.', 'wp-security-pack' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Strict-Transport-Security', 'wp-security-pack' ); ?></th>
<td>
<input type="text" name="wpsp_settings[security_headers][strict_transport_security]" id="header-strict_transport_security"
value="<?php echo esc_attr( $headers['strict_transport_security'] ?? 'max-age=31536000; includeSubDomains' ); ?>" class="large-text"
data-default="max-age=31536000; includeSubDomains" />
<p class="description"><?php esc_html_e( 'Forces HTTPS. 31536000 = 1 year.', 'wp-security-pack' ); ?></p>
</td>
</tr>
</table>
</div>
<script>
jQuery(document).ready(function($) {
$('#wpsp-reset-headers').on('click', function() {
if (confirm('<?php echo esc_js( __( 'Reset all security headers to their default values?', 'wp-security-pack' ) ); ?>')) {
$('[id^="header-"]').each(function() {
var defaultVal = $(this).data('default');
if ($(this).is('select')) {
$(this).val(defaultVal);
} else if ($(this).is('textarea')) {
$(this).val(defaultVal);
} else {
$(this).val(defaultVal);
}
});
}
});
$('#wpsp-load-example-csp').on('click', function() {
var exampleCSP = "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self';";
$('#header-content_security_policy').val(exampleCSP);
});
});
</script>
<?php
}
/**
* Render 2FA tab.
*/
private function render_2fa_tab() {
// Count users with 2FA enabled.
$users_with_2fa = get_users( array(
'meta_key' => '_wpsp_2fa_enabled',
'meta_value' => '1',
'fields' => 'ID',
) );
$total_admins = count( get_users( array(
'role' => 'administrator',
'fields' => 'ID',
) ) );
$admins_with_2fa = 0;
foreach ( $users_with_2fa as $user_id ) {
if ( user_can( $user_id, 'manage_options' ) ) {
$admins_with_2fa++;
}
}
?>
<input type="hidden" name="wpsp_settings[wpsp_current_tab]" value="2fa" />
<div class="wpsp-section">
<h2><?php esc_html_e( 'Two-Factor Authentication', 'wp-security-pack' ); ?></h2>
<p class="description"><?php esc_html_e( 'Requires a code from your phone app after password. Stops attackers even if they steal your password.', 'wp-security-pack' ); ?></p>
<?php if ( count( $users_with_2fa ) > 0 || $total_admins > 0 ) : ?>
<div class="wpsp-stats" style="margin: 15px 0;">
<div class="wpsp-stat">
<span class="wpsp-stat-value"><?php echo esc_html( count( $users_with_2fa ) ); ?></span>
<span class="wpsp-stat-label"><?php esc_html_e( 'Users with 2FA', 'wp-security-pack' ); ?></span>
</div>
<div class="wpsp-stat">
<span class="wpsp-stat-value"><?php echo esc_html( $admins_with_2fa . '/' . $total_admins ); ?></span>
<span class="wpsp-stat-label"><?php esc_html_e( 'Admins with 2FA', 'wp-security-pack' ); ?></span>
</div>
</div>
<?php endif; ?>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Enable 2FA', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[two_factor_enabled]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'two_factor_enabled', false ) ); ?> />
<?php esc_html_e( 'Allow users to enable Two-Factor Authentication', 'wp-security-pack' ); ?>
</label>
<p class="description"><?php esc_html_e( 'When enabled, users can set up 2FA from their Profile page.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Enforce for Admins', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[two_factor_enforce_admin]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'two_factor_enforce_admin', false ) ); ?> />
<?php esc_html_e( 'Require 2FA for administrator accounts', 'wp-security-pack' ); ?>
</label>
<p class="description"><?php esc_html_e( 'Admins must set up 2FA to access dashboard.', 'wp-security-pack' ); ?></p>
</td>
</tr>
</table>
</div>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Compatible Authenticator Apps', 'wp-security-pack' ); ?></h2>
<p class="description"><?php esc_html_e( 'Any TOTP app works:', 'wp-security-pack' ); ?></p>
<ul style="list-style: disc; margin-left: 20px;">
<li><strong>Google Authenticator</strong> - <?php esc_html_e( 'iOS & Android', 'wp-security-pack' ); ?></li>
<li><strong>Authy</strong> - <?php esc_html_e( 'iOS, Android, Desktop (with cloud backup)', 'wp-security-pack' ); ?></li>
<li><strong>Microsoft Authenticator</strong> - <?php esc_html_e( 'iOS & Android', 'wp-security-pack' ); ?></li>
<li><strong>1Password</strong> - <?php esc_html_e( 'iOS, Android, Desktop', 'wp-security-pack' ); ?></li>
<li><strong>Bitwarden</strong> - <?php esc_html_e( 'All platforms (premium feature)', 'wp-security-pack' ); ?></li>
</ul>
</div>
<div class="wpsp-section">
<h2><?php esc_html_e( 'How it works', 'wp-security-pack' ); ?></h2>
<ol>
<li><?php esc_html_e( 'Enable 2FA above and save settings.', 'wp-security-pack' ); ?></li>
<li>
<?php
printf(
/* translators: %s: Link to profile page */
esc_html__( 'Users go to their %s and click "Set Up 2FA".', 'wp-security-pack' ),
'<a href="' . esc_url( admin_url( 'profile.php' ) ) . '">' . esc_html__( 'Profile page', 'wp-security-pack' ) . '</a>'
);
?>
</li>
<li><?php esc_html_e( 'Scan the QR code with your authenticator app or enter the secret key manually.', 'wp-security-pack' ); ?></li>
<li><?php esc_html_e( 'Enter the 6-digit verification code to confirm setup.', 'wp-security-pack' ); ?></li>
<li><?php esc_html_e( 'On next login, enter your password as usual, then the 2FA code from your app.', 'wp-security-pack' ); ?></li>
</ol>
</div>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Backup Codes', 'wp-security-pack' ); ?></h2>
<p class="description">
<?php esc_html_e( '10 one-time backup codes are generated at setup.', 'wp-security-pack' ); ?>
</p>
<ul style="list-style: disc; margin-left: 20px; color: #666;">
<li><?php esc_html_e( 'Codes are shown ONCE at setup - users must save them immediately', 'wp-security-pack' ); ?></li>
<li><?php esc_html_e( 'Codes are stored securely hashed (not viewable later)', 'wp-security-pack' ); ?></li>
<li><?php esc_html_e( 'Users can regenerate codes from their Profile (invalidates old codes)', 'wp-security-pack' ); ?></li>
</ul>
</div>
<?php
}
/**
* Render scanner tab.
*/
private function render_scanner_tab() {
$file_integrity = new WPSP_File_Integrity();
$file_results = $file_integrity->get_last_scan_results();
$malware_scanner = new WPSP_Malware_Scanner();
$malware_results = $malware_scanner->get_last_scan_results();
$file_changes_count = count( $file_results['changes']['modified'] ?? array() );
$malware_issues_count = count( $malware_results['results'] ?? array() );
?>
<input type="hidden" name="wpsp_settings[wpsp_current_tab]" value="scanner" />
<?php if ( $file_results['time'] > 0 || $malware_results['time'] > 0 ) : ?>
<div class="wpsp-stats" style="margin-bottom: 20px;">
<div class="wpsp-stat">
<span class="wpsp-stat-value" style="color: <?php echo $file_changes_count > 0 ? '#dc3232' : '#46b450'; ?>;">
<?php echo $file_changes_count > 0 ? esc_html( $file_changes_count ) : '✓'; ?>
</span>
<span class="wpsp-stat-label"><?php esc_html_e( 'Core File Issues', 'wp-security-pack' ); ?></span>
</div>
<div class="wpsp-stat">
<span class="wpsp-stat-value" style="color: <?php echo $malware_issues_count > 0 ? '#dc3232' : '#46b450'; ?>;">
<?php echo $malware_issues_count > 0 ? esc_html( $malware_issues_count ) : '✓'; ?>
</span>
<span class="wpsp-stat-label"><?php esc_html_e( 'Suspicious Files', 'wp-security-pack' ); ?></span>
</div>
</div>
<?php endif; ?>
<div class="wpsp-section">
<h2><?php esc_html_e( 'File Integrity Monitoring', 'wp-security-pack' ); ?></h2>
<p class="description"><?php esc_html_e( 'Detects if core WordPress files have been modified. Catches hacked files.', 'wp-security-pack' ); ?></p>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Enable', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[file_integrity_enabled]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'file_integrity_enabled', true ) ); ?> />
<?php esc_html_e( 'Monitor WordPress core files for changes', 'wp-security-pack' ); ?>
</label>
<p class="description"><?php esc_html_e( 'Runs automatically once per day.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Last Scan', 'wp-security-pack' ); ?></th>
<td>
<?php if ( $file_results['time'] > 0 ) : ?>
<?php echo esc_html( human_time_diff( $file_results['time'] ) . ' ' . __( 'ago', 'wp-security-pack' ) ); ?>
<?php else : ?>
<?php esc_html_e( 'Never', 'wp-security-pack' ); ?>
<?php endif; ?>
<button type="button" class="button" id="wpsp-run-file-scan">
<?php esc_html_e( 'Run Scan Now', 'wp-security-pack' ); ?>
</button>
<?php if ( $file_results['time'] > 0 ) : ?>
<button type="button" class="button" id="wpsp-reset-baseline">
<?php esc_html_e( 'Reset Baseline', 'wp-security-pack' ); ?>
</button>
<?php endif; ?>
</td>
</tr>
</table>
<?php if ( ! empty( $file_results['changes']['modified'] ) ) : ?>
<h3><?php esc_html_e( 'Modified Core Files', 'wp-security-pack' ); ?></h3>
<div class="notice notice-warning inline" style="margin: 10px 0;">
<p><?php esc_html_e( 'Files differ from WordPress.org. Could be: WP update (normal), manual edit, or compromise.', 'wp-security-pack' ); ?></p>
</div>
<table class="widefat striped">
<thead>
<tr>
<th><?php esc_html_e( 'File', 'wp-security-pack' ); ?></th>
<th><?php esc_html_e( 'Status', 'wp-security-pack' ); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ( $file_results['changes']['modified'] as $file ) : ?>
<tr>
<td><code><?php echo esc_html( is_array( $file ) ? $file['file'] : $file ); ?></code></td>
<td><span class="wpsp-status wpsp-status-warning"><?php esc_html_e( 'Modified', 'wp-security-pack' ); ?></span></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<p style="margin-top: 10px;">
<button type="button" class="button" id="wpsp-dismiss-file-changes">
<?php esc_html_e( 'Dismiss & Update Baseline', 'wp-security-pack' ); ?>
</button>
<span class="description" style="margin-left: 10px;"><?php esc_html_e( 'Use this after verifying the changes are legitimate.', 'wp-security-pack' ); ?></span>
</p>
<?php elseif ( $file_results['time'] > 0 ) : ?>
<div class="notice notice-success inline" style="margin: 10px 0;">
<p><?php esc_html_e( 'All core files match official WordPress.org checksums.', 'wp-security-pack' ); ?></p>
</div>
<?php endif; ?>
</div>
<?php
?>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Malware Scanner', 'wp-security-pack' ); ?></h2>
<p class="wpsp-description"><?php esc_html_e( 'Looks for backdoors, web shells, and suspicious code in plugins, themes, and uploads.', 'wp-security-pack' ); ?></p>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Enable', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[malware_scan_enabled]" value="1"
<?php checked( WP_Security_Pack::get_setting( 'malware_scan_enabled', true ) ); ?> />
<?php esc_html_e( 'Enable scheduled malware scanning', 'wp-security-pack' ); ?>
</label>
<p class="description"><?php esc_html_e( 'Runs weekly. Manual scans anytime.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Last Scan', 'wp-security-pack' ); ?></th>
<td>
<?php
$scan_stats = get_option( 'wpsp_malware_scan_stats', array() );
if ( $malware_results['time'] > 0 ) :
?>
<?php echo esc_html( human_time_diff( $malware_results['time'] ) . ' ' . __( 'ago', 'wp-security-pack' ) ); ?>
<?php if ( ! empty( $scan_stats['files_scanned'] ) ) : ?>
<span class="description" style="margin-left: 10px;">
<?php
printf(
/* translators: 1: Number of files, 2: Duration in seconds */
esc_html__( '(%1$s files in %2$ss)', 'wp-security-pack' ),
esc_html( number_format_i18n( $scan_stats['files_scanned'] ) ),
esc_html( $scan_stats['duration'] )
);
?>
</span>
<?php endif; ?>
<?php else : ?>
<?php esc_html_e( 'Never', 'wp-security-pack' ); ?>
<?php endif; ?>
<button type="button" class="button" id="wpsp-run-malware-scan">
<?php esc_html_e( 'Run Scan Now', 'wp-security-pack' ); ?>
</button>
<span id="wpsp-scan-status"></span>
</td>
</tr>
</table>
<?php if ( ! empty( $malware_results['results'] ) ) : ?>
<h3><?php esc_html_e( 'Suspicious Files Found', 'wp-security-pack' ); ?></h3>
<div class="notice notice-warning inline" style="margin: 10px 0;">
<p>
<?php esc_html_e( 'Review each finding - some may be false positives.', 'wp-security-pack' ); ?>
</p>
</div>
<table class="widefat striped">
<thead>
<tr>
<th><?php esc_html_e( 'File', 'wp-security-pack' ); ?></th>
<th><?php esc_html_e( 'Finding', 'wp-security-pack' ); ?></th>
<th style="width: 120px;"><?php esc_html_e( 'Actions', 'wp-security-pack' ); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ( $malware_results['results'] as $file => $findings ) : ?>
<tr data-file="<?php echo esc_attr( $file ); ?>">
<td><code><?php echo esc_html( str_replace( ABSPATH, '', $file ) ); ?></code></td>
<td>
<?php foreach ( $findings as $finding ) : ?>
<span class="wpsp-badge" style="background-color: <?php echo esc_attr( WPSP_Malware_Scanner::get_severity_color( $finding['severity'] ) ); ?>;">
<?php echo esc_html( strtoupper( $finding['severity'] ) ); ?>
</span>
<?php echo esc_html( $finding['name'] ); ?>
<span class="description" style="display: block; font-size: 12px; color: #666;">
<?php echo esc_html( $finding['description'] ); ?>
</span>
<?php endforeach; ?>
</td>
<td>
<button type="button" class="button button-small wpsp-quarantine-file" data-file="<?php echo esc_attr( $file ); ?>">
<?php esc_html_e( 'Quarantine', 'wp-security-pack' ); ?>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<?php if ( ! empty( $malware_results['results'] ) ) : ?>
<p style="margin-top: 10px;">
<button type="button" class="button" id="wpsp-clear-malware-results">
<?php esc_html_e( 'Clear Results', 'wp-security-pack' ); ?>
</button>
</p>
<?php elseif ( $malware_results['time'] > 0 ) : ?>
<div class="notice notice-success inline" style="margin: 10px 0;">
<p><?php esc_html_e( 'No malware or suspicious files detected.', 'wp-security-pack' ); ?></p>
</div>
<?php endif; ?>
<?php
$quarantined_files = $malware_scanner->get_quarantined_files();
if ( ! empty( $quarantined_files ) ) :
?>
<h3 style="margin-top: 20px;"><?php esc_html_e( 'Quarantined Files', 'wp-security-pack' ); ?></h3>
<table class="widefat striped" style="margin-top: 10px;">
<thead>
<tr>
<th><?php esc_html_e( 'Original Location', 'wp-security-pack' ); ?></th>
<th><?php esc_html_e( 'Quarantined', 'wp-security-pack' ); ?></th>
<th><?php esc_html_e( 'Size', 'wp-security-pack' ); ?></th>
<th style="width: 180px;"><?php esc_html_e( 'Actions', 'wp-security-pack' ); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ( $quarantined_files as $name => $meta ) : ?>
<tr data-quarantine-name="<?php echo esc_attr( $name ); ?>">
<td><code><?php echo esc_html( $meta['relative_path'] ?? $meta['original_name'] ); ?></code></td>
<td><?php echo esc_html( human_time_diff( $meta['quarantined_at'] ) . ' ' . __( 'ago', 'wp-security-pack' ) ); ?></td>
<td><?php echo esc_html( size_format( $meta['file_size'] ?? 0 ) ); ?></td>
<td>
<button type="button" class="button button-small wpsp-restore-file" data-name="<?php echo esc_attr( $name ); ?>">
<?php esc_html_e( 'Restore', 'wp-security-pack' ); ?>
</button>
<button type="button" class="button button-small wpsp-delete-quarantined" data-name="<?php echo esc_attr( $name ); ?>" style="color: #a00;">
<?php esc_html_e( 'Delete', 'wp-security-pack' ); ?>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Scan Coverage', 'wp-security-pack' ); ?></h2>
<h3><?php esc_html_e( 'Directories Scanned', 'wp-security-pack' ); ?></h3>
<ul style="list-style: disc; margin-left: 20px; color: #666;">
<li><strong><?php esc_html_e( 'File Integrity:', 'wp-security-pack' ); ?></strong> <?php esc_html_e( 'wp-admin/, wp-includes/, and root WordPress files', 'wp-security-pack' ); ?></li>
<li><strong><?php esc_html_e( 'Malware Scanner:', 'wp-security-pack' ); ?></strong> <?php esc_html_e( 'wp-content/plugins/, wp-content/themes/, wp-content/uploads/, wp-content/mu-plugins/', 'wp-security-pack' ); ?></li>
</ul>
<h3><?php esc_html_e( 'Detection Methods', 'wp-security-pack' ); ?></h3>
<table class="widefat striped" style="max-width: 600px;">
<tr>
<td><strong><?php esc_html_e( 'Checksum verification', 'wp-security-pack' ); ?></strong></td>
<td><?php esc_html_e( 'Compares core files against WordPress.org checksums', 'wp-security-pack' ); ?></td>
<td><span style="color: green;">✓ <?php esc_html_e( '100% accurate', 'wp-security-pack' ); ?></span></td>
</tr>
<tr>
<td><strong><?php esc_html_e( 'Pattern-based', 'wp-security-pack' ); ?></strong></td>
<td><?php esc_html_e( 'Scans code for suspicious patterns and functions', 'wp-security-pack' ); ?></td>
<td><span style="color: orange;">⚠ <?php esc_html_e( 'May have false positives', 'wp-security-pack' ); ?></span></td>
</tr>
</table>
</div>
<script>
jQuery(document).ready(function($) {
$('#wpsp-run-file-scan').on('click', function() {
var $btn = $(this);
$btn.prop('disabled', true).text('<?php echo esc_js( __( 'Scanning...', 'wp-security-pack' ) ); ?>');
$.post(ajaxurl, {
action: 'wpsp_run_file_scan',
_ajax_nonce: '<?php echo esc_js( wp_create_nonce( 'wpsp_admin' ) ); ?>'
}, function(response) {
location.reload();
});
});
$('#wpsp-reset-baseline, #wpsp-dismiss-file-changes').on('click', function() {
var $btn = $(this);
$btn.prop('disabled', true);
$.post(ajaxurl, {
action: 'wpsp_reset_file_baseline',
_ajax_nonce: '<?php echo esc_js( wp_create_nonce( 'wpsp_admin' ) ); ?>'
}, function(response) {
location.reload();
});
});
$('#wpsp-run-malware-scan').on('click', function() {
var $btn = $(this);
$btn.prop('disabled', true);
$('#wpsp-scan-status').text('<?php echo esc_js( __( 'Scanning... This may take a while.', 'wp-security-pack' ) ); ?>');
$.post(ajaxurl, {
action: 'wpsp_run_malware_scan',
_ajax_nonce: '<?php echo esc_js( wp_create_nonce( 'wpsp_admin' ) ); ?>'
}, function(response) {
location.reload();
});
});
$('#wpsp-clear-malware-results').on('click', function() {
var $btn = $(this);
$btn.prop('disabled', true);
$.post(ajaxurl, {
action: 'wpsp_clear_malware_results',
_ajax_nonce: '<?php echo esc_js( wp_create_nonce( 'wpsp_admin' ) ); ?>'
}, function(response) {
location.reload();
});
});
// Quarantine file.
$('.wpsp-quarantine-file').on('click', function() {
var $btn = $(this);
var filePath = $btn.data('file');
if (!confirm('<?php echo esc_js( __( 'Quarantine this file? It will be moved to a safe location and cannot execute.', 'wp-security-pack' ) ); ?>')) {
return;
}
$btn.prop('disabled', true).text('<?php echo esc_js( __( 'Moving...', 'wp-security-pack' ) ); ?>');
$.post(ajaxurl, {
action: 'wpsp_quarantine_file',
file_path: filePath,
_ajax_nonce: '<?php echo esc_js( wp_create_nonce( 'wpsp_admin' ) ); ?>'
}, function(response) {
if (response.success) {
$btn.closest('tr').fadeOut(function() {
$(this).remove();
location.reload();
});
} else {
alert(response.data.message || '<?php echo esc_js( __( 'Failed to quarantine file.', 'wp-security-pack' ) ); ?>');
$btn.prop('disabled', false).text('<?php echo esc_js( __( 'Quarantine', 'wp-security-pack' ) ); ?>');
}
});
});
// Restore file from quarantine.
$('.wpsp-restore-file').on('click', function() {
var $btn = $(this);
var name = $btn.data('name');
if (!confirm('<?php echo esc_js( __( 'Restore this file to its original location? Make sure it is safe before restoring.', 'wp-security-pack' ) ); ?>')) {
return;
}
$btn.prop('disabled', true).text('<?php echo esc_js( __( 'Restoring...', 'wp-security-pack' ) ); ?>');
$.post(ajaxurl, {
action: 'wpsp_restore_file',
quarantine_name: name,
_ajax_nonce: '<?php echo esc_js( wp_create_nonce( 'wpsp_admin' ) ); ?>'
}, function(response) {
if (response.success) {
location.reload();
} else {
alert(response.data.message || '<?php echo esc_js( __( 'Failed to restore file.', 'wp-security-pack' ) ); ?>');
$btn.prop('disabled', false).text('<?php echo esc_js( __( 'Restore', 'wp-security-pack' ) ); ?>');
}
});
});
// Delete quarantined file permanently.
$('.wpsp-delete-quarantined').on('click', function() {
var $btn = $(this);
var name = $btn.data('name');
if (!confirm('<?php echo esc_js( __( 'Permanently delete this file? This cannot be undone.', 'wp-security-pack' ) ); ?>')) {
return;
}
$btn.prop('disabled', true).text('<?php echo esc_js( __( 'Deleting...', 'wp-security-pack' ) ); ?>');
$.post(ajaxurl, {
action: 'wpsp_delete_quarantined',
quarantine_name: name,
_ajax_nonce: '<?php echo esc_js( wp_create_nonce( 'wpsp_admin' ) ); ?>'
}, function(response) {
if (response.success) {
$btn.closest('tr').fadeOut(function() {
$(this).remove();
});
} else {
alert(response.data.message || '<?php echo esc_js( __( 'Failed to delete file.', 'wp-security-pack' ) ); ?>');
$btn.prop('disabled', false).text('<?php echo esc_js( __( 'Delete', 'wp-security-pack' ) ); ?>');
}
});
});
});
</script>
<?php
}
/**
* Render logs tab.
*/
private function render_logs_tab() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$page = isset( $_GET['paged'] ) ? max( 1, absint( $_GET['paged'] ) ) : 1;
$per_page = 50;
$offset = ( $page - 1 ) * $per_page;
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$filter_type = isset( $_GET['event_type'] ) ? sanitize_text_field( wp_unslash( $_GET['event_type'] ) ) : '';
$logs = WPSP_Activity_Log::get_logs( array(
'limit' => $per_page,
'offset' => $offset,
'event_type' => $filter_type,
) );
$total = WPSP_Activity_Log::get_log_count( array( 'event_type' => $filter_type ) );
$pages = ceil( $total / $per_page );
$stats = WPSP_Activity_Log::get_stats( 30 );
?>
<input type="hidden" name="wpsp_settings[wpsp_current_tab]" value="logs" />
<div class="wpsp-section">
<h2><?php esc_html_e( 'Activity Log', 'wp-security-pack' ); ?></h2>
<p class="description"><?php esc_html_e( 'Login attempts, lockouts, and blocks. See who\'s trying to get in.', 'wp-security-pack' ); ?></p>
<h3><?php esc_html_e( 'Statistics (Last 30 Days)', 'wp-security-pack' ); ?></h3>
<div class="wpsp-stats">
<div class="wpsp-stat wpsp-stat-success">
<span class="wpsp-stat-value"><?php echo esc_html( $stats['login_success'] ); ?></span>
<span class="wpsp-stat-label"><?php esc_html_e( 'Successful Logins', 'wp-security-pack' ); ?></span>
</div>
<div class="wpsp-stat wpsp-stat-warning">
<span class="wpsp-stat-value"><?php echo esc_html( $stats['login_failed'] ); ?></span>
<span class="wpsp-stat-label"><?php esc_html_e( 'Failed Logins', 'wp-security-pack' ); ?></span>
</div>
<div class="wpsp-stat wpsp-stat-danger">
<span class="wpsp-stat-value"><?php echo esc_html( $stats['lockout'] ); ?></span>
<span class="wpsp-stat-label"><?php esc_html_e( 'Lockouts', 'wp-security-pack' ); ?></span>
</div>
<div class="wpsp-stat wpsp-stat-info">
<span class="wpsp-stat-value"><?php echo esc_html( $stats['ip_blocked'] + $stats['geo_blocked'] ); ?></span>
<span class="wpsp-stat-label"><?php esc_html_e( 'Blocks', 'wp-security-pack' ); ?></span>
</div>
</div>
<h3><?php esc_html_e( 'Log Events', 'wp-security-pack' ); ?></h3>
<div class="tablenav top">
<div class="alignleft actions">
<select id="wpsp-filter-type">
<option value=""><?php esc_html_e( 'All Events', 'wp-security-pack' ); ?></option>
<option value="login_success" <?php selected( $filter_type, 'login_success' ); ?>><?php esc_html_e( 'Login Success', 'wp-security-pack' ); ?></option>
<option value="login_failed" <?php selected( $filter_type, 'login_failed' ); ?>><?php esc_html_e( 'Login Failed', 'wp-security-pack' ); ?></option>
<option value="lockout" <?php selected( $filter_type, 'lockout' ); ?>><?php esc_html_e( 'Lockout', 'wp-security-pack' ); ?></option>
<option value="ip_blocked" <?php selected( $filter_type, 'ip_blocked' ); ?>><?php esc_html_e( 'IP Blocked', 'wp-security-pack' ); ?></option>
<option value="geo_blocked" <?php selected( $filter_type, 'geo_blocked' ); ?>><?php esc_html_e( 'Geo Blocked', 'wp-security-pack' ); ?></option>
</select>
<button type="button" class="button" id="wpsp-filter-logs"><?php esc_html_e( 'Filter', 'wp-security-pack' ); ?></button>
</div>
<div class="alignright">
<button type="button" class="button" id="wpsp-export-logs"><?php esc_html_e( 'Export CSV', 'wp-security-pack' ); ?></button>
<button type="button" class="button" id="wpsp-clear-logs"><?php esc_html_e( 'Clear All Logs', 'wp-security-pack' ); ?></button>
</div>
</div>
<table class="widefat striped">
<thead>
<tr>
<th><?php esc_html_e( 'Time', 'wp-security-pack' ); ?></th>
<th><?php esc_html_e( 'Event', 'wp-security-pack' ); ?></th>
<th style="width: 50px; text-align: center;"><?php esc_html_e( 'Country', 'wp-security-pack' ); ?></th>
<th><?php esc_html_e( 'IP Address', 'wp-security-pack' ); ?></th>
<th><?php esc_html_e( 'Username', 'wp-security-pack' ); ?></th>
<th><?php esc_html_e( 'Details', 'wp-security-pack' ); ?></th>
</tr>
</thead>
<tbody>
<?php if ( empty( $logs ) ) : ?>
<tr>
<td colspan="6"><?php esc_html_e( 'No activity logged yet.', 'wp-security-pack' ); ?></td>
</tr>
<?php else : ?>
<?php foreach ( $logs as $log ) : ?>
<tr>
<td><?php echo esc_html( $log->created_at ); ?></td>
<td>
<span class="wpsp-event-<?php echo esc_attr( $log->event_type ); ?>">
<?php echo esc_html( WPSP_Activity_Log::get_event_label( $log->event_type ) ); ?>
</span>
</td>
<td style="text-align: center;" title="<?php echo esc_attr( $log->country_code ? $log->country_code : '' ); ?>">
<?php if ( $log->country_code ) : ?>
<?php echo esc_html( WPSP_Helper::get_country_flag( $log->country_code ) ); ?>
<?php else : ?>
-
<?php endif; ?>
</td>
<td><code><?php echo esc_html( $log->ip_address ); ?></code></td>
<td><?php echo esc_html( $log->username ? $log->username : '-' ); ?></td>
<td><?php echo esc_html( $log->details ? $log->details : '-' ); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<?php if ( $pages > 1 ) : ?>
<div class="tablenav bottom">
<div class="tablenav-pages">
<?php
echo wp_kses_post( paginate_links( array(
'base' => add_query_arg( 'paged', '%#%' ),
'format' => '',
'current' => $page,
'total' => $pages,
) ) );
?>
</div>
</div>
<?php endif; ?>
</div>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Email Alerts', 'wp-security-pack' ); ?></h2>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Enable Email Alerts', 'wp-security-pack' ); ?></th>
<td>
<label>
<input type="checkbox" name="wpsp_settings[email_alerts_enabled]" value="1" <?php checked( WP_Security_Pack::get_setting( 'email_alerts_enabled', false ) ); ?> />
<?php esc_html_e( 'Send email notifications for security events', 'wp-security-pack' ); ?>
</label>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Alert Email Address', 'wp-security-pack' ); ?></th>
<td>
<input type="email" name="wpsp_settings[email_alerts_address]"
value="<?php echo esc_attr( WP_Security_Pack::get_setting( 'email_alerts_address', get_option( 'admin_email' ) ) ); ?>" class="regular-text" />
<p class="description"><?php esc_html_e( 'Leave empty to use the site admin email.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Alert Threshold', 'wp-security-pack' ); ?></th>
<td>
<input type="number" name="wpsp_settings[email_alert_threshold]" min="1" max="50"
value="<?php echo esc_attr( WP_Security_Pack::get_setting( 'email_alert_threshold', 3 ) ); ?>" class="small-text" />
<?php esc_html_e( 'failed login attempts', 'wp-security-pack' ); ?>
</td>
</tr>
</table>
</div>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Log Settings', 'wp-security-pack' ); ?></h2>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Log Retention', 'wp-security-pack' ); ?></th>
<td>
<input type="number" name="wpsp_settings[log_retention_days]" min="1" max="365"
value="<?php echo esc_attr( WP_Security_Pack::get_setting( 'log_retention_days', 30 ) ); ?>" class="small-text" />
<?php esc_html_e( 'days', 'wp-security-pack' ); ?>
<p class="description"><?php esc_html_e( 'Auto-deleted daily.', 'wp-security-pack' ); ?></p>
</td>
</tr>
</table>
<?php submit_button(); ?>
</div>
<script>
jQuery(document).ready(function($) {
$('#wpsp-filter-logs').on('click', function() {
var type = $('#wpsp-filter-type').val();
var url = new URL(window.location.href);
if (type) {
url.searchParams.set('event_type', type);
} else {
url.searchParams.delete('event_type');
}
url.searchParams.delete('paged');
window.location.href = url.toString();
});
$('#wpsp-export-logs').on('click', function() {
$.post(ajaxurl, {
action: 'wpsp_export_logs',
_ajax_nonce: '<?php echo esc_js( wp_create_nonce( 'wpsp_admin' ) ); ?>'
}, function(response) {
if (response.success) {
var blob = new Blob([response.data.csv], {type: 'text/csv'});
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = 'wp-security-pack-logs-' + new Date().toISOString().split('T')[0] + '.csv';
a.click();
URL.revokeObjectURL(url);
}
});
});
$('#wpsp-clear-logs').on('click', function() {
if (confirm('<?php echo esc_js( __( 'Are you sure you want to clear all logs? This cannot be undone.', 'wp-security-pack' ) ); ?>')) {
$.post(ajaxurl, {
action: 'wpsp_clear_logs',
_ajax_nonce: '<?php echo esc_js( wp_create_nonce( 'wpsp_admin' ) ); ?>'
}, function(response) {
if (response.success) {
location.reload();
}
});
}
});
});
</script>
<?php
}
/**
* Render tools tab.
*/
private function render_tools_tab() {
$current_ip = WPSP_Helper::get_client_ip();
$geo_blocking = wpsp()->get_component( 'geo_blocking' );
$geo_info = $geo_blocking ? $geo_blocking->get_database_info() : array( 'exists' => false );
$country_code = $geo_blocking && $current_ip ? $geo_blocking->get_country_code( $current_ip ) : null;
?>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Your Current IP', 'wp-security-pack' ); ?></h2>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Your IP Address', 'wp-security-pack' ); ?></th>
<td>
<code style="font-size: 14px; padding: 5px 10px;"><?php echo esc_html( $current_ip ); ?></code>
<?php if ( $country_code ) : ?>
<?php echo esc_html( WPSP_Helper::get_country_flag( $country_code ) . ' ' . $country_code ); ?>
<?php endif; ?>
<?php
$ip_control = wpsp()->get_component( 'ip_control' );
$is_whitelisted = $ip_control && $ip_control->is_whitelisted( $current_ip );
?>
<?php if ( $is_whitelisted ) : ?>
<span style="color: green; margin-left: 10px;">✓ <?php esc_html_e( 'Whitelisted', 'wp-security-pack' ); ?></span>
<?php else : ?>
<span style="color: orange; margin-left: 10px;">○ <?php esc_html_e( 'Not whitelisted', 'wp-security-pack' ); ?></span>
<?php endif; ?>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Add to Global Whitelist', 'wp-security-pack' ); ?></th>
<td>
<?php if ( ! $is_whitelisted ) : ?>
<button type="button" class="button button-primary" id="wpsp-whitelist-my-ip"><?php esc_html_e( 'Whitelist My IP', 'wp-security-pack' ); ?></button>
<span id="wpsp-whitelist-status"></span>
<p class="description"><?php esc_html_e( 'Whitelisted IPs bypass all security checks.', 'wp-security-pack' ); ?></p>
<?php else : ?>
<span style="color: green;">✓ <?php esc_html_e( 'Your IP is already in the global whitelist.', 'wp-security-pack' ); ?></span>
<?php endif; ?>
</td>
</tr>
</table>
</div>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Lockout Recovery', 'wp-security-pack' ); ?></h2>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Method 1: Wait', 'wp-security-pack' ); ?></th>
<td>
<?php
$lockout_duration = WP_Security_Pack::get_setting( 'login_lockout_duration', 15 );
printf(
/* translators: %d: lockout duration in minutes */
esc_html__( 'Lockouts automatically expire after %d minutes.', 'wp-security-pack' ),
$lockout_duration
);
?>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Method 2: FTP/File Manager', 'wp-security-pack' ); ?></th>
<td>
<?php esc_html_e( 'Rename or delete the plugin folder via FTP:', 'wp-security-pack' ); ?>
<code>wp-content/plugins/wp-security-pack</code>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Method 3: Database', 'wp-security-pack' ); ?></th>
<td>
<?php esc_html_e( 'Run this SQL query in phpMyAdmin to clear all lockouts:', 'wp-security-pack' ); ?>
<br><code>TRUNCATE TABLE <?php echo esc_html( $GLOBALS['wpdb']->prefix ); ?>wpsp_lockouts;</code>
</td>
</tr>
</table>
</div>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Manage Lockouts', 'wp-security-pack' ); ?></h2>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Clear All Lockouts', 'wp-security-pack' ); ?></th>
<td>
<button type="button" class="button" id="wpsp-clear-lockouts"><?php esc_html_e( 'Clear All Lockouts', 'wp-security-pack' ); ?></button>
<span id="wpsp-lockout-status"></span>
<p class="description"><?php esc_html_e( 'Removes all temporary IP lockouts from the database.', 'wp-security-pack' ); ?></p>
</td>
</tr>
</table>
</div>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Session Management', 'wp-security-pack' ); ?></h2>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Force Logout All Users', 'wp-security-pack' ); ?></th>
<td>
<button type="button" class="button" id="wpsp-force-logout-all"><?php esc_html_e( 'Logout All Users', 'wp-security-pack' ); ?></button>
<span id="wpsp-logout-status"></span>
<p class="description"><?php esc_html_e( 'Everyone (including you) will need to log in again.', 'wp-security-pack' ); ?></p>
</td>
</tr>
</table>
</div>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Import / Export Settings', 'wp-security-pack' ); ?></h2>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Export Settings', 'wp-security-pack' ); ?></th>
<td>
<button type="button" class="button" id="wpsp-export-settings"><?php esc_html_e( 'Export Settings', 'wp-security-pack' ); ?></button>
<p class="description"><?php esc_html_e( 'Download your current settings as a JSON file.', 'wp-security-pack' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Import Settings', 'wp-security-pack' ); ?></th>
<td>
<input type="file" id="wpsp-import-file" accept=".json" />
<button type="button" class="button" id="wpsp-import-settings"><?php esc_html_e( 'Import Settings', 'wp-security-pack' ); ?></button>
<span id="wpsp-import-status"></span>
<p class="description"><?php esc_html_e( 'Upload a previously exported settings file.', 'wp-security-pack' ); ?></p>
</td>
</tr>
</table>
</div>
<div class="wpsp-section">
<h2><?php esc_html_e( 'System Information', 'wp-security-pack' ); ?></h2>
<table class="widefat striped">
<tr>
<td style="width: 200px;"><strong><?php esc_html_e( 'WordPress Version', 'wp-security-pack' ); ?></strong></td>
<td><?php echo esc_html( get_bloginfo( 'version' ) ); ?></td>
</tr>
<tr>
<td><strong><?php esc_html_e( 'PHP Version', 'wp-security-pack' ); ?></strong></td>
<td><?php echo esc_html( PHP_VERSION ); ?></td>
</tr>
<tr>
<td><strong><?php esc_html_e( 'Plugin Version', 'wp-security-pack' ); ?></strong></td>
<td><?php echo esc_html( WPSP_VERSION ); ?></td>
</tr>
<tr>
<td><strong><?php esc_html_e( 'Server IP Address', 'wp-security-pack' ); ?></strong></td>
<td>
<?php
$server_ip = WPSP_Helper::get_server_ip();
if ( $server_ip ) :
?>
<code><?php echo esc_html( $server_ip ); ?></code>
<?php else : ?>
<em><?php esc_html_e( 'Unable to detect', 'wp-security-pack' ); ?></em>
<?php endif; ?>
</td>
</tr>
<tr>
<td><strong><?php esc_html_e( 'HTTPS', 'wp-security-pack' ); ?></strong></td>
<td>
<?php if ( is_ssl() ) : ?>
<span style="color: green;">✓</span> <?php esc_html_e( 'Yes', 'wp-security-pack' ); ?>
<?php else : ?>
<span style="color: red;">✗</span> <?php esc_html_e( 'No', 'wp-security-pack' ); ?>
<em>(<?php esc_html_e( 'HTTPS is recommended', 'wp-security-pack' ); ?>)</em>
<?php endif; ?>
</td>
</tr>
<tr>
<td><strong><?php esc_html_e( 'Geo Database', 'wp-security-pack' ); ?></strong></td>
<td>
<?php if ( $geo_info['exists'] ) : ?>
<span style="color: green;">✓</span> <?php esc_html_e( 'Installed', 'wp-security-pack' ); ?>
(<?php echo esc_html( size_format( $geo_info['size'] ) ); ?>)
<?php else : ?>
<span style="color: orange;">○</span> <?php esc_html_e( 'Not installed', 'wp-security-pack' ); ?>
<em>(<?php esc_html_e( 'Required for geo-blocking', 'wp-security-pack' ); ?>)</em>
<?php endif; ?>
</td>
</tr>
<tr>
<td><strong><?php esc_html_e( 'Custom Login URL', 'wp-security-pack' ); ?></strong></td>
<td>
<?php
$custom_url = WP_Security_Pack::get_setting( 'login_custom_url', '' );
if ( WP_Security_Pack::get_setting( 'login_rename_enabled', false ) && ! empty( $custom_url ) ) :
?>
<code><?php echo esc_html( home_url( '/' . $custom_url ) ); ?></code>
<?php else : ?>
<?php esc_html_e( 'Default (wp-login.php)', 'wp-security-pack' ); ?>
<?php endif; ?>
</td>
</tr>
<tr>
<td><strong><?php esc_html_e( 'Server Software', 'wp-security-pack' ); ?></strong></td>
<td><?php echo esc_html( isset( $_SERVER['SERVER_SOFTWARE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) : __( 'Unknown', 'wp-security-pack' ) ); ?></td>
</tr>
</table>
</div>
<div class="wpsp-section">
<h2><?php esc_html_e( 'Danger Zone', 'wp-security-pack' ); ?></h2>
<p class="wpsp-description" style="color: #d63638;"><?php esc_html_e( 'These actions are destructive and cannot be undone.', 'wp-security-pack' ); ?></p>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Reset All Settings', 'wp-security-pack' ); ?></th>
<td>
<button type="button" class="button" id="wpsp-reset-settings" style="border-color: #d63638; color: #d63638;"><?php esc_html_e( 'Reset to Defaults', 'wp-security-pack' ); ?></button>
</td>
</tr>
</table>
</div>
<script>
jQuery(document).ready(function($) {
$('#wpsp-whitelist-my-ip').on('click', function() {
var $btn = $(this);
$btn.prop('disabled', true);
$.post(ajaxurl, {
action: 'wpsp_whitelist_ip',
ip: '<?php echo esc_js( $current_ip ); ?>',
_ajax_nonce: '<?php echo esc_js( wp_create_nonce( 'wpsp_admin' ) ); ?>'
}, function(response) {
if (response.success) {
$('#wpsp-whitelist-status').text('<?php echo esc_js( __( 'IP added to whitelist!', 'wp-security-pack' ) ); ?>').css('color', 'green');
setTimeout(function() { location.reload(); }, 1000);
} else {
$('#wpsp-whitelist-status').text(response.data.message || '<?php echo esc_js( __( 'Error', 'wp-security-pack' ) ); ?>').css('color', 'red');
$btn.prop('disabled', false);
}
});
});
$('#wpsp-clear-lockouts').on('click', function() {
if (confirm('<?php echo esc_js( __( 'Are you sure you want to clear all lockouts?', 'wp-security-pack' ) ); ?>')) {
$.post(ajaxurl, {
action: 'wpsp_clear_lockouts',
_ajax_nonce: '<?php echo esc_js( wp_create_nonce( 'wpsp_admin' ) ); ?>'
}, function(response) {
if (response.success) {
$('#wpsp-lockout-status').text('<?php echo esc_js( __( 'All lockouts cleared.', 'wp-security-pack' ) ); ?>').css('color', 'green');
}
});
}
});
$('#wpsp-force-logout-all').on('click', function() {
if (confirm('<?php echo esc_js( __( 'Are you sure? ALL users (including you) will be logged out immediately.', 'wp-security-pack' ) ); ?>')) {
$.post(ajaxurl, {
action: 'wpsp_force_logout_all',
_ajax_nonce: '<?php echo esc_js( wp_create_nonce( 'wpsp_admin' ) ); ?>'
}, function(response) {
if (response.success) {
$('#wpsp-logout-status').text('<?php echo esc_js( __( 'All sessions terminated. Redirecting to login...', 'wp-security-pack' ) ); ?>').css('color', 'green');
setTimeout(function() {
window.location.href = '<?php echo esc_js( wp_login_url() ); ?>';
}, 1500);
}
});
}
});
$('#wpsp-export-settings').on('click', function() {
$.post(ajaxurl, {
action: 'wpsp_export_settings',
_ajax_nonce: '<?php echo esc_js( wp_create_nonce( 'wpsp_admin' ) ); ?>'
}, function(response) {
if (response.success) {
var blob = new Blob([JSON.stringify(response.data, null, 2)], {type: 'application/json'});
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = 'wp-security-pack-settings.json';
a.click();
URL.revokeObjectURL(url);
}
});
});
$('#wpsp-import-settings').on('click', function() {
var file = $('#wpsp-import-file')[0].files[0];
if (!file) {
alert('<?php echo esc_js( __( 'Please select a file to import.', 'wp-security-pack' ) ); ?>');
return;
}
var reader = new FileReader();
reader.onload = function(e) {
$.post(ajaxurl, {
action: 'wpsp_import_settings',
settings: e.target.result,
_ajax_nonce: '<?php echo esc_js( wp_create_nonce( 'wpsp_admin' ) ); ?>'
}, function(response) {
if (response.success) {
$('#wpsp-import-status').text('<?php echo esc_js( __( 'Settings imported successfully!', 'wp-security-pack' ) ); ?>').css('color', 'green');
setTimeout(function() { location.reload(); }, 1000);
} else {
$('#wpsp-import-status').text(response.data.message).css('color', 'red');
}
});
};
reader.readAsText(file);
});
$('#wpsp-reset-settings').on('click', function() {
if (confirm('<?php echo esc_js( __( 'Are you sure you want to reset all settings to defaults? This cannot be undone.', 'wp-security-pack' ) ); ?>')) {
$.post(ajaxurl, {
action: 'wpsp_reset_settings',
_ajax_nonce: '<?php echo esc_js( wp_create_nonce( 'wpsp_admin' ) ); ?>'
}, function(response) {
if (response.success) {
location.reload();
}
});
}
});
});
</script>
<?php
}
/**
* AJAX: Clear logs.
*/
public function ajax_clear_logs() {
check_ajax_referer( 'wpsp_admin' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'wp-security-pack' ) ) );
}
WPSP_Activity_Log::clear_all_logs();
wp_send_json_success();
}
/**
* AJAX: Whitelist IP.
*
* Adds an IP to the whitelist setting.
*/
public function ajax_whitelist_ip() {
check_ajax_referer( 'wpsp_admin' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'wp-security-pack' ) ) );
}
$ip = isset( $_POST['ip'] ) ? sanitize_text_field( wp_unslash( $_POST['ip'] ) ) : '';
if ( empty( $ip ) || ! filter_var( $ip, FILTER_VALIDATE_IP ) ) {
wp_send_json_error( array( 'message' => __( 'Invalid IP address.', 'wp-security-pack' ) ) );
}
// Get current whitelist.
$whitelist = WP_Security_Pack::get_setting( 'ip_whitelist', '' );
// Check if IP is already in whitelist.
$whitelist_array = array_filter( array_map( 'trim', explode( "\n", $whitelist ) ) );
if ( in_array( $ip, $whitelist_array, true ) ) {
wp_send_json_error( array( 'message' => __( 'IP is already whitelisted.', 'wp-security-pack' ) ) );
}
// Add IP to whitelist.
$whitelist_array[] = $ip;
$new_whitelist = implode( "\n", $whitelist_array );
WP_Security_Pack::update_setting( 'ip_whitelist', $new_whitelist );
// Also remove from lockouts if present.
$ip_control = wpsp()->get_component( 'ip_control' );
if ( $ip_control ) {
$ip_control->unblock_ip( $ip );
}
wp_send_json_success();
}
/**
* AJAX: Run file scan.
*/
public function ajax_run_file_scan() {
check_ajax_referer( 'wpsp_admin' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'wp-security-pack' ) ) );
}
$file_integrity = new WPSP_File_Integrity();
$results = $file_integrity->scan_core_files( true );
wp_send_json_success( $results );
}
/**
* AJAX: Run malware scan.
*/
public function ajax_run_malware_scan() {
check_ajax_referer( 'wpsp_admin' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'wp-security-pack' ) ) );
}
// Increase time limit.
set_time_limit( 300 );
$scanner = new WPSP_Malware_Scanner();
$results = $scanner->scan_files();
update_option( WPSP_Malware_Scanner::RESULTS_OPTION, $results );
update_option( WPSP_Malware_Scanner::LAST_SCAN_OPTION, time() );
wp_send_json_success( $results );
}
/**
* AJAX: Reset file integrity baseline.
*/
public function ajax_reset_file_baseline() {
check_ajax_referer( 'wpsp_admin' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'wp-security-pack' ) ) );
}
$file_integrity = new WPSP_File_Integrity();
$file_integrity->reset_baseline();
// Run a fresh scan to establish new baseline.
$file_integrity->scan_core_files( true );
wp_send_json_success();
}
/**
* AJAX: Clear malware scan results.
*/
public function ajax_clear_malware_results() {
check_ajax_referer( 'wpsp_admin' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'wp-security-pack' ) ) );
}
$scanner = new WPSP_Malware_Scanner();
$scanner->clear_results();
wp_send_json_success();
}
/**
* AJAX: Download GeoIP database.
*/
public function ajax_download_geo_db() {
check_ajax_referer( 'wpsp_admin' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'wp-security-pack' ) ) );
}
$geo_blocking = wpsp()->get_component( 'geo_blocking' );
if ( ! $geo_blocking ) {
wp_send_json_error( array( 'message' => __( 'Geo blocking not available.', 'wp-security-pack' ) ) );
}
$result = $geo_blocking->download_database();
if ( is_wp_error( $result ) ) {
wp_send_json_error( array( 'message' => $result->get_error_message() ) );
}
wp_send_json_success();
}
/**
* AJAX: Send test email.
*/
public function ajax_test_email() {
check_ajax_referer( 'wpsp_admin' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'wp-security-pack' ) ) );
}
$email = isset( $_POST['email'] ) ? sanitize_email( wp_unslash( $_POST['email'] ) ) : '';
if ( empty( $email ) ) {
$email = get_option( 'admin_email' );
}
if ( ! is_email( $email ) ) {
wp_send_json_error( array( 'message' => __( 'Invalid email address.', 'wp-security-pack' ) ) );
}
$site_name = get_bloginfo( 'name' );
$subject = sprintf(
/* translators: %s: Site name */
__( '[%s] WP Security Pack - Test Email', 'wp-security-pack' ),
$site_name
);
$message = sprintf(
/* translators: 1: Site name, 2: Site URL */
__( "This is a test email from WP Security Pack.\n\nIf you received this email, your email alerts are configured correctly.\n\nSite: %1\$s\nURL: %2\$s", 'wp-security-pack' ),
$site_name,
home_url()
);
$sent = wp_mail( $email, $subject, $message );
if ( $sent ) {
wp_send_json_success();
} else {
wp_send_json_error( array( 'message' => __( 'Failed to send email. Check your server mail configuration.', 'wp-security-pack' ) ) );
}
}
/**
* AJAX: Force logout all users.
*/
public function ajax_force_logout_all() {
check_ajax_referer( 'wpsp_admin' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'wp-security-pack' ) ) );
}
// Get all users and destroy their sessions.
$users = get_users( array( 'fields' => 'ID' ) );
foreach ( $users as $user_id ) {
$sessions = WP_Session_Tokens::get_instance( $user_id );
$sessions->destroy_all();
}
// Log the action.
WPSP_Activity_Log::log( 'force_logout', WPSP_Helper::get_client_ip(), wp_get_current_user()->user_login, __( 'All user sessions terminated', 'wp-security-pack' ) );
wp_send_json_success();
}
/**
* AJAX: Export settings.
*/
public function ajax_export_settings() {
check_ajax_referer( 'wpsp_admin' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'wp-security-pack' ) ) );
}
$settings = get_option( 'wpsp_settings', array() );
wp_send_json_success( $settings );
}
/**
* AJAX: Import settings.
*/
public function ajax_import_settings() {
check_ajax_referer( 'wpsp_admin' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'wp-security-pack' ) ) );
}
$settings_json = isset( $_POST['settings'] ) ? wp_unslash( $_POST['settings'] ) : '';
if ( empty( $settings_json ) ) {
wp_send_json_error( array( 'message' => __( 'No settings data provided.', 'wp-security-pack' ) ) );
}
$settings = json_decode( $settings_json, true );
if ( null === $settings ) {
wp_send_json_error( array( 'message' => __( 'Invalid JSON format.', 'wp-security-pack' ) ) );
}
// Sanitize the imported settings.
$sanitized = $this->sanitize_settings( $settings );
update_option( 'wpsp_settings', $sanitized );
wp_send_json_success();
}
/**
* AJAX: Export logs as CSV.
*/
public function ajax_export_logs() {
check_ajax_referer( 'wpsp_admin' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'wp-security-pack' ) ) );
}
$logs = WPSP_Activity_Log::get_logs( array( 'limit' => 10000 ) );
$csv_lines = array();
$csv_lines[] = 'Time,Event,IP Address,Country,Username,Details,User Agent';
foreach ( $logs as $log ) {
$csv_lines[] = sprintf(
'"%s","%s","%s","%s","%s","%s","%s"',
str_replace( '"', '""', $log->created_at ),
str_replace( '"', '""', WPSP_Activity_Log::get_event_label( $log->event_type ) ),
str_replace( '"', '""', $log->ip_address ),
str_replace( '"', '""', $log->country_code ? $log->country_code : '' ),
str_replace( '"', '""', $log->username ? $log->username : '' ),
str_replace( '"', '""', $log->details ? $log->details : '' ),
str_replace( '"', '""', $log->user_agent ? $log->user_agent : '' )
);
}
wp_send_json_success( array( 'csv' => implode( "\n", $csv_lines ) ) );
}
/**
* AJAX: Unblock a single IP.
*/
public function ajax_unblock_ip() {
check_ajax_referer( 'wpsp_admin' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'wp-security-pack' ) ) );
}
$ip = isset( $_POST['ip'] ) ? sanitize_text_field( wp_unslash( $_POST['ip'] ) ) : '';
if ( empty( $ip ) || ! filter_var( $ip, FILTER_VALIDATE_IP ) ) {
wp_send_json_error( array( 'message' => __( 'Invalid IP address.', 'wp-security-pack' ) ) );
}
$ip_control = wpsp()->get_component( 'ip_control' );
if ( $ip_control ) {
$ip_control->unblock_ip( $ip );
}
wp_send_json_success();
}
/**
* AJAX: Clear all lockouts.
*/
public function ajax_clear_lockouts() {
check_ajax_referer( 'wpsp_admin' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'wp-security-pack' ) ) );
}
global $wpdb;
$table = WPSP_DB::get_lockout_table();
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$wpdb->query( "TRUNCATE TABLE {$table}" );
wp_send_json_success();
}
/**
* AJAX: Reset all settings to defaults.
*/
public function ajax_reset_settings() {
check_ajax_referer( 'wpsp_admin' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'wp-security-pack' ) ) );
}
$defaults = WP_Security_Pack::get_default_settings();
update_option( 'wpsp_settings', $defaults );
wp_send_json_success();
}
/**
* AJAX: Quarantine a suspicious file.
*/
public function ajax_quarantine_file() {
check_ajax_referer( 'wpsp_admin' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'wp-security-pack' ) ) );
}
$file_path = isset( $_POST['file_path'] ) ? sanitize_text_field( wp_unslash( $_POST['file_path'] ) ) : '';
if ( empty( $file_path ) ) {
wp_send_json_error( array( 'message' => __( 'No file specified.', 'wp-security-pack' ) ) );
}
$scanner = new WPSP_Malware_Scanner();
$result = $scanner->quarantine_file( $file_path );
if ( is_wp_error( $result ) ) {
wp_send_json_error( array( 'message' => $result->get_error_message() ) );
}
wp_send_json_success( $result );
}
/**
* AJAX: Restore a file from quarantine.
*/
public function ajax_restore_file() {
check_ajax_referer( 'wpsp_admin' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'wp-security-pack' ) ) );
}
$quarantine_name = isset( $_POST['quarantine_name'] ) ? sanitize_file_name( wp_unslash( $_POST['quarantine_name'] ) ) : '';
if ( empty( $quarantine_name ) ) {
wp_send_json_error( array( 'message' => __( 'No file specified.', 'wp-security-pack' ) ) );
}
$scanner = new WPSP_Malware_Scanner();
$result = $scanner->restore_file( $quarantine_name );
if ( is_wp_error( $result ) ) {
wp_send_json_error( array( 'message' => $result->get_error_message() ) );
}
wp_send_json_success( $result );
}
/**
* AJAX: Delete a quarantined file permanently.
*/
public function ajax_delete_quarantined() {
check_ajax_referer( 'wpsp_admin' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'wp-security-pack' ) ) );
}
$quarantine_name = isset( $_POST['quarantine_name'] ) ? sanitize_file_name( wp_unslash( $_POST['quarantine_name'] ) ) : '';
if ( empty( $quarantine_name ) ) {
wp_send_json_error( array( 'message' => __( 'No file specified.', 'wp-security-pack' ) ) );
}
$scanner = new WPSP_Malware_Scanner();
$result = $scanner->delete_quarantined_file( $quarantine_name );
if ( is_wp_error( $result ) ) {
wp_send_json_error( array( 'message' => $result->get_error_message() ) );
}
wp_send_json_success( $result );
}
/**
* AJAX: Delete WordPress info files (readme.html, license.txt).
*/
public function ajax_delete_wp_file() {
check_ajax_referer( 'wpsp_admin' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'wp-security-pack' ) ) );
}
$file = isset( $_POST['file'] ) ? sanitize_file_name( wp_unslash( $_POST['file'] ) ) : '';
// Only allow specific safe files to be deleted.
$allowed_files = array( 'readme.html', 'license.txt' );
if ( ! in_array( $file, $allowed_files, true ) ) {
wp_send_json_error( array( 'message' => __( 'Invalid file.', 'wp-security-pack' ) ) );
}
$file_path = ABSPATH . $file;
if ( ! file_exists( $file_path ) ) {
wp_send_json_error( array( 'message' => __( 'File not found.', 'wp-security-pack' ) ) );
}
// phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
if ( ! unlink( $file_path ) ) {
wp_send_json_error( array( 'message' => __( 'Failed to delete file. Check file permissions.', 'wp-security-pack' ) ) );
}
wp_send_json_success();
}
}