settings = $settings; $this->database = $database; $this->activity_log = $activity_log; $this->setup_tabs(); $this->init_hooks(); } /** * Setup available tabs */ private function setup_tabs() { $this->tabs = array( 'dashboard' => __( 'Dashboard', 'vigilante' ), 'firewall' => __( 'Firewall', 'vigilante' ), 'headers' => __( 'Security Headers', 'vigilante' ), 'login' => __( 'Login Security', 'vigilante' ), 'rest-api' => __( 'REST API', 'vigilante' ), 'users' => __( 'User Security', 'vigilante' ), 'wp-hardening' => __( 'WP Hardening', 'vigilante' ), 'file-integrity' => __( 'File Integrity', 'vigilante' ), 'activity-log' => __( 'Security Audit', 'vigilante' ), 'tools' => __( 'Settings & Tools', 'vigilante' ), ); } /** * Initialize hooks */ private function init_hooks() { add_action( 'admin_menu', array( $this, 'add_menu' ) ); add_action( 'admin_init', array( $this, 'redirect_submenu_shortcuts' ) ); add_action( 'admin_init', array( $this, 'register_settings' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); add_action( 'admin_notices', array( $this, 'show_admin_notices' ) ); // Highlight correct submenu based on active tab add_filter( 'submenu_file', array( $this, 'highlight_submenu_tab' ) ); // Set browser tab title to show plugin name and active tab add_filter( 'admin_title', array( $this, 'set_admin_page_title' ), 10, 2 ); // AJAX handlers add_action( 'wp_ajax_vigilante_save_settings', array( $this, 'ajax_save_settings' ) ); add_action( 'wp_ajax_vigilante_apply_preset', array( $this, 'ajax_apply_preset' ) ); add_action( 'wp_ajax_vigilante_reset_section', array( $this, 'ajax_reset_section' ) ); add_action( 'wp_ajax_vigilante_clear_lockouts', array( $this, 'ajax_clear_lockouts' ) ); add_action( 'wp_ajax_vigilante_clear_logs', array( $this, 'ajax_clear_logs' ) ); add_action( 'wp_ajax_vigilante_run_scan', array( $this, 'ajax_run_scan' ) ); add_action( 'wp_ajax_vigilante_clear_scan', array( $this, 'ajax_clear_scan' ) ); add_action( 'wp_ajax_vigilante_ignore_file', array( $this, 'ajax_ignore_file' ) ); add_action( 'wp_ajax_vigilante_unignore_file', array( $this, 'ajax_unignore_file' ) ); add_action( 'wp_ajax_vigilante_bulk_ignore_files', array( $this, 'ajax_bulk_ignore_files' ) ); add_action( 'wp_ajax_vigilante_bulk_unignore_files', array( $this, 'ajax_bulk_unignore_files' ) ); add_action( 'wp_ajax_vigilante_clear_ignored', array( $this, 'ajax_clear_ignored' ) ); add_action( 'wp_ajax_vigilante_ignore_closed_plugin', array( $this, 'ajax_ignore_closed_plugin' ) ); add_action( 'wp_ajax_vigilante_unignore_closed_plugin', array( $this, 'ajax_unignore_closed_plugin' ) ); add_action( 'wp_ajax_vigilante_clear_ignored_closed_plugins', array( $this, 'ajax_clear_ignored_closed_plugins' ) ); add_action( 'wp_ajax_vigilante_approve_critical_file', array( $this, 'ajax_approve_critical_file' ) ); add_action( 'wp_ajax_vigilante_export_settings', array( $this, 'ajax_export_settings' ) ); add_action( 'wp_ajax_vigilante_import_settings', array( $this, 'ajax_import_settings' ) ); add_action( 'wp_ajax_vigilante_get_logs', array( $this, 'ajax_get_logs' ) ); add_action( 'wp_ajax_vigilante_test_headers', array( $this, 'ajax_test_headers' ) ); add_action( 'wp_ajax_vigilante_download_files_backup', array( $this, 'ajax_download_files_backup' ) ); // 2FA AJAX handlers add_action( 'wp_ajax_vigilante_search_users_2fa', array( $this, 'ajax_search_users_2fa' ) ); add_action( 'wp_ajax_vigilante_send_2fa_notification', array( $this, 'ajax_send_2fa_notification' ) ); add_action( 'wp_ajax_vigilante_search_totp_users', array( $this, 'ajax_search_totp_users' ) ); add_action( 'wp_ajax_vigilante_reset_totp_users', array( $this, 'ajax_reset_totp_users' ) ); add_action( 'wp_ajax_vigilante_totp_get_setup', array( $this, 'ajax_totp_get_setup' ) ); add_action( 'wp_ajax_vigilante_notify_login_url', array( $this, 'ajax_notify_login_url' ) ); // Password Reset AJAX handlers add_action( 'wp_ajax_vigilante_search_users_password_reset', array( $this, 'ajax_search_users_password_reset' ) ); add_action( 'wp_ajax_vigilante_force_password_reset', array( $this, 'ajax_force_password_reset' ) ); add_action( 'wp_ajax_vigilante_force_password_reset_all', array( $this, 'ajax_force_password_reset_all' ) ); add_action( 'wp_ajax_vigilante_force_password_reset_by_role', array( $this, 'ajax_force_password_reset_by_role' ) ); // User approval AJAX handlers add_action( 'wp_ajax_vigilante_approve_user', array( $this, 'ajax_approve_user' ) ); add_action( 'wp_ajax_vigilante_reject_user', array( $this, 'ajax_reject_user' ) ); // Session management AJAX handlers add_action( 'wp_ajax_vigilante_get_user_sessions', array( $this, 'ajax_get_user_sessions' ) ); add_action( 'wp_ajax_vigilante_revoke_session', array( $this, 'ajax_revoke_session' ) ); add_action( 'wp_ajax_vigilante_revoke_all_sessions', array( $this, 'ajax_revoke_all_sessions' ) ); // Under Attack mode AJAX handlers add_action( 'wp_ajax_vigilante_activate_under_attack', array( $this, 'ajax_activate_under_attack' ) ); add_action( 'wp_ajax_vigilante_deactivate_under_attack', array( $this, 'ajax_deactivate_under_attack' ) ); add_action( 'wp_ajax_vigilante_under_attack_status', array( $this, 'ajax_under_attack_status' ) ); // Database backup AJAX handlers add_action( 'wp_ajax_vigilante_get_db_tables', array( $this, 'ajax_get_db_tables' ) ); add_action( 'wp_ajax_vigilante_download_db_backup', array( $this, 'ajax_download_db_backup' ) ); // Database prefix AJAX handlers add_action( 'wp_ajax_vigilante_generate_prefix', array( $this, 'ajax_generate_prefix' ) ); add_action( 'wp_ajax_vigilante_change_prefix', array( $this, 'ajax_change_prefix' ) ); // Firewall list management from activity log popup add_action( 'wp_ajax_vigilante_add_to_firewall_list', array( $this, 'ajax_add_to_firewall_list' ) ); add_action( 'wp_ajax_vigilante_unblock_firewall_ip', array( $this, 'ajax_unblock_firewall_ip' ) ); // Security Analyzer AJAX handlers (v2.1.0) add_action( 'wp_ajax_vigilante_analyzer_run', array( $this, 'ajax_analyzer_run' ) ); add_action( 'wp_ajax_vigilante_analyzer_history', array( $this, 'ajax_analyzer_history' ) ); add_action( 'wp_ajax_vigilante_analyzer_dismiss_notice', array( $this, 'ajax_analyzer_dismiss_notice' ) ); add_action( 'wp_ajax_vigilante_analyzer_save_settings', array( $this, 'ajax_analyzer_save_settings' ) ); // Security Headers settings recovery (2.10.0) add_action( 'wp_ajax_vigilante_headers_recovery_restore', array( $this, 'ajax_headers_recovery_restore' ) ); add_action( 'wp_ajax_vigilante_headers_recovery_undo', array( $this, 'ajax_headers_recovery_undo' ) ); add_action( 'wp_ajax_vigilante_headers_recovery_dismiss', array( $this, 'ajax_headers_recovery_dismiss' ) ); // Shared "Send test email" handler — Notification settings, File Integrity, Audit Alerts (v2.8.0) add_action( 'wp_ajax_vigilante_send_test_email', array( $this, 'ajax_send_test_email' ) ); // Run migrations on admin load add_action( 'admin_init', array( $this, 'run_migrations' ) ); } /** * Run database migrations based on stored version */ public function run_migrations() { /* * admin-ajax.php fires admin_init before it decides who is asking * (wp-admin/admin-ajax.php:45), so until 2.11.10 an anonymous POST to * admin-ajax.php with any action ran every pending migration. That is * not a read: the migrations rewrite wp-config.php through * apply_security_constants(), rewrite the root .htaccess, move user meta * of the whole network and can rebuild the file integrity baseline, * taking whatever is on disk as approved. Reproduced on 12 sep 2026 with * curl and no cookies, and found by the file-by-file review of 2.11.10. * * Migrations are maintenance for whoever administers the site, so they * wait for an administrator to load a screen. Nothing is lost by * waiting: every migration is idempotent and version gated. */ if ( ! is_user_logged_in() || ! current_user_can( 'manage_options' ) ) { return; } $db_version = get_option( 'vigilante_db_version', '0' ); // 1.2.3: Fix IP lists corrupted by sanitize_text_field stripping newlines if ( version_compare( $db_version, '1.2.3', '<' ) ) { $this->migrate_fix_ip_lists(); update_option( 'vigilante_db_version', '1.2.3' ); } // 1.3.0: Add request_method column to activity log table if ( version_compare( $db_version, '1.3.0', '<' ) ) { $this->database->run_migrations(); } // 1.9.0: Re-apply wp-config constants (performance constants removed from managed list) if ( version_compare( $db_version, '1.9.0', '<' ) ) { if ( $this->settings->is_module_enabled( 'wp_hardening' ) ) { require_once VIGILANTE_INCLUDES_DIR . 'class-wpconfig-security.php'; $wpconfig = new Vigilante_Wpconfig_Security( $this->settings ); $wpconfig->apply_security_constants(); } update_option( 'vigilante_db_version', '1.9.0' ); } // 1.10.0: Clean up orphaned email fields (centralized notification recipients) if ( version_compare( $db_version, '1.10.0', '<' ) ) { $this->migrate_cleanup_email_fields(); update_option( 'vigilante_db_version', '1.10.0' ); } // 1.11.0: Remove stale 'enabled' key from activity_log settings // + Convert additional_recipients from string to array if ( version_compare( $db_version, '1.11.0', '<' ) ) { $options = get_option( Vigilante_Settings::OPTION_NAME, array() ); $changed = false; if ( isset( $options['activity_log']['enabled'] ) ) { unset( $options['activity_log']['enabled'] ); $changed = true; } // Convert corrupted string to array for additional_recipients if ( isset( $options['email']['additional_recipients'] ) && is_string( $options['email']['additional_recipients'] ) ) { $raw = trim( $options['email']['additional_recipients'] ); if ( ! empty( $raw ) ) { $emails = array_filter( array_map( 'trim', preg_split( '/[\r\n,; ]+/', $raw ) ) ); $options['email']['additional_recipients'] = array_values( array_filter( $emails, 'is_email' ) ); } else { $options['email']['additional_recipients'] = array(); } $changed = true; } if ( $changed ) { update_option( Vigilante_Settings::OPTION_NAME, $options ); } update_option( 'vigilante_db_version', '1.11.0' ); } // 1.12.1: Regenerate htaccess (WooCommerce IPN exclusion in bot blocking rule) if ( version_compare( $db_version, '1.12.1', '<' ) ) { if ( ! empty( $this->settings->get_section( 'firewall' )['block_bad_bots'] ) ) { require_once VIGILANTE_INCLUDES_DIR . 'class-htaccess-protection.php'; $htaccess = new Vigilante_Htaccess_Protection( $this->settings ); $htaccess->apply_rules(); } update_option( 'vigilante_db_version', '1.12.1' ); } // 1.14.0: Generate critical config files baseline (wp-config.php, .htaccess) if ( version_compare( $db_version, '1.14.0', '<' ) ) { if ( ! class_exists( 'Vigilante_File_Integrity' ) ) { require_once VIGILANTE_INCLUDES_DIR . 'class-file-integrity.php'; } $fi = new Vigilante_File_Integrity( $this->settings, $this->database, $this->activity_log ); /* * Only when there is nothing on record. This migration exists to * create the baseline that did not exist, never to discard the one * the owner approved: rebuilding it from the files takes whatever * is on disk right now as approved, so a wp-config.php modified and * awaiting review would be blessed in silence. * * And this is not theory. vigilante_db_version is written on two * different scales into the same option: this file counts in plugin * versions (2.11.0) and Vigilante_Database counts in schema * versions, currently 1.4.0 (class-database.php:322 and :380). For * version_compare, 1.4.0 is LOWER than 1.14.0, so any site whose * option was last written by the schema runs this migration again. * Measured on the Multisite install on 10 sep 2026: one of the three * sites was sitting on 1.4.0. */ if ( ! $fi->get_critical_files_baseline() ) { $fi->regenerate_all_baselines(); } update_option( 'vigilante_db_version', '1.14.0' ); } // 2.0.0: Move hide_server_signature and remove_fingerprinting_headers // from firewall section to security_headers section if ( version_compare( $db_version, '2.0.0', '<' ) ) { $options = get_option( Vigilante_Settings::OPTION_NAME, array() ); $changed = false; foreach ( array( 'hide_server_signature', 'remove_fingerprinting_headers' ) as $key ) { if ( isset( $options['firewall'][ $key ] ) ) { if ( ! isset( $options['security_headers'][ $key ] ) ) { $options['security_headers'][ $key ] = $options['firewall'][ $key ]; } unset( $options['firewall'][ $key ] ); $changed = true; } } if ( $changed ) { update_option( Vigilante_Settings::OPTION_NAME, $options ); } update_option( 'vigilante_db_version', '2.0.0' ); } // 2.6.1: Two things happen here. // // 1. Re-apply wp-config constants so the block is rewritten with // "if ( ! defined() )" guards around every define(). Without guards, // non-standard setups that pre-define WordPress constants outside // wp-config.php (custom bootstraps that load constants from .env or // similar) hit a fatal "Constant already defined" when wp-config.php // is parsed and reaches our block. // // 2. Drop the cached Security Check report. The cached "max" per // category was frozen at scan time; with the internal category // bumping from 22 to 28 points (closed_plugins added in 2.6.0), // the cached report would keep displaying 22/22 until the next // full scan. Clearing it forces a fresh scan with the new caps. if ( version_compare( $db_version, '2.6.1', '<' ) ) { if ( $this->settings->is_module_enabled( 'wp_hardening' ) ) { require_once VIGILANTE_INCLUDES_DIR . 'class-wpconfig-security.php'; $wpconfig = new Vigilante_Wpconfig_Security( $this->settings ); $wpconfig->apply_security_constants(); } delete_option( 'vigilante_analyzer_last_scan' ); // Schedule an immediate background scan so the dashboard widget // doesn't display "Last scan: never" right after the upgrade. // Reuses the same hook the post-Under-Attack flow uses. if ( ! wp_next_scheduled( 'vigilante_under_attack_post_scan' ) ) { wp_schedule_single_event( time() + 5, 'vigilante_under_attack_post_scan' ); } update_option( 'vigilante_db_version', '2.6.1' ); } // 2.9.3: Regenerate the .htaccess protection block. The bad-bots // User-Agent list dropped substring-prone tokens that 403'd // legitimate clients (e.g. "rma" matched inside "Performance" and // blocked WP Rocket's page fetch), and the blocking rules now honour // the firewall IP / User-Agent whitelists as negated exceptions. // Existing sites only rewrite the block when Server Protection is // saved, so the upgrade has to refresh it once itself (same pattern // as the 1.12.1 WooCommerce IPN migration). if ( version_compare( $db_version, '2.9.3', '<' ) ) { require_once VIGILANTE_INCLUDES_DIR . 'class-htaccess-protection.php'; $htaccess = new Vigilante_Htaccess_Protection( $this->settings ); if ( $htaccess->are_rules_active() ) { $htaccess->apply_rules(); } update_option( 'vigilante_db_version', '2.9.3' ); } /* * 2.9.8: the mixed content handling changes shape. "Upgrade Insecure * Requests" becomes a setting of its own, and Fix Mixed Content ships * off, where before it shipped on and carried the directive with it. * Both have to be written down for sites that are updating, so their * pages keep loading exactly what they loaded yesterday. * * Read the RAW stored options, not get_section(): that one merges the * defaults, so a site that never stored the key would be read with the * new default and silently lose the behaviour it had. Absent means the * site was running on the old default, which was on. */ if ( version_compare( $db_version, '2.9.8', '<' ) ) { $raw = get_option( Vigilante_Settings::OPTION_NAME, array() ); $stored = ( is_array( $raw ) && isset( $raw['security_headers'] ) && is_array( $raw['security_headers'] ) ) ? $raw['security_headers'] : array(); $had_fix = array_key_exists( 'fix_mixed_content', $stored ) ? ! empty( $stored['fix_mixed_content'] ) : true; /* * Merge, never replace. update_section() overwrites the whole * section, so passing just these two keys wiped every other header * setting the site had stored (HSTS, CSP, cross-origin policies, * the HTTPS switches, Server Identity) and left the screen showing * factory defaults while the .htaccess kept serving the old values. */ $this->settings->update_section( 'security_headers', array_merge( $stored, array( 'fix_mixed_content' => $had_fix, 'upgrade_insecure_requests' => $had_fix, ) ) ); update_option( 'vigilante_db_version', '2.9.8' ); } /* * 2.9.9: drop the settings that no code has read for versions. * * They were carried in the defaults and therefore written into every * saved configuration, they show up in an exported configuration, and * anyone reading them assumes a feature exists behind them. Removing * them from the defaults is not enough: the stored copies survive, so * they are swept here too. Nothing reads them, so nothing changes. */ if ( version_compare( $db_version, '2.9.9', '<' ) ) { $raw = get_option( Vigilante_Settings::OPTION_NAME, array() ); $dead = array( 'firewall' => array( 'country_blocking', 'protected_file_extensions' ), 'file_integrity' => array( 'suspicious_patterns' ), 'backup' => array( 'auto_backup', 'backup_before_update' ), 'advanced' => array( 'block_author_archives', 'disable_embeds', 'uninstall_cleanup', 'debug_mode' ), ); $changed = false; foreach ( $dead as $section => $keys ) { if ( ! isset( $raw[ $section ] ) || ! is_array( $raw[ $section ] ) ) { continue; } foreach ( $keys as $key ) { if ( array_key_exists( $key, $raw[ $section ] ) ) { unset( $raw[ $section ][ $key ] ); $changed = true; } } } if ( $changed ) { update_option( Vigilante_Settings::OPTION_NAME, $raw ); } update_option( 'vigilante_db_version', '2.9.9' ); } /* * 2.11.0: security release (audit of 28 Aug 2026). Runs here and not * from Vigilante_Database::needs_update(): this option is shared with * that class, and on any updated site it already holds a plugin version * (2.9.9 or later), so a bump of DB_VERSION would never fire. * create_tables() widens the email code column through dbDelta (varchar * 6 to 64, the code is stored hashed since 2.11.0) and purge_for_2_11_0() * does what dbDelta cannot: it empties the trusted devices, which were * identified by User-Agent until now (S1), and the pending email codes, * stored in clear until now (S11). Every remembered device asks for the * second factor once more after this update, and the changelog says so. */ if ( version_compare( $db_version, '2.11.0', '<' ) ) { $this->database->create_tables(); $this->database->purge_for_2_11_0(); update_option( 'vigilante_db_version', '2.11.0' ); } /* * 2.11.9: clear the raw .htaccess copies that older versions left in * options, on the first admin load after the update. Uninstall already * removes them, but that only fires when the plugin is deleted, so a * site that keeps the plugin carried them until now. Three stores, each * a copy of a file that can hold secrets (a SetEnv token, an * Authorization header): the same exposure the wp.org review flagged as * 4.4, on the paths its fix did not reach. * * - vigilante_htaccess_history: up to five raw copies, by design, until * 2.11.8. The writer is gone, nothing reads it, so it is deleted. * - vigilante_htaccess_backup: the single rollback buffer, normally * cleared in the finally of each write; a copy only lingers if a write * crashed mid-operation. Nothing outside one write reads it, so a * leftover is deleted. * - vigilante_htaccess_pre_migration: still read by the header recovery, * but older versions stored the whole file where only our own block is * ever used. Truncated to that block, so the feature keeps working and * nothing outside our markers stays in the option. */ if ( version_compare( $db_version, '2.11.9', '<' ) ) { delete_option( 'vigilante_htaccess_history' ); delete_option( 'vigilante_htaccess_backup' ); $snapshot = get_option( 'vigilante_htaccess_pre_migration' ); if ( is_array( $snapshot ) && isset( $snapshot['content'] ) && '' !== (string) $snapshot['content'] ) { require_once VIGILANTE_INCLUDES_DIR . 'class-htaccess-recovery.php'; $block = Vigilante_Htaccess_Recovery::get_raw_block(); if ( '' === $block ) { delete_option( 'vigilante_htaccess_pre_migration' ); } elseif ( $block !== $snapshot['content'] ) { $snapshot['content'] = $block; update_option( 'vigilante_htaccess_pre_migration', $snapshot, false ); } } update_option( 'vigilante_db_version', '2.11.9' ); } /* * 2.11.10: the pending-approval flag becomes one per site on a network. * Until 2.11.9 it was a single global user meta, so the queue was shared * across the whole network. Moving the key is not enough: the accounts * already waiting carry the old key, and reading only the new one would * let them log in. So they are moved here, each to the site it belongs * to, and the old key is removed only once the new one is written. */ if ( version_compare( $db_version, '2.11.10', '<' ) ) { $this->migrate_pending_approval_per_site(); update_option( 'vigilante_db_version', '2.11.10' ); } } /** * Move the pending-approval flag of a network to a key per site * * Runs once for the whole network, not once per site: the data it moves is * global, so the guard is a network option and any site may be the one that * does it. On a single site the key does not change and there is nothing to * do. * * Each waiting account goes to its primary site, or to the only site it * belongs to; one that belongs to none goes to the main site rather than * nowhere, because losing the flag would silently approve it. * * @since 2.11.10 */ private function migrate_pending_approval_per_site() { global $wpdb; if ( ! is_multisite() ) { return; } if ( get_site_option( 'vigilante_pending_per_site_done' ) ) { return; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- One-off migration of the plugin's own user meta; the meta API has no "list every user with this key". $user_ids = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT user_id FROM {$wpdb->usermeta} WHERE meta_key = %s", 'vigilante_pending_approval' ) ); foreach ( (array) $user_ids as $user_id ) { $user_id = (int) $user_id; if ( ! $user_id ) { continue; } $pending = get_user_meta( $user_id, 'vigilante_pending_approval', true ); $since = get_user_meta( $user_id, 'vigilante_pending_since', true ); /* * Every site the account belongs to, not its primary one. The global * flag does not say where the registration happened, and the first * version of this guessed the primary blog: an account that * registered on B while its primary was A came out pending on A and * free to log in on B, which is the very site it had never been * approved on. Found by the cross review of 2.11.10. * * Marking every site it belongs to fails closed instead: the account * stays blocked wherever it can log in, and shows up in the queue of * each of those sites so somebody can actually act on it. An account * that belongs to no site goes to the main one rather than nowhere, * because losing the flag would silently approve it. */ /* * With $all true, because the default leaves out archived, spam and * deleted sites (wp-includes/user.php:1113-1117): a site archived on * the day this runs would lose the flag, and the account would walk * in unapproved the moment it was brought back. Found by the second * cross review of 2.11.10. */ $blog_ids = array(); foreach ( get_blogs_of_user( $user_id, true ) as $blog ) { if ( ! empty( $blog->userblog_id ) ) { $blog_ids[] = (int) $blog->userblog_id; } } if ( empty( $blog_ids ) ) { $blog_ids[] = (int) get_main_site_id(); } foreach ( array_unique( $blog_ids ) as $blog_id ) { $prefix = $wpdb->get_blog_prefix( $blog_id ); update_user_meta( $user_id, $prefix . 'vigilante_pending_approval', $pending ); if ( '' !== $since && false !== $since ) { update_user_meta( $user_id, $prefix . 'vigilante_pending_since', $since ); } } delete_user_meta( $user_id, 'vigilante_pending_approval' ); delete_user_meta( $user_id, 'vigilante_pending_since' ); } update_site_option( 'vigilante_pending_per_site_done', 1 ); } /** * Migration: Remove orphaned email fields from saved options * * v1.10.0 centralized notification recipients into email section. * Old per-module notify_email fields and dead email section fields * are removed to avoid confusion. */ private function migrate_cleanup_email_fields() { $options = get_option( Vigilante_Settings::OPTION_NAME, array() ); $modified = false; // Remove orphaned fields from email section $dead_email_keys = array( 'enabled', 'from_name', 'from_email', 'admin_email', 'send_activation_email', 'custom_email' ); if ( isset( $options['email'] ) && is_array( $options['email'] ) ) { foreach ( $dead_email_keys as $key ) { if ( array_key_exists( $key, $options['email'] ) ) { unset( $options['email'][ $key ] ); $modified = true; } } // Ensure new fields exist with defaults if ( ! array_key_exists( 'send_to_admin_email', $options['email'] ) ) { $options['email']['send_to_admin_email'] = true; $modified = true; } if ( ! array_key_exists( 'additional_recipients', $options['email'] ) ) { $options['email']['additional_recipients'] = ''; $modified = true; } } // Remove notify_email from login_security if ( isset( $options['login_security']['notify_email'] ) ) { unset( $options['login_security']['notify_email'] ); $modified = true; } // Remove notify_email from file_integrity if ( isset( $options['file_integrity']['notify_email'] ) ) { unset( $options['file_integrity']['notify_email'] ); $modified = true; } if ( $modified ) { update_option( Vigilante_Settings::OPTION_NAME, $options ); // Clear settings cache so the plugin uses clean data immediately $this->settings->clear_cache(); } } /** * Migration: Fix IP whitelist/blacklist entries merged into single line * * Prior to 1.2.3, sanitize_text_field() stripped newlines from textarea data, * causing multiple IPs to be stored as a single space-separated string. */ private function migrate_fix_ip_lists() { $options = get_option( Vigilante_Settings::OPTION_NAME, array() ); $fixed = false; foreach ( array( 'ip_whitelist', 'ip_blacklist' ) as $key ) { if ( ! empty( $options['firewall'][ $key ] ) && is_array( $options['firewall'][ $key ] ) ) { $new_list = array(); foreach ( $options['firewall'][ $key ] as $entry ) { // Split entries that were joined by spaces $parts = preg_split( '/\s+/', trim( $entry ) ); foreach ( $parts as $part ) { $part = trim( $part ); if ( '' !== $part ) { $new_list[] = $part; } } } if ( count( $new_list ) !== count( $options['firewall'][ $key ] ) ) { $options['firewall'][ $key ] = array_unique( $new_list ); $fixed = true; } } } if ( $fixed ) { update_option( Vigilante_Settings::OPTION_NAME, $options ); // Clear settings cache so changes take effect immediately $this->settings->clear_cache(); } } /** * Add admin menu page in last position */ public function add_menu() { $menu_title = __( 'Vigilant', 'vigilante' ); // Count pending approvals (separate concern, always red if present) $pending_count = $this->get_pending_approvals_count(); // Get security issues with severity $security_status = $this->get_security_status_for_badge(); // Total count for badge $total_badge = $pending_count + $security_status['count']; if ( $total_badge > 0 ) { // Determine badge color: // - Red (awaiting-mod): pending approvals OR critical modules disabled // - Orange (update-plugins): only non-critical modules disabled if ( $pending_count > 0 || $security_status['has_critical'] ) { $badge_class = 'awaiting-mod'; } else { $badge_class = 'update-plugins vigilante-badge-warning'; } $menu_title .= sprintf( ' %d', esc_attr( $badge_class ), $total_badge, $total_badge ); } add_menu_page( __( 'Vigilant', 'vigilante' ), $menu_title, 'manage_options', 'vigilante', array( $this, 'render_settings_page' ), 'dashicons-shield', 999 ); // Rename auto-generated first submenu to "Dashboard" add_submenu_page( 'vigilante', __( 'Dashboard', 'vigilante' ), __( 'Dashboard', 'vigilante' ), 'manage_options', 'vigilante', array( $this, 'render_settings_page' ) ); // Security Audit shortcut add_submenu_page( 'vigilante', __( 'Security Audit', 'vigilante' ), __( 'Security Audit', 'vigilante' ), 'manage_options', 'vigilante-activity-log', array( $this, 'redirect_to_tab' ) ); // File Integrity shortcut add_submenu_page( 'vigilante', __( 'File Integrity', 'vigilante' ), __( 'File Integrity', 'vigilante' ), 'manage_options', 'vigilante-file-integrity', array( $this, 'redirect_to_tab' ) ); } /** * Redirect submenu shortcuts early, before headers are sent */ public function redirect_submenu_shortcuts() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Just reading page slug for redirect $page = isset( $_GET['page'] ) ? sanitize_key( $_GET['page'] ) : ''; $tab_map = array( 'vigilante-activity-log' => 'activity-log', 'vigilante-file-integrity' => 'file-integrity', ); if ( isset( $tab_map[ $page ] ) ) { wp_safe_redirect( admin_url( 'admin.php?page=vigilante&tab=' . $tab_map[ $page ] ) ); exit; } } /** * Fallback redirect for submenu shortcuts (JS-based) */ public function redirect_to_tab() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Just reading page slug for redirect $page = isset( $_GET['page'] ) ? sanitize_key( $_GET['page'] ) : ''; $tab_map = array( 'vigilante-activity-log' => 'activity-log', 'vigilante-file-integrity' => 'file-integrity', ); if ( isset( $tab_map[ $page ] ) ) { $url = admin_url( 'admin.php?page=vigilante&tab=' . $tab_map[ $page ] ); echo ''; } } /** * Highlight the correct submenu item based on active tab * * @param string $submenu_file Current submenu file. * @return string */ public function highlight_submenu_tab( $submenu_file ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading tab for menu highlight only $page = isset( $_GET['page'] ) ? sanitize_key( $_GET['page'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : ''; if ( 'vigilante' !== $page || empty( $tab ) ) { return $submenu_file; } $tab_to_submenu = array( 'activity-log' => 'vigilante-activity-log', 'file-integrity' => 'vigilante-file-integrity', ); if ( isset( $tab_to_submenu[ $tab ] ) ) { return $tab_to_submenu[ $tab ]; } return $submenu_file; } /** * Set browser tab title to show plugin name and active tab * * Changes "Dashboard ‹ Site Name — WordPress" to * "Vigilant > Dashboard ‹ Site Name — WordPress" * * @param string $admin_title Full admin title. * @param string $title Page title from add_menu_page/add_submenu_page. * @return string Modified title. */ public function set_admin_page_title( $admin_title, $title ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading page slug for title only $page = isset( $_GET['page'] ) ? sanitize_key( $_GET['page'] ) : ''; // Only modify on Vigilante pages if ( 0 !== strpos( $page, 'vigilante' ) ) { return $admin_title; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'dashboard'; if ( isset( $this->tabs[ $tab ] ) ) { $tab_label = $this->tabs[ $tab ]; } else { $tab_label = __( 'Dashboard', 'vigilante' ); } $plugin_title = __( 'Vigilant', 'vigilante' ) . ' › ' . $tab_label; // Replace the original page title portion return str_replace( $title, $plugin_title, $admin_title ); } /** * Get count of users pending approval * * @return int Count of pending users. */ private function get_pending_approvals_count() { // Prevent early execution before WordPress is ready if ( ! did_action( 'plugins_loaded' ) ) { return 0; } /* * Counted whether the feature is on or off. An account already waiting * stays blocked when it is switched off (see init_enforcement_hooks()), * so reporting zero there hid people who cannot log in and whom nobody * could see to approve. Found by the cross review of 2.11.10. */ // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key, WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Limited results in admin context. $args = array( 'meta_key' => Vigilante_User_Security::site_user_meta_key( 'vigilante_pending_approval' ), 'meta_value' => '1', 'fields' => 'ID', ); // phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_key, WordPress.DB.SlowDBQuery.slow_db_query_meta_value // Same query as Vigilante_User_Security::get_pending_users(), and for the // same reason: the meta key already scopes this to the site, and adding // core's membership filter on top hid the accounts that have no role yet. if ( is_multisite() ) { $args['blog_id'] = 0; } $pending_users = get_users( $args ); return count( $pending_users ); } /** * Calculate comprehensive security score (0-100) * * @param array $options Plugin options. * @return int Security score. */ private function calculate_security_score( $options ) { $score = 0; $max_score = 0; // Module scores (60 points total) $module_weights = array( 'firewall' => 10, 'security_headers' => 8, 'login_security' => 10, 'rest_api_security' => 6, 'user_security' => 8, 'wp_hardening' => 8, 'file_integrity' => 5, 'activity_log' => 5, ); foreach ( $module_weights as $module => $weight ) { $max_score += $weight; if ( ! empty( $options['modules'][ $module ] ) ) { $score += $weight; } } // Firewall details (10 points) if ( ! empty( $options['modules']['firewall'] ) ) { $firewall = $options['firewall'] ?? array(); $max_score += 10; $firewall_checks = array( 'block_sql_injection', 'block_xss_attacks', 'block_bad_query_strings', 'block_file_inclusion', 'block_directory_traversal', ); $firewall_enabled = 0; foreach ( $firewall_checks as $check ) { if ( ! empty( $firewall[ $check ] ) ) { $firewall_enabled++; } } $score += min( 10, $firewall_enabled * 2 ); } // Login security details (10 points) if ( ! empty( $options['modules']['login_security'] ) ) { $login = $options['login_security'] ?? array(); $max_score += 10; // Max attempts configured if ( isset( $login['max_attempts'] ) && $login['max_attempts'] <= 5 ) { $score += 3; } // XML-RPC disabled if ( ! empty( $login['disable_xmlrpc'] ) ) { $score += 3; } // 2FA enabled if ( ! empty( $login['two_factor']['enabled'] ) ) { $score += 4; } } // Security headers details (10 points) if ( ! empty( $options['modules']['security_headers'] ) ) { $headers = $options['security_headers'] ?? array(); $max_score += 10; if ( ! empty( $headers['x_frame_options'] ) ) { $score += 2; } if ( ! empty( $headers['x_content_type_options'] ) ) { $score += 2; } if ( ! empty( $headers['hsts']['enabled'] ) ) { $score += 3; } if ( ! empty( $headers['csp']['enabled'] ) ) { $score += 3; } } // User security details (10 points) if ( ! empty( $options['modules']['user_security'] ) ) { $user = $options['user_security'] ?? array(); $max_score += 10; if ( ! empty( $user['block_insecure_usernames'] ) ) { $score += 3; } if ( ! empty( $user['force_strong_passwords'] ) ) { $score += 3; } if ( ! empty( $user['password_expiration']['enabled'] ) ) { $score += 2; } if ( ! empty( $user['email_verification']['enabled'] ) ) { $score += 2; } } // Audit alerts details (6 points) - only when Security Audit is on, // because the alerting layer rides on top of the activity log. Leaving // both alert legs off keeps these points unearned. if ( ! empty( $options['modules']['activity_log'] ) ) { $alerts = isset( $options['audit_alerts'] ) ? (array) $options['audit_alerts'] : array(); $max_score += 6; if ( Vigilante_Audit_Alerts::immediate_is_active( $alerts ) ) { $score += 3; } if ( Vigilante_Audit_Alerts::threshold_is_active( $alerts ) ) { $score += 3; } } // Environment checks (8 points) - penalize insecure server configuration $max_score += 8; $env_score = 8; // WP_DEBUG active in production is a security risk (exposes paths, errors) if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { $env_score -= 5; } // Accounts with insecure usernames (targeted by brute force attacks) $insecure_admins = $this->get_insecure_admin_usernames(); if ( ! empty( $insecure_admins ) ) { $env_score -= 3; } $score += max( 0, $env_score ); return $max_score > 0 ? round( ( $score / $max_score ) * 100 ) : 0; } /** * Get security recommendations based on current settings * * @param array $options Plugin options. * @return array Array of recommendations. */ private function get_security_recommendations( $options ) { $recommendations = array(); // Critical: Firewall disabled if ( empty( $options['modules']['firewall'] ) ) { $recommendations[] = array( 'icon' => 'warning', 'priority' => 'critical', 'message' => __( 'Enable Firewall to protect against common attacks.', 'vigilante' ), ); } // Critical: Login security disabled if ( empty( $options['modules']['login_security'] ) ) { $recommendations[] = array( 'icon' => 'warning', 'priority' => 'critical', 'message' => __( 'Enable Login Security to prevent brute force attacks.', 'vigilante' ), ); } // High: Security headers disabled if ( empty( $options['modules']['security_headers'] ) ) { $recommendations[] = array( 'icon' => 'admin-generic', 'priority' => 'high', 'message' => __( 'Enable Security Headers to protect against clickjacking and XSS.', 'vigilante' ), ); } // High: User security disabled if ( empty( $options['modules']['user_security'] ) ) { $recommendations[] = array( 'icon' => 'admin-users', 'priority' => 'high', 'message' => __( 'Enable User Security to enforce password policies and username protection.', 'vigilante' ), ); } // High: 2FA not enabled (only if login security is active) $login = $options['login_security'] ?? array(); if ( ! empty( $options['modules']['login_security'] ) && empty( $login['two_factor']['enabled'] ) ) { $recommendations[] = array( 'icon' => 'shield', 'priority' => 'high', 'message' => __( 'Enable Two-Factor Authentication for enhanced login security.', 'vigilante' ), 'tab' => 'login', ); } // Medium: REST API security disabled if ( empty( $options['modules']['rest_api_security'] ) ) { $recommendations[] = array( 'icon' => 'rest-api', 'priority' => 'medium', 'message' => __( 'Enable REST API Security to control API access and prevent enumeration.', 'vigilante' ), ); } // Medium: WP Hardening disabled if ( empty( $options['modules']['wp_hardening'] ) ) { $recommendations[] = array( 'icon' => 'lock', 'priority' => 'medium', 'message' => __( 'Enable WP Hardening to remove version info and protect core files.', 'vigilante' ), ); } // Medium: File integrity disabled if ( empty( $options['modules']['file_integrity'] ) ) { $recommendations[] = array( 'icon' => 'media-text', 'priority' => 'medium', 'message' => __( 'Enable File Integrity to detect unauthorized file changes.', 'vigilante' ), ); } // Medium: Security Audit disabled if ( empty( $options['modules']['activity_log'] ) ) { $recommendations[] = array( 'icon' => 'list-view', 'priority' => 'medium', 'message' => __( 'Enable Security Audit to track security events.', 'vigilante' ), ); } // Low: XML-RPC enabled (only if login security is active) if ( ! empty( $options['modules']['login_security'] ) && empty( $login['disable_xmlrpc'] ) ) { $recommendations[] = array( 'icon' => 'info', 'priority' => 'low', 'message' => __( 'Disable XML-RPC if not needed (reduces attack surface).', 'vigilante' ), 'tab' => 'login', ); } // Low: Strong passwords not enforced (only if user security is active) $user = $options['user_security'] ?? array(); if ( ! empty( $options['modules']['user_security'] ) && empty( $user['force_strong_passwords'] ) ) { $recommendations[] = array( 'icon' => 'admin-users', 'priority' => 'low', 'message' => __( 'Enforce strong passwords for all users.', 'vigilante' ), 'tab' => 'users', ); } // High: WP_DEBUG active in production (regardless of Vigilante settings) if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { $recommendations[] = array( 'icon' => 'warning', 'priority' => 'high', 'message' => __( 'WP_DEBUG is active. Debug mode exposes sensitive information and should be disabled in production.', 'vigilante' ), 'tab' => 'wp-hardening', ); } // Low: Users with display name matching login username (only if user security active) if ( ! empty( $options['modules']['user_security'] ) ) { $exposed_users = $this->get_users_with_exposed_login(); if ( ! empty( $exposed_users ) ) { $recommendations[] = array( 'icon' => 'admin-users', 'priority' => 'low', 'message' => sprintf( /* translators: %s: Comma-separated list of usernames */ __( 'These users have their login username as display name (publicly visible): %s', 'vigilante' ), implode( ', ', $exposed_users ) ), 'tab' => 'users', ); } } // High: Accounts with insecure usernames (regardless of module status) $insecure_admins = $this->get_insecure_admin_usernames(); if ( ! empty( $insecure_admins ) ) { $recommendations[] = array( 'icon' => 'warning', 'priority' => 'high', 'message' => sprintf( /* translators: %s: Comma-separated list of usernames */ __( 'Insecure usernames detected: %s. These are commonly targeted in brute force attacks. Create new accounts with unique usernames and remove these.', 'vigilante' ), implode( ', ', $insecure_admins ) ), ); } // Critical: Closed or removed plugins detected by the daily check. // Reads from the cached state map populated by Vigilante_Plugin_Status, so // there is no extra HTTP call here. Ignored slugs are filtered out so the // recommendation respects the user's per-slug Ignore decisions. if ( ! empty( $options['modules']['file_integrity'] ) && ! empty( $options['file_integrity']['check_closed_plugins'] ) ) { if ( ! class_exists( 'Vigilante_Plugin_Status' ) ) { require_once VIGILANTE_INCLUDES_DIR . 'class-plugin-status.php'; } $closed_checker = new Vigilante_Plugin_Status( $this->settings, $this->activity_log ); $closed_active = $closed_checker->get_closed_plugins(); if ( ! empty( $closed_active ) ) { $names = array(); foreach ( $closed_active as $slug => $entry ) { $names[] = isset( $entry['name'] ) ? $entry['name'] : $slug; } $recommendations[] = array( 'icon' => 'warning', 'priority' => 'critical', 'message' => sprintf( /* translators: 1: count, 2: comma-separated plugin names */ _n( '%1$d closed plugin detected on this site: %2$s. Closures in WordPress.org usually indicate malware, security issues or supply chain compromises. Uninstall and replace as soon as possible.', '%1$d closed plugins detected on this site: %2$s. Closures in WordPress.org usually indicate malware, security issues or supply chain compromises. Uninstall and replace as soon as possible.', count( $closed_active ), 'vigilante' ), count( $closed_active ), implode( ', ', $names ) ), 'tab' => 'file-integrity', ); } } // Medium: Security Audit is on but no audit alert is configured. The // alerting layer only makes sense while the activity log is running. if ( ! empty( $options['modules']['activity_log'] ) ) { $alerts = isset( $options['audit_alerts'] ) ? (array) $options['audit_alerts'] : array(); if ( ! Vigilante_Audit_Alerts::has_active_alerts( $alerts ) ) { $recommendations[] = array( 'icon' => 'email-alt', 'priority' => 'medium', 'message' => __( 'Set up Audit Alerts to get an email when something important happens (a new admin, a closed plugin, or an attack in progress).', 'vigilante' ), 'tab' => 'activity-log', ); } } // Sort by priority $priority_order = array( 'critical' => 0, 'high' => 1, 'medium' => 2, 'low' => 3 ); usort( $recommendations, function( $a, $b ) use ( $priority_order ) { return ( $priority_order[ $a['priority'] ] ?? 99 ) - ( $priority_order[ $b['priority'] ] ?? 99 ); } ); return $recommendations; } /** * Get users whose display name matches their login username * * Limited to administrators and editors for performance and relevance. * Cached with transient to avoid repeated queries on every dashboard load. * * @return array Array of usernames with exposed login. */ private function get_users_with_exposed_login() { $cache_key = 'vigilante_exposed_display_names'; $cached = get_transient( $cache_key ); if ( false !== $cached ) { return $cached; } $exposed = array(); $users = get_users( array( 'role__in' => array( 'administrator', 'editor' ), 'fields' => array( 'ID', 'user_login', 'display_name' ), ) ); foreach ( $users as $user ) { if ( strcasecmp( $user->display_name, $user->user_login ) === 0 ) { $exposed[] = $user->user_login; } } // Cache for 12 hours set_transient( $cache_key, $exposed, 12 * HOUR_IN_SECONDS ); return $exposed; } /** * Get accounts with insecure usernames * * Checks for common default usernames that are targeted by brute force attacks. * Detects any user regardless of role (consistent with username creation blocking). * Uses WordPress object cache via get_user_by() so no transient needed. * * @return array Array of insecure usernames found. */ private function get_insecure_admin_usernames() { $priority_usernames = array( 'admin', 'administrator', 'root', 'test', 'user', 'guest', 'info', 'sysadmin', 'webmaster' ); $found = array(); foreach ( $priority_usernames as $username ) { $user = get_user_by( 'login', $username ); if ( $user ) { $found[] = $username; } } return $found; } /** * Get security status for menu badge * * Returns count of disabled modules and whether there are critical issues. * Critical = Firewall or Login Security disabled. * * @return array Array with 'count' and 'has_critical'. */ public function get_security_status_for_badge() { $options = $this->settings->get_all_options(); $modules = $options['modules'] ?? array(); // Count disabled modules $disabled_count = 0; $has_critical = false; // Critical modules - if disabled, badge is red $critical_modules = array( 'firewall', 'login_security' ); foreach ( $modules as $module => $enabled ) { // Handle both boolean and string values ('1', '0', true, false) $is_enabled = filter_var( $enabled, FILTER_VALIDATE_BOOLEAN ); if ( ! $is_enabled ) { $disabled_count++; // Check if this is a critical module if ( in_array( $module, $critical_modules, true ) ) { $has_critical = true; } } } return array( 'count' => $disabled_count, 'has_critical' => $has_critical, ); } /** * Get count of security issues for menu badge (deprecated, use get_security_status_for_badge) * * @return int Count of critical/high issues. */ public function get_security_issues_count() { $status = $this->get_security_status_for_badge(); return $status['count']; } /** * Register settings */ public function register_settings() { register_setting( 'vigilante_options', Vigilante_Settings::OPTION_NAME, array( $this->settings, 'validate_options' ) ); } /** * Index behind the settings search box. * * Each entry points at one settings row. The search matches on the label, * on its English original and on 'keywords', which are extra terms someone * might type instead of the label itself. * * Those keywords are wrapped in _x() with the context "settings search * keywords" so every locale can supply its own: the source strings are in * English, and a Spanish user typing "contrasena" or a German one typing * "Kennwort" only reaches the password settings if that locale translated * them. Translators can add, drop or replace terms freely, one per space; * they are never displayed, only matched against what the user types. * * The list is maintained by hand, so a new settings row needs an entry here * or it cannot be found. It had drifted to 68 of 131 rows before 2.9.7. * * @return array */ private function get_search_index() { return array( // Firewall - Main array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Firewall Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-main', 'label' => __( 'Block bad bots', 'vigilante' ), 'label_en' => 'Block bad bots', 'keywords' => _x( 'block bad bots blocking blocked deny malicious harmful bot crawler crawlers spider scraper robots', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Firewall Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-main', 'label' => __( 'Block malicious requests', 'vigilante' ), 'label_en' => 'Block malicious requests', 'keywords' => _x( 'block malicious requests blocking blocked deny attack attacks exploit injection sqli xss rfi lfi request traffic', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Firewall Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-main', 'label' => __( 'Rate limiting', 'vigilante' ), 'label_en' => 'Rate limiting', 'keywords' => _x( 'rate limiting throttle flood burst limit limits', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Firewall Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-main', 'label' => __( 'Brute force protection', 'vigilante' ), 'label_en' => 'Brute force protection', 'keywords' => _x( 'brute force protection bruteforce login', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Firewall Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-main', 'label' => __( 'IP Whitelist', 'vigilante' ), 'label_en' => 'IP Whitelist', 'keywords' => _x( 'ip whitelist ips address addresses cidr ipv4 ipv6 allowlist allowed trusted', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Firewall Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-main', 'label' => __( 'IP Blacklist', 'vigilante' ), 'label_en' => 'IP Blacklist', 'keywords' => _x( 'ip blacklist ips address addresses cidr ipv4 ipv6 blocklist denylist banned', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Firewall Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-main', 'label' => __( 'User-Agent Whitelist', 'vigilante' ), 'label_en' => 'User-Agent Whitelist', 'keywords' => _x( 'user-agent whitelist ua useragent browser allowlist allowed trusted user', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Firewall Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-main', 'label' => __( 'User-Agent Blacklist', 'vigilante' ), 'label_en' => 'User-Agent Blacklist', 'keywords' => _x( 'user-agent blacklist ua useragent browser blocklist denylist banned user', 'settings search keywords', 'vigilante' ) ), // Firewall - Server Protection array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Server Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-server', 'label' => __( 'Directory Browsing', 'vigilante' ), 'label_en' => 'Directory Browsing', 'keywords' => _x( 'directory browsing folder folders listing indexing index', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Server Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-server', 'label' => __( 'Protect wp-config.php', 'vigilante' ), 'label_en' => 'Protect wp-config.php', 'keywords' => _x( 'protect wp-config php protection secure lock', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Server Protection', 'vigilante' ), 'anchor' => 'field-protect-wp-cron', 'label' => __( 'Protect wp-cron.php', 'vigilante' ), 'label_en' => 'Protect wp-cron.php', 'keywords' => _x( 'protect wp-cron php protection secure lock cron scheduled tasks block spam', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Server Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-server', 'label' => __( 'Protect wp-includes', 'vigilante' ), 'label_en' => 'Protect wp-includes', 'keywords' => _x( 'protect wp-includes protection secure lock', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Server Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-server', 'label' => __( 'PHP in Uploads', 'vigilante' ), 'label_en' => 'PHP in Uploads', 'keywords' => _x( 'php in uploads media upload', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Server Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-server', 'label' => __( 'Sensitive Files', 'vigilante' ), 'label_en' => 'Sensitive Files', 'keywords' => _x( 'sensitive files private secret file log', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Server Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-server', 'label' => __( 'Limit HTTP Methods', 'vigilante' ), 'label_en' => 'Limit HTTP Methods', 'keywords' => _x( 'limit http methods', 'settings search keywords', 'vigilante' ) ), // Security Headers array( 'tab' => 'headers', 'tab_label' => __( 'Security Headers', 'vigilante' ), 'section' => __( 'Security Headers', 'vigilante' ), 'anchor' => 'vigilante-section-headers-main', 'label' => __( 'X-Frame-Options', 'vigilante' ), 'label_en' => 'X-Frame-Options', 'keywords' => _x( 'x-frame-options headers', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'headers', 'tab_label' => __( 'Security Headers', 'vigilante' ), 'section' => __( 'Security Headers', 'vigilante' ), 'anchor' => 'vigilante-section-headers-main', 'label' => __( 'X-Content-Type-Options', 'vigilante' ), 'label_en' => 'X-Content-Type-Options', 'keywords' => _x( 'x-content-type-options content headers', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'headers', 'tab_label' => __( 'Security Headers', 'vigilante' ), 'section' => __( 'Security Headers', 'vigilante' ), 'anchor' => 'vigilante-section-headers-main', 'label' => __( 'Referrer-Policy', 'vigilante' ), 'label_en' => 'Referrer-Policy', 'keywords' => _x( 'referrer-policy headers', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'headers', 'tab_label' => __( 'Security Headers', 'vigilante' ), 'section' => __( 'HSTS', 'vigilante' ), 'anchor' => 'vigilante-section-headers-main', 'label' => __( 'HSTS', 'vigilante' ), 'label_en' => 'HSTS', 'keywords' => _x( 'hsts strict transport security ssl tls https headers', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'headers', 'tab_label' => __( 'Security Headers', 'vigilante' ), 'section' => __( 'Content Security Policy', 'vigilante' ), 'anchor' => 'vigilante-section-headers-main', 'label' => __( 'Content Security Policy', 'vigilante' ), 'label_en' => 'Content Security Policy', 'keywords' => _x( 'content security policy csp xss headers', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'headers', 'tab_label' => __( 'Security Headers', 'vigilante' ), 'section' => __( 'Server Identity', 'vigilante' ), 'anchor' => 'vigilante-section-headers-main', 'label' => __( 'Server Signature', 'vigilante' ), 'label_en' => 'Server Signature', 'keywords' => _x( 'server signature fingerprint banner', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'headers', 'tab_label' => __( 'Security Headers', 'vigilante' ), 'section' => __( 'Server Identity', 'vigilante' ), 'anchor' => 'vigilante-section-headers-main', 'label' => __( 'Remove Fingerprinting Headers', 'vigilante' ), 'label_en' => 'Remove Fingerprinting Headers', 'keywords' => _x( 'remove fingerprinting headers fingerprint banner header http', 'settings search keywords', 'vigilante' ) ), // Security Headers - Cross-Origin Policies array( 'tab' => 'headers', 'tab_label' => __( 'Security Headers', 'vigilante' ), 'section' => __( 'Cross-Origin Policies', 'vigilante' ), 'anchor' => 'vigilante-section-headers-cross-origin', 'label' => __( 'Cross-Origin-Opener-Policy (COOP)', 'vigilante' ), 'label_en' => 'Cross-Origin-Opener-Policy (COOP)', 'keywords' => _x( 'coop cross-origin opener policy popup popups window opener tag assistant google isolation browsing context headers', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'headers', 'tab_label' => __( 'Security Headers', 'vigilante' ), 'section' => __( 'Cross-Origin Policies', 'vigilante' ), 'anchor' => 'vigilante-section-headers-cross-origin', 'label' => __( 'Cross-Origin-Embedder-Policy (COEP)', 'vigilante' ), 'label_en' => 'Cross-Origin-Embedder-Policy (COEP)', 'keywords' => _x( 'coep cross-origin embedder policy require-corp credentialless embed embeds iframe fonts headers', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'headers', 'tab_label' => __( 'Security Headers', 'vigilante' ), 'section' => __( 'Cross-Origin Policies', 'vigilante' ), 'anchor' => 'vigilante-section-headers-cross-origin', 'label' => __( 'Cross-Origin-Resource-Policy (CORP)', 'vigilante' ), 'label_en' => 'Cross-Origin-Resource-Policy (CORP)', 'keywords' => _x( 'corp cross-origin resource policy hotlink hotlinking cdn images assets headers', 'settings search keywords', 'vigilante' ) ), // Login Security array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Custom login URL', 'vigilante' ), 'label_en' => 'Custom login URL', 'keywords' => _x( 'custom login url signin log-in access slug', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Two-Factor Authentication', 'vigilante' ), 'label_en' => 'Two-Factor Authentication', 'keywords' => _x( 'two-factor authentication 2fa mfa otp totp authenticator', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( '2FA', 'vigilante' ), 'label_en' => '2FA', 'keywords' => _x( '2fa two-factor mfa otp totp authenticator', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Failed login attempts', 'vigilante' ), 'label_en' => 'Failed login attempts', 'keywords' => _x( 'failed login attempts signin log-in access tries retries', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Lockout', 'vigilante' ), 'label_en' => 'Lockout', 'keywords' => _x( 'lockout lock ban block login', 'settings search keywords', 'vigilante' ) ), // REST API array( 'tab' => 'rest-api', 'tab_label' => __( 'REST API', 'vigilante' ), 'section' => __( 'REST API Security', 'vigilante' ), 'anchor' => 'vigilante-section-rest-api-main', 'label' => __( 'Access Mode', 'vigilante' ), 'label_en' => 'Access Mode', 'keywords' => _x( 'access mode rest api', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'rest-api', 'tab_label' => __( 'REST API', 'vigilante' ), 'section' => __( 'REST API Security', 'vigilante' ), 'anchor' => 'vigilante-section-rest-api-main', 'label' => __( 'Block User Enumeration', 'vigilante' ), 'label_en' => 'Block User Enumeration', 'keywords' => _x( 'block user enumeration blocking blocked deny users account author slug', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'rest-api', 'tab_label' => __( 'REST API', 'vigilante' ), 'section' => __( 'REST API Security', 'vigilante' ), 'anchor' => 'vigilante-section-rest-api-main', 'label' => __( 'Disable JSONP', 'vigilante' ), 'label_en' => 'Disable JSONP', 'keywords' => _x( 'disable jsonp', 'settings search keywords', 'vigilante' ) ), // User Security array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Username & password protection', 'vigilante' ), 'anchor' => 'vigilante-section-users-password', 'label' => __( 'Username protection', 'vigilante' ), 'label_en' => 'Username protection', 'keywords' => _x( 'username protection admin', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Username & password protection', 'vigilante' ), 'anchor' => 'vigilante-section-users-password', 'label' => __( 'Password strength', 'vigilante' ), 'label_en' => 'Password strength', 'keywords' => _x( 'password strength passwords credentials', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Admin monitoring', 'vigilante' ), 'anchor' => 'vigilante-section-users-admin-monitoring', 'label' => __( 'Admin monitoring', 'vigilante' ), 'label_en' => 'Admin monitoring', 'keywords' => _x( 'admin monitoring administrator administrators', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Registration approval', 'vigilante' ), 'anchor' => 'vigilante-section-users-registration', 'label' => __( 'Registration approval', 'vigilante' ), 'label_en' => 'Registration approval', 'keywords' => _x( 'registration approval signup register approve moderate', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Session limits', 'vigilante' ), 'anchor' => 'vigilante-section-users-sessions', 'label' => __( 'Session limits', 'vigilante' ), 'label_en' => 'Session limits', 'keywords' => _x( 'session limits sessions concurrent', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Password expiration', 'vigilante' ), 'anchor' => 'vigilante-section-users-password-exp', 'label' => __( 'Password expiration', 'vigilante' ), 'label_en' => 'Password expiration', 'keywords' => _x( 'password expiration passwords credentials expiry expire caducity', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Email verification', 'vigilante' ), 'anchor' => 'vigilante-section-users-email-verify', 'label' => __( 'Email verification', 'vigilante' ), 'label_en' => 'Email verification', 'keywords' => _x( 'email verification mail notification notify verify confirm', 'settings search keywords', 'vigilante' ) ), // WP Hardening array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'Database Hardening', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-database', 'label' => __( 'Database Hardening', 'vigilante' ), 'label_en' => 'Database Hardening', 'keywords' => _x( 'database hardening db mysql tables', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'Database Hardening', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-database', 'label' => __( 'Database prefix', 'vigilante' ), 'label_en' => 'Database prefix', 'keywords' => _x( 'database prefix db mysql tables table', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'wp-config.php Security', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-wpconfig', 'label' => __( 'Disable file editing', 'vigilante' ), 'label_en' => 'Disable file editing', 'keywords' => _x( 'disable file editing files editor edit', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'wp-config.php Security', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-wpconfig', 'label' => __( 'Disable plugin/theme installation', 'vigilante' ), 'label_en' => 'Disable plugin/theme installation', 'keywords' => _x( 'disable plugin theme installation install', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'wp-config.php Security', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-wpconfig', 'label' => __( 'Force SSL admin', 'vigilante' ), 'label_en' => 'Force SSL admin', 'keywords' => _x( 'force ssl admin bruteforce administrator administrators tls https', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'wp-config.php Security', 'vigilante' ), 'anchor' => 'field-disable-wp-cron', 'label' => __( 'Disable WP Cron', 'vigilante' ), 'label_en' => 'Disable WP Cron', 'keywords' => _x( 'disable wp cron scheduled tasks wp-cron', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'Comment Security', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-comments', 'label' => __( 'Comment Security', 'vigilante' ), 'label_en' => 'Comment Security', 'keywords' => _x( 'comment security comments spam honeypot url', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'Header Cleanup', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-headers', 'label' => __( 'Header Cleanup', 'vigilante' ), 'label_en' => 'Header Cleanup', 'keywords' => _x( 'header cleanup headers http meta generator rsd wlwmanifest', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'Header Cleanup', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-headers', 'label' => __( 'Remove WordPress version', 'vigilante' ), 'label_en' => 'Remove WordPress version', 'keywords' => _x( 'remove wordpress version generator meta', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'Header Cleanup', 'vigilante' ), 'anchor' => 'field-remove-wp-version-assets', 'label' => __( 'Remove version from assets', 'vigilante' ), 'label_en' => 'Remove version from assets', 'keywords' => _x( 'remove version from assets', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'Header Cleanup', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-xmlrpc', 'label' => __( 'Disable XML-RPC', 'vigilante' ), 'label_en' => 'Disable XML-RPC', 'keywords' => _x( 'disable xml-rpc xmlrpc rpc remote jetpack app pingback trackback', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'RSS Feed Settings', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-rss', 'label' => __( 'RSS Feed Settings', 'vigilante' ), 'label_en' => 'RSS Feed Settings', 'keywords' => _x( 'rss feed settings feeds atom', 'settings search keywords', 'vigilante' ) ), // File Integrity array( 'tab' => 'file-integrity', 'tab_label' => __( 'File Integrity', 'vigilante' ), 'section' => __( 'File Integrity Monitoring', 'vigilante' ), 'anchor' => 'vigilante-section-fi-monitoring', 'label' => __( 'File Integrity Monitoring', 'vigilante' ), 'label_en' => 'File Integrity Monitoring', 'keywords' => _x( 'file integrity monitoring files checksum checksums tamper', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'file-integrity', 'tab_label' => __( 'File Integrity', 'vigilante' ), 'section' => __( 'File Integrity Monitoring', 'vigilante' ), 'anchor' => 'vigilante-section-fi-monitoring', 'label' => __( 'Scan schedule', 'vigilante' ), 'label_en' => 'Scan schedule', 'keywords' => _x( 'scan schedule scans scanning check cron', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'file-integrity', 'tab_label' => __( 'File Integrity', 'vigilante' ), 'section' => __( 'File Integrity Monitoring', 'vigilante' ), 'anchor' => 'vigilante-section-fi-monitoring', 'label' => __( 'Instant alert', 'vigilante' ), 'label_en' => 'Instant alert', 'keywords' => _x( 'instant alert alerts notification warning email', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'file-integrity', 'tab_label' => __( 'File Integrity', 'vigilante' ), 'section' => __( 'Ignored Files', 'vigilante' ), 'anchor' => 'vigilante-section-fi-ignored', 'label' => __( 'Ignored Files', 'vigilante' ), 'label_en' => 'Ignored Files', 'keywords' => _x( 'ignored files file exclude', 'settings search keywords', 'vigilante' ) ), // Security Audit array( 'tab' => 'activity-log', 'tab_label' => __( 'Security Audit', 'vigilante' ), 'section' => __( 'Security Audit Settings', 'vigilante' ), 'anchor' => 'vigilante-section-audit-settings', 'label' => __( 'Retention', 'vigilante' ), 'label_en' => 'Retention', 'keywords' => _x( 'retention keep days storage log', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'activity-log', 'tab_label' => __( 'Security Audit', 'vigilante' ), 'section' => __( 'Security Audit Settings', 'vigilante' ), 'anchor' => 'vigilante-section-audit-settings', 'label' => __( 'Events to Log', 'vigilante' ), 'label_en' => 'Events to Log', 'keywords' => _x( 'events to log', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'activity-log', 'tab_label' => __( 'Security Audit', 'vigilante' ), 'section' => __( 'Security Audit Settings', 'vigilante' ), 'anchor' => 'vigilante-section-audit-settings', 'label' => __( 'Option Tracking', 'vigilante' ), 'label_en' => 'Option Tracking', 'keywords' => _x( 'option tracking', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'activity-log', 'tab_label' => __( 'Security Audit', 'vigilante' ), 'section' => __( 'Security Audit Settings', 'vigilante' ), 'anchor' => 'vigilante-section-audit-settings', 'label' => __( 'Exclusions', 'vigilante' ), 'label_en' => 'Exclusions', 'keywords' => _x( 'exclusions roles ip', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'activity-log', 'tab_label' => __( 'Security Audit', 'vigilante' ), 'section' => __( 'Audit Alerts', 'vigilante' ), 'anchor' => 'vigilante-section-audit-alerts', 'label' => __( 'Audit Alerts', 'vigilante' ), 'label_en' => 'Audit Alerts', 'keywords' => _x( 'audit alerts email mail warning critical', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'activity-log', 'tab_label' => __( 'Security Audit', 'vigilante' ), 'section' => __( 'Audit Alerts', 'vigilante' ), 'anchor' => 'field-audit-alerts-immediate', 'label' => __( 'Immediate alerts', 'vigilante' ), 'label_en' => 'Immediate alerts', 'keywords' => _x( 'immediate alerts email mail critical warning', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'activity-log', 'tab_label' => __( 'Security Audit', 'vigilante' ), 'section' => __( 'Audit Alerts', 'vigilante' ), 'anchor' => 'field-audit-alerts-threshold', 'label' => __( 'Threshold alerts', 'vigilante' ), 'label_en' => 'Threshold alerts', 'keywords' => _x( 'threshold alerts email mail login', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'activity-log', 'tab_label' => __( 'Security Audit', 'vigilante' ), 'section' => __( 'Recent Activity', 'vigilante' ), 'anchor' => 'vigilante-section-audit-recent', 'label' => __( 'Recent Activity', 'vigilante' ), 'label_en' => 'Recent Activity', 'keywords' => _x( 'recent activity log', 'settings search keywords', 'vigilante' ) ), // Settings & Tools array( 'tab' => 'tools', 'tab_label' => __( 'Settings & Tools', 'vigilante' ), 'section' => __( 'Notification settings', 'vigilante' ), 'anchor' => 'vigilante-section-tools-notifications', 'label' => __( 'Notification settings', 'vigilante' ), 'label_en' => 'Notification settings', 'keywords' => _x( 'notification settings email', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'tools', 'tab_label' => __( 'Settings & Tools', 'vigilante' ), 'section' => __( 'Notification settings', 'vigilante' ), 'anchor' => 'vigilante-section-tools-notifications', 'label' => __( 'Additional Recipients', 'vigilante' ), 'label_en' => 'Additional Recipients', 'keywords' => _x( 'additional recipients email recipient', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'tools', 'tab_label' => __( 'Settings & Tools', 'vigilante' ), 'section' => __( 'Tools', 'vigilante' ), 'anchor' => 'vigilante-section-tools-main', 'label' => __( 'Export Settings', 'vigilante' ), 'label_en' => 'Export Settings', 'keywords' => _x( 'export settings json', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'tools', 'tab_label' => __( 'Settings & Tools', 'vigilante' ), 'section' => __( 'Tools', 'vigilante' ), 'anchor' => 'vigilante-section-tools-main', 'label' => __( 'Import Settings', 'vigilante' ), 'label_en' => 'Import Settings', 'keywords' => _x( 'import settings json', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'tools', 'tab_label' => __( 'Settings & Tools', 'vigilante' ), 'section' => __( 'Tools', 'vigilante' ), 'anchor' => 'vigilante-section-tools-main', 'label' => __( 'Reset to Defaults', 'vigilante' ), 'label_en' => 'Reset to Defaults', 'keywords' => _x( 'reset to defaults', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'tools', 'tab_label' => __( 'Settings & Tools', 'vigilante' ), 'section' => __( 'Tools', 'vigilante' ), 'anchor' => 'vigilante-section-tools-main', 'label' => __( 'Create Backup', 'vigilante' ), 'label_en' => 'Create Backup', 'keywords' => _x( 'create backup', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'tools', 'tab_label' => __( 'Settings & Tools', 'vigilante' ), 'section' => __( 'Tools', 'vigilante' ), 'anchor' => 'vigilante-section-tools-main', 'label' => __( 'Database Backup', 'vigilante' ), 'label_en' => 'Database Backup', 'keywords' => _x( 'database backup db mysql tables', 'settings search keywords', 'vigilante' ) ), // Entradas anadidas en la 2.9.7 tras comprobar que el indice cubria 68 de // las 131 filas de ajustes: buscar XML-RPC, por ejemplo, no devolvia nada. // El indice se mantiene a mano, asi que al anadir una fila de ajustes hay // que anadirla tambien aqui. array( 'tab' => 'tools', 'tab_label' => __( 'Settings & Tools', 'vigilante' ), 'section' => __( 'Notification settings', 'vigilante' ), 'anchor' => 'vigilante-section-tools-notifications', 'label' => __( 'WordPress Admin Email', 'vigilante' ), 'label_en' => 'WordPress Admin Email', 'keywords' => _x( 'wordpress admin email administrator administrators mail notification notify', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'tools', 'tab_label' => __( 'Settings & Tools', 'vigilante' ), 'section' => __( 'Notification settings', 'vigilante' ), 'anchor' => 'vigilante-section-tools-notifications', 'label' => __( 'Plugin Deactivation', 'vigilante' ), 'label_en' => 'Plugin Deactivation', 'keywords' => _x( 'plugin deactivation', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Firewall Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-main', 'label' => __( 'Block Bad Query Strings', 'vigilante' ), 'label_en' => 'Block Bad Query Strings', 'keywords' => _x( 'block bad query strings blocking blocked deny malicious harmful', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Firewall Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-main', 'label' => __( 'SQL Injection Protection', 'vigilante' ), 'label_en' => 'SQL Injection Protection', 'keywords' => _x( 'sql injection protection', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Firewall Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-main', 'label' => __( 'XSS Protection', 'vigilante' ), 'label_en' => 'XSS Protection', 'keywords' => _x( 'xss protection', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Firewall Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-main', 'label' => __( 'File Inclusion Protection', 'vigilante' ), 'label_en' => 'File Inclusion Protection', 'keywords' => _x( 'file inclusion protection files', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Firewall Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-main', 'label' => __( 'Directory Traversal Protection', 'vigilante' ), 'label_en' => 'Directory Traversal Protection', 'keywords' => _x( 'directory traversal protection folder folders', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Firewall Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-main', 'label' => __( 'Enable Rate Limiting', 'vigilante' ), 'label_en' => 'Enable Rate Limiting', 'keywords' => _x( 'enable rate limiting throttle flood burst limit limits', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Firewall Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-main', 'label' => __( 'Requests per Minute', 'vigilante' ), 'label_en' => 'Requests per Minute', 'keywords' => _x( 'requests per minute request traffic', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Firewall Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-main', 'label' => __( 'Block Duration (seconds)', 'vigilante' ), 'label_en' => 'Block Duration (seconds)', 'keywords' => _x( 'block duration seconds blocking blocked deny', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Firewall Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-main', 'label' => __( 'Progressive Blocking', 'vigilante' ), 'label_en' => 'Progressive Blocking', 'keywords' => _x( 'progressive blocking', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Firewall Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-main', 'label' => __( 'Maximum Block Duration', 'vigilante' ), 'label_en' => 'Maximum Block Duration', 'keywords' => _x( 'maximum block duration blocking blocked deny', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'firewall', 'tab_label' => __( 'Firewall', 'vigilante' ), 'section' => __( 'Firewall Protection', 'vigilante' ), 'anchor' => 'vigilante-section-firewall-main', 'label' => __( 'Visitor IP detection', 'vigilante' ), 'label_en' => 'Visitor IP detection', 'keywords' => _x( 'visitor ip detection ips address addresses cidr ipv4 ipv6', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Max Login Attempts', 'vigilante' ), 'label_en' => 'Max Login Attempts', 'keywords' => _x( 'max login attempts signin log-in access tries retries', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Lockout Duration', 'vigilante' ), 'label_en' => 'Lockout Duration', 'keywords' => _x( 'lockout duration lock ban block', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Progressive Lockout', 'vigilante' ), 'label_en' => 'Progressive Lockout', 'keywords' => _x( 'progressive lockout lock ban block', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Hide Login Errors', 'vigilante' ), 'label_en' => 'Hide Login Errors', 'keywords' => _x( 'hide login errors signin log-in access error debug log', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Disable Application Passwords', 'vigilante' ), 'label_en' => 'Disable Application Passwords', 'keywords' => _x( 'disable application passwords password credentials', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Login URL Slug', 'vigilante' ), 'label_en' => 'Login URL Slug', 'keywords' => _x( 'login url slug signin log-in access path', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Notify users', 'vigilante' ), 'label_en' => 'Notify users', 'keywords' => _x( 'notify users notification alert email user accounts', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Notify on Lockout', 'vigilante' ), 'label_en' => 'Notify on Lockout', 'keywords' => _x( 'notify on lockout notification alert email lock ban block', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Notify on Admin Login', 'vigilante' ), 'label_en' => 'Notify on Admin Login', 'keywords' => _x( 'notify on admin login notification alert email administrator administrators signin log-in access', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection Status', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Current settings', 'vigilante' ), 'label_en' => 'Current settings', 'keywords' => _x( 'current settings', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection Status', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Blocked IPs', 'vigilante' ), 'label_en' => 'Blocked IPs', 'keywords' => _x( 'blocked ips', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection Status', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Enable 2FA', 'vigilante' ), 'label_en' => 'Enable 2FA', 'keywords' => _x( 'enable 2fa two-factor mfa otp totp authenticator', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection Status', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Verification method', 'vigilante' ), 'label_en' => 'Verification method', 'keywords' => _x( 'verification method verify confirm', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection Status', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Enforce for roles', 'vigilante' ), 'label_en' => 'Enforce for roles', 'keywords' => _x( 'enforce for roles role capabilities', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection Status', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Exclude specific users', 'vigilante' ), 'label_en' => 'Exclude specific users', 'keywords' => _x( 'exclude specific users user accounts', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection Status', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Remember device', 'vigilante' ), 'label_en' => 'Remember device', 'keywords' => _x( 'remember device', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection Status', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Grace period', 'vigilante' ), 'label_en' => 'Grace period', 'keywords' => _x( 'grace period', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection Status', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Email sender name', 'vigilante' ), 'label_en' => 'Email sender name', 'keywords' => _x( 'email sender name mail notification notify names', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection Status', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Reset user TOTP', 'vigilante' ), 'label_en' => 'Reset user TOTP', 'keywords' => _x( 'reset user totp users account 2fa authenticator app', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection Status', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Notify on enable', 'vigilante' ), 'label_en' => 'Notify on enable', 'keywords' => _x( 'notify on enable notification alert email', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'headers', 'tab_label' => __( 'Security Headers', 'vigilante' ), 'section' => __( 'Security Headers', 'vigilante' ), 'anchor' => 'vigilante-section-headers-main', 'label' => __( 'Enable CSP', 'vigilante' ), 'label_en' => 'Enable CSP', 'keywords' => _x( 'enable csp content security policy', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'headers', 'tab_label' => __( 'Security Headers', 'vigilante' ), 'section' => __( 'Security Headers', 'vigilante' ), 'anchor' => 'vigilante-section-headers-main', 'label' => __( 'Report Only Mode', 'vigilante' ), 'label_en' => 'Report Only Mode', 'keywords' => _x( 'report only mode', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'headers', 'tab_label' => __( 'Security Headers', 'vigilante' ), 'section' => __( 'Security Headers', 'vigilante' ), 'anchor' => 'vigilante-section-headers-main', 'label' => __( 'Redirect HTTP to HTTPS', 'vigilante' ), 'label_en' => 'Redirect HTTP to HTTPS', 'keywords' => _x( 'redirect http to https redirection forward ssl tls secure', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'headers', 'tab_label' => __( 'Security Headers', 'vigilante' ), 'section' => __( 'Security Headers', 'vigilante' ), 'anchor' => 'vigilante-section-headers-main', 'label' => __( 'Fix Mixed Content', 'vigilante' ), 'label_en' => 'Fix Mixed Content', 'keywords' => _x( 'fix mixed content insecure http', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'headers', 'tab_label' => __( 'Security Headers', 'vigilante' ), 'section' => __( 'Security Headers', 'vigilante' ), 'anchor' => 'field-upgrade-insecure-requests', 'label' => __( 'Upgrade Insecure Requests', 'vigilante' ), 'label_en' => 'Upgrade Insecure Requests', 'keywords' => _x( 'upgrade insecure requests mixed content csp https external resources', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'headers', 'tab_label' => __( 'Security Headers', 'vigilante' ), 'section' => __( 'Security Headers', 'vigilante' ), 'anchor' => 'vigilante-section-headers-main', 'label' => __( 'Rewrite Site Address on Activation', 'vigilante' ), 'label_en' => 'Rewrite Site Address on Activation', 'keywords' => _x( 'rewrite site address on activation', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'headers', 'tab_label' => __( 'Security Headers', 'vigilante' ), 'section' => __( 'Security Headers', 'vigilante' ), 'anchor' => 'vigilante-section-headers-main', 'label' => __( 'Enable HSTS', 'vigilante' ), 'label_en' => 'Enable HSTS', 'keywords' => _x( 'enable hsts strict transport security', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'headers', 'tab_label' => __( 'Security Headers', 'vigilante' ), 'section' => __( 'Security Headers', 'vigilante' ), 'anchor' => 'vigilante-section-headers-main', 'label' => __( 'Max Age', 'vigilante' ), 'label_en' => 'Max Age', 'keywords' => _x( 'max age', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'headers', 'tab_label' => __( 'Security Headers', 'vigilante' ), 'section' => __( 'Security Headers', 'vigilante' ), 'anchor' => 'vigilante-section-headers-main', 'label' => __( 'Include Subdomains', 'vigilante' ), 'label_en' => 'Include Subdomains', 'keywords' => _x( 'include subdomains', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Username & password protection', 'vigilante' ), 'anchor' => 'vigilante-section-users-password', 'label' => __( 'Block Insecure Usernames', 'vigilante' ), 'label_en' => 'Block Insecure Usernames', 'keywords' => _x( 'block insecure usernames blocking blocked deny weak unsafe', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Username & password protection', 'vigilante' ), 'anchor' => 'vigilante-section-users-password', 'label' => __( 'Enforce Strong Passwords', 'vigilante' ), 'label_en' => 'Enforce Strong Passwords', 'keywords' => _x( 'enforce strong passwords complexity password credentials', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Username & password protection', 'vigilante' ), 'anchor' => 'vigilante-section-users-password', 'label' => __( 'Minimum Password Length', 'vigilante' ), 'label_en' => 'Minimum Password Length', 'keywords' => _x( 'minimum password length passwords credentials characters', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Username & password protection', 'vigilante' ), 'anchor' => 'vigilante-section-users-password', 'label' => __( 'Password Requirements', 'vigilante' ), 'label_en' => 'Password Requirements', 'keywords' => _x( 'password requirements passwords credentials', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Username & password protection', 'vigilante' ), 'anchor' => 'vigilante-section-users-password', 'label' => __( 'Apply Password Rules To', 'vigilante' ), 'label_en' => 'Apply Password Rules To', 'keywords' => _x( 'apply password rules to passwords credentials', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Username & password protection', 'vigilante' ), 'anchor' => 'vigilante-section-users-password', 'label' => __( 'Block Author Scanning', 'vigilante' ), 'label_en' => 'Block Author Scanning', 'keywords' => _x( 'block author scanning blocking blocked deny authors enumeration probing', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Username & password protection', 'vigilante' ), 'anchor' => 'vigilante-section-users-password', 'label' => __( 'Display Name Protection', 'vigilante' ), 'label_en' => 'Display Name Protection', 'keywords' => _x( 'display name protection public visible names', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Admin monitoring', 'vigilante' ), 'anchor' => 'vigilante-section-users-admin-monitoring', 'label' => __( 'New Administrator Alert', 'vigilante' ), 'label_en' => 'New Administrator Alert', 'keywords' => _x( 'new administrator alert alerts notification warning', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Admin monitoring', 'vigilante' ), 'anchor' => 'vigilante-section-users-admin-monitoring', 'label' => __( 'Admin Email Change Alert', 'vigilante' ), 'label_en' => 'Admin Email Change Alert', 'keywords' => _x( 'admin email change alert administrator administrators mail notification notify alerts warning', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Admin monitoring', 'vigilante' ), 'anchor' => 'vigilante-section-users-admin-monitoring', 'label' => __( 'Permission Elevation Alert', 'vigilante' ), 'label_en' => 'Permission Elevation Alert', 'keywords' => _x( 'permission elevation alert alerts notification warning', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Admin monitoring', 'vigilante' ), 'anchor' => 'vigilante-section-users-admin-monitoring', 'label' => __( 'Admin Password Change Alert', 'vigilante' ), 'label_en' => 'Admin Password Change Alert', 'keywords' => _x( 'admin password change alert administrator administrators passwords credentials alerts notification warning', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Registration approval', 'vigilante' ), 'anchor' => 'vigilante-section-users-registration', 'label' => __( 'Enable Registration Approval', 'vigilante' ), 'label_en' => 'Enable Registration Approval', 'keywords' => _x( 'enable registration approval signup register approve moderate', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Registration approval', 'vigilante' ), 'anchor' => 'vigilante-section-users-registration', 'label' => __( 'Notify Admin', 'vigilante' ), 'label_en' => 'Notify Admin', 'keywords' => _x( 'notify admin notification alert email administrator administrators', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Registration approval', 'vigilante' ), 'anchor' => 'vigilante-section-users-registration', 'label' => __( 'Auto-reject After', 'vigilante' ), 'label_en' => 'Auto-reject After', 'keywords' => _x( 'auto-reject after', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Session limits', 'vigilante' ), 'anchor' => 'vigilante-section-users-sessions', 'label' => __( 'Enable Session Limits', 'vigilante' ), 'label_en' => 'Enable Session Limits', 'keywords' => _x( 'enable session limits sessions concurrent', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Session limits', 'vigilante' ), 'anchor' => 'vigilante-section-users-sessions', 'label' => __( 'Maximum Sessions', 'vigilante' ), 'label_en' => 'Maximum Sessions', 'keywords' => _x( 'maximum sessions session concurrent', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Session limits', 'vigilante' ), 'anchor' => 'vigilante-section-users-sessions', 'label' => __( 'When Limit Exceeded', 'vigilante' ), 'label_en' => 'When Limit Exceeded', 'keywords' => _x( 'when limit exceeded', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Session limits', 'vigilante' ), 'anchor' => 'vigilante-section-users-sessions', 'label' => __( 'Exclude Administrators', 'vigilante' ), 'label_en' => 'Exclude Administrators', 'keywords' => _x( 'exclude administrators', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Password expiration', 'vigilante' ), 'anchor' => 'vigilante-section-users-password-exp', 'label' => __( 'Enable Password Expiration', 'vigilante' ), 'label_en' => 'Enable Password Expiration', 'keywords' => _x( 'enable password expiration passwords credentials expiry expire caducity', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Password expiration', 'vigilante' ), 'anchor' => 'vigilante-section-users-password-exp', 'label' => __( 'Expire After', 'vigilante' ), 'label_en' => 'Expire After', 'keywords' => _x( 'expire after', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Password expiration', 'vigilante' ), 'anchor' => 'vigilante-section-users-password-exp', 'label' => __( 'Warning Period', 'vigilante' ), 'label_en' => 'Warning Period', 'keywords' => _x( 'warning period', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Password expiration', 'vigilante' ), 'anchor' => 'vigilante-section-users-password-exp', 'label' => __( 'Password History', 'vigilante' ), 'label_en' => 'Password History', 'keywords' => _x( 'password history passwords credentials reuse previous', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Password expiration', 'vigilante' ), 'anchor' => 'vigilante-section-users-password-exp', 'label' => __( 'Email Reminder', 'vigilante' ), 'label_en' => 'Email Reminder', 'keywords' => _x( 'email reminder mail notification notify', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Password expiration', 'vigilante' ), 'anchor' => 'vigilante-section-users-password-exp', 'label' => __( 'Affected Roles', 'vigilante' ), 'label_en' => 'Affected Roles', 'keywords' => _x( 'affected roles role capabilities', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Password expiration', 'vigilante' ), 'anchor' => 'vigilante-section-users-password-exp', 'label' => __( 'Exclude specific users', 'vigilante' ), 'label_en' => 'Exclude specific users', 'keywords' => _x( 'exclude specific users user accounts', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Email verification', 'vigilante' ), 'anchor' => 'vigilante-section-users-email-verify', 'label' => __( 'Enable Email Verification', 'vigilante' ), 'label_en' => 'Enable Email Verification', 'keywords' => _x( 'enable email verification mail notification notify verify confirm', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Email verification', 'vigilante' ), 'anchor' => 'vigilante-section-users-email-verify', 'label' => __( 'Link Expiration', 'vigilante' ), 'label_en' => 'Link Expiration', 'keywords' => _x( 'link expiration expiry expire caducity', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Email verification', 'vigilante' ), 'anchor' => 'vigilante-section-users-email-verify', 'label' => __( 'Allow Resend', 'vigilante' ), 'label_en' => 'Allow Resend', 'keywords' => _x( 'allow resend', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'users', 'tab_label' => __( 'User Security', 'vigilante' ), 'section' => __( 'Email verification', 'vigilante' ), 'anchor' => 'vigilante-section-users-email-verify', 'label' => __( 'Auto-delete Unverified', 'vigilante' ), 'label_en' => 'Auto-delete Unverified', 'keywords' => _x( 'auto-delete unverified', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'Database Hardening', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-database', 'label' => __( 'Current prefix', 'vigilante' ), 'label_en' => 'Current prefix', 'keywords' => _x( 'current prefix database db table tables mysql', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'Database Hardening', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-database', 'label' => __( 'New prefix', 'vigilante' ), 'label_en' => 'New prefix', 'keywords' => _x( 'new prefix database db table tables mysql', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'wp-config.php Security', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-wpconfig', 'label' => __( 'Disable File Editor', 'vigilante' ), 'label_en' => 'Disable File Editor', 'keywords' => _x( 'disable file editor files edit editing', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'wp-config.php Security', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-wpconfig', 'label' => __( 'Disable File Modifications', 'vigilante' ), 'label_en' => 'Disable File Modifications', 'keywords' => _x( 'disable file modifications files modify install update', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'wp-config.php Security', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-wpconfig', 'label' => __( 'Hide PHP errors from visitors', 'vigilante' ), 'label_en' => 'Hide PHP errors from visitors', 'keywords' => _x( 'hide php errors from visitors error debug log', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'XML-RPC', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-xmlrpc', 'label' => __( 'XML-RPC access', 'vigilante' ), 'label_en' => 'XML-RPC access', 'keywords' => _x( 'xml-rpc access xmlrpc rpc remote jetpack app', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'Comment Security', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-comments', 'label' => __( 'Disable Pingbacks', 'vigilante' ), 'label_en' => 'Disable Pingbacks', 'keywords' => _x( 'disable pingbacks pingback ping', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'Comment Security', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-comments', 'label' => __( 'Disable Trackbacks', 'vigilante' ), 'label_en' => 'Disable Trackbacks', 'keywords' => _x( 'disable trackbacks trackback ping', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'Comment Security', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-comments', 'label' => __( 'Require Moderation', 'vigilante' ), 'label_en' => 'Require Moderation', 'keywords' => _x( 'require moderation moderate approve', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'Comment Security', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-comments', 'label' => __( 'Close Old Comments', 'vigilante' ), 'label_en' => 'Close Old Comments', 'keywords' => _x( 'close old comments comment discussion', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'Comment Security', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-comments', 'label' => __( 'Honeypot Protection', 'vigilante' ), 'label_en' => 'Honeypot Protection', 'keywords' => _x( 'honeypot protection spam bots trap', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'Header Cleanup', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-headers', 'label' => __( 'Remove Generator', 'vigilante' ), 'label_en' => 'Remove Generator', 'keywords' => _x( 'remove generator version meta', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'Header Cleanup', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-headers', 'label' => __( 'Remove RSD Link', 'vigilante' ), 'label_en' => 'Remove RSD Link', 'keywords' => _x( 'remove rsd link discovery', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'Header Cleanup', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-headers', 'label' => __( 'Remove WLW Manifest', 'vigilante' ), 'label_en' => 'Remove WLW Manifest', 'keywords' => _x( 'remove wlw manifest wlwmanifest', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'Header Cleanup', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-headers', 'label' => __( 'Remove Shortlink', 'vigilante' ), 'label_en' => 'Remove Shortlink', 'keywords' => _x( 'remove shortlink link', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'Header Cleanup', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-headers', 'label' => __( 'Remove REST API Link', 'vigilante' ), 'label_en' => 'Remove REST API Link', 'keywords' => _x( 'remove rest api link json endpoint', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'RSS Feed Settings', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-rss', 'label' => __( 'Disable Feeds', 'vigilante' ), 'label_en' => 'Disable Feeds', 'keywords' => _x( 'disable feeds feed rss atom syndication', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'RSS Feed Settings', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-rss', 'label' => __( 'Disable If No Content', 'vigilante' ), 'label_en' => 'Disable If No Content', 'keywords' => _x( 'disable if no content', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'RSS Feed Settings', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-rss', 'label' => __( 'Remove Feed Version', 'vigilante' ), 'label_en' => 'Remove Feed Version', 'keywords' => _x( 'remove feed version feeds rss atom', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'activity-log', 'tab_label' => __( 'Security Audit', 'vigilante' ), 'section' => __( 'Audit Alerts', 'vigilante' ), 'anchor' => 'vigilante-section-audit-alerts', 'label' => __( 'Alert on severity', 'vigilante' ), 'label_en' => 'Alert on severity', 'keywords' => _x( 'alert on severity alerts notification warning level critical', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'activity-log', 'tab_label' => __( 'Security Audit', 'vigilante' ), 'section' => __( 'Audit Alerts', 'vigilante' ), 'anchor' => 'vigilante-section-audit-alerts', 'label' => __( 'Time window', 'vigilante' ), 'label_en' => 'Time window', 'keywords' => _x( 'time window', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'activity-log', 'tab_label' => __( 'Security Audit', 'vigilante' ), 'section' => __( 'Audit Alerts', 'vigilante' ), 'anchor' => 'vigilante-section-audit-alerts', 'label' => __( 'Thresholds per category', 'vigilante' ), 'label_en' => 'Thresholds per category', 'keywords' => _x( 'thresholds per category threshold limit', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'activity-log', 'tab_label' => __( 'Security Audit', 'vigilante' ), 'section' => __( 'Audit Alerts', 'vigilante' ), 'anchor' => 'vigilante-section-audit-alerts', 'label' => __( 'Recipients', 'vigilante' ), 'label_en' => 'Recipients', 'keywords' => _x( 'recipients email recipient', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'file-integrity', 'tab_label' => __( 'File Integrity', 'vigilante' ), 'section' => __( 'File Integrity Monitoring', 'vigilante' ), 'anchor' => 'vigilante-section-fi-monitoring', 'label' => __( 'Automatic Scans', 'vigilante' ), 'label_en' => 'Automatic Scans', 'keywords' => _x( 'automatic scans scan scanning', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'file-integrity', 'tab_label' => __( 'File Integrity', 'vigilante' ), 'section' => __( 'File Integrity Monitoring', 'vigilante' ), 'anchor' => 'vigilante-section-fi-monitoring', 'label' => __( 'Scan Frequency', 'vigilante' ), 'label_en' => 'Scan Frequency', 'keywords' => _x( 'scan frequency scans scanning check', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'file-integrity', 'tab_label' => __( 'File Integrity', 'vigilante' ), 'section' => __( 'File Integrity Monitoring', 'vigilante' ), 'anchor' => 'vigilante-section-fi-monitoring', 'label' => __( 'Email Notifications', 'vigilante' ), 'label_en' => 'Email Notifications', 'keywords' => _x( 'email notifications mail notification notify', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'file-integrity', 'tab_label' => __( 'File Integrity', 'vigilante' ), 'section' => __( 'File Integrity Monitoring', 'vigilante' ), 'anchor' => 'vigilante-section-fi-monitoring', 'label' => __( 'Test email', 'vigilante' ), 'label_en' => 'Test email', 'keywords' => _x( 'test email mail notification notify', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'file-integrity', 'tab_label' => __( 'File Integrity', 'vigilante' ), 'section' => __( 'File Integrity Monitoring', 'vigilante' ), 'anchor' => 'vigilante-section-fi-monitoring', 'label' => __( 'Scan Scope', 'vigilante' ), 'label_en' => 'Scan Scope', 'keywords' => _x( 'scan scope scans scanning check', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'file-integrity', 'tab_label' => __( 'File Integrity', 'vigilante' ), 'section' => __( 'File Integrity Monitoring', 'vigilante' ), 'anchor' => 'vigilante-section-fi-monitoring', 'label' => __( 'Excluded Paths', 'vigilante' ), 'label_en' => 'Excluded Paths', 'keywords' => _x( 'excluded paths exclude exclusions ignore ignored path folder folders', 'settings search keywords', 'vigilante' ) ), array( 'tab' => 'file-integrity', 'tab_label' => __( 'File Integrity', 'vigilante' ), 'section' => __( 'File Integrity Monitoring', 'vigilante' ), 'anchor' => 'vigilante-section-fi-monitoring', 'label' => __( 'Excluded Extensions', 'vigilante' ), 'label_en' => 'Excluded Extensions', 'keywords' => _x( 'excluded extensions exclude exclusions ignore ignored extension filetype', 'settings search keywords', 'vigilante' ) ), ); } /** * Enqueue admin assets * * @param string $hook Current admin page. */ public function enqueue_assets( $hook ) { // toplevel_page_vigilante for top-level menu page if ( 'toplevel_page_vigilante' !== $hook ) { return; } wp_enqueue_style( 'vigilante-admin', VIGILANTE_ASSETS_URL . 'css/admin.css', array(), VIGILANTE_VERSION ); wp_enqueue_script( 'vigilante-admin', VIGILANTE_ASSETS_URL . 'js/admin.js', array( 'jquery' ), VIGILANTE_VERSION, true ); wp_localize_script( 'vigilante-admin', 'vigilanteAdmin', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'vigilante_admin_nonce' ), 'currentUserId' => get_current_user_id(), 'logoutUrl' => wp_logout_url( wp_login_url() ), 'adminUrl' => admin_url( 'admin.php?page=vigilante' ), 'searchIndex' => $this->get_search_index(), // The scan repaints this table from JavaScript, so the same gate // has to travel with it or half the screen keeps the dead button. 'approvalLocked' => $this->critical_approval_locked(), 'underAttack' => array( 'active' => ( new Vigilante_Under_Attack( $this->settings, $this->activity_log ) )->is_active(), 'remaining' => ( new Vigilante_Under_Attack( $this->settings, $this->activity_log ) )->get_remaining_time(), ), 'strings' => array( 'saving' => __( 'Saving...', 'vigilante' ), 'sendingTest' => __( 'Sending...', 'vigilante' ), 'saved' => __( 'Settings saved', 'vigilante' ), 'error' => __( 'Error saving settings', 'vigilante' ), 'confirm' => __( 'Are you sure?', 'vigilante' ), 'scanning' => __( 'Scanning...', 'vigilante' ), 'scanComplete' => __( 'Scan complete', 'vigilante' ), 'loading' => __( 'Loading...', 'vigilante' ), 'searching' => __( 'Searching...', 'vigilante' ), 'noUsersFound' => __( 'No users found', 'vigilante' ), 'searchError' => __( 'Error searching users', 'vigilante' ), 'sending' => __( 'Sending...', 'vigilante' ), 'sendNotification' => __( 'Send notification now', 'vigilante' ), 'notificationsSent' => __( 'notifications sent', 'vigilante' ), 'skipped' => __( 'skipped', 'vigilante' ), 'failed' => __( 'failed', 'vigilante' ), 'customConfig' => __( 'Custom Configuration', 'vigilante' ), // Header tester strings 'testHeaders' => __( 'Test Headers', 'vigilante' ), 'testing' => __( 'Testing...', 'vigilante' ), 'score' => __( 'Score', 'vigilante' ), 'enabledHeaders' => __( 'Enabled headers', 'vigilante' ), 'missingHeaders' => __( 'Missing headers', 'vigilante' ), 'warnings' => __( 'Warnings', 'vigilante' ), // File integrity scan results strings 'scanResults' => __( 'Scan Results', 'vigilante' ), 'ok' => __( 'OK', 'vigilante' ), 'modified' => __( 'Modified', 'vigilante' ), 'suspicious' => __( 'Suspicious', 'vigilante' ), 'totalScanned' => __( 'Total Scanned', 'vigilante' ), 'suspiciousFiles' => __( 'Suspicious Files', 'vigilante' ), 'suspiciousWarning' => __( 'These files may contain malicious code or are in unexpected locations. Review immediately!', 'vigilante' ), 'file' => __( 'File', 'vigilante' ), 'reason' => __( 'Reason', 'vigilante' ), 'type' => __( 'Type', 'vigilante' ), 'unknown' => __( 'Unknown', 'vigilante' ), 'modifiedFiles' => __( 'Modified Files', 'vigilante' ), 'modifiedDescription' => __( 'These files (apparently) differ from the original WordPress or plugin versions.', 'vigilante' ), 'extraFiles' => __( 'Extra Files', 'vigilante' ), 'extra' => __( 'Extra', 'vigilante' ), 'ignored' => __( 'Ignored', 'vigilante' ), 'extraDescription' => __( 'PHP files found in plugins or themes that are not part of the original distribution from WordPress.org.', 'vigilante' ), 'actions' => __( 'Actions', 'vigilante' ), 'ignore' => __( 'Ignore', 'vigilante' ), 'ignoring' => __( 'Ignoring...', 'vigilante' ), 'fileIgnored' => __( 'File added to ignored list.', 'vigilante' ), 'fileUnignored' => __( 'File removed from ignored list.', 'vigilante' ), 'confirmClearIgnored' => __( 'Remove all files from the ignored list? They will appear in scan results again.', 'vigilante' ), 'ignoredCleared' => __( 'Ignored files list cleared. Page will reload...', 'vigilante' ), 'selectAll' => __( 'Select all', 'vigilante' ), 'bulkIgnoreSelected' => __( 'Ignore selected', 'vigilante' ), 'bulkUnignoreSelected'=> __( 'Stop ignoring selected', 'vigilante' ), 'bulkNoSelection' => __( 'Select at least one file first.', 'vigilante' ), 'bulkConfirmIgnore' => __( 'Ignore the selected files? They will be hidden from future scan results until you remove them from the ignored list.', 'vigilante' ), 'bulkConfirmUnignore' => __( 'Remove the selected files from the ignored list? They will appear in scan results again.', 'vigilante' ), 'bulkProcessing' => __( 'Processing...', 'vigilante' ), /* translators: %d: number of files selected for bulk action. */ 'bulkSelectedCount' => __( '%d selected', 'vigilante' ), 'allClear' => __( 'All files verified - no issues found!', 'vigilante' ), 'criticalConfigTitle' => __( 'Critical config files modified', 'vigilante' ), 'criticalConfigDesc' => __( 'These files are common targets for code injection. Review the changes and approve if they are legitimate. Vigilant\'s own blocks are excluded from this check.', 'vigilante' ), 'approve' => __( 'Approve', 'vigilante' ), 'approving' => __( 'Approving...', 'vigilante' ), 'approvalLockedNotice' => $this->critical_approval_notice(), 'criticalApproved' => __( 'Change approved. Next scan will use the current state as baseline.', 'vigilante' ), 'reviewChanges' => __( 'Review changes', 'vigilante' ), 'hideChanges' => __( 'Hide changes', 'vigilante' ), 'changes' => __( 'Changes', 'vigilante' ), 'diffUnavailable' => __( 'Diff not available for this file (baseline was created before diff tracking was added). Approve to enable diff on future changes.', 'vigilante' ), 'diffNetwork' => __( 'This file belongs to the whole network, so its line changes are only shown to network administrators, on the main site.', 'vigilante' ), 'diffRescan' => __( 'Run a new scan to see the line changes of this file.', 'vigilante' ), 'diffRedaction' => __( 'The line changes of this file are not shown because a value in it could not be hidden safely. The change itself is still detected.', 'vigilante' ), 'diffEmpty' => __( 'No line-level changes detected (may be whitespace or reordering).', 'vigilante' ), 'diffLines' => __( 'lines', 'vigilante' ), // Under Attack mode strings 'underAttackConfirmActivate' => __( 'Activate Under Attack mode? All visitors will see a verification page for the next 4 hours.', 'vigilante' ), 'underAttackConfirmDeactivate' => __( 'Deactivate Under Attack mode?', 'vigilante' ), 'underAttackActivating' => __( 'Activating...', 'vigilante' ), 'underAttackDeactivating' => __( 'Deactivating...', 'vigilante' ), // Database backup strings 'dbBackupDownloading' => __( 'Generating backup...', 'vigilante' ), 'dbBackupNoTables' => __( 'Please select at least one table.', 'vigilante' ), 'dbBackupSuccess' => __( 'Database backup downloaded successfully.', 'vigilante' ), // Firewall unblock 'confirmUnblockIp' => __( 'Unblock this IP from firewall rate limiting?', 'vigilante' ), // Database prefix strings 'dbPrefixConfirm' => __( 'This operation will change your database prefix. It is irreversible. Make sure you have a current database backup before proceeding.', 'vigilante' ), 'dbPrefixChanging' => __( 'Changing prefix...', 'vigilante' ), 'dbPrefixSuccess' => __( 'Database prefix changed successfully. The page will reload now.', 'vigilante' ), 'dbPrefixCheckbox' => __( 'You must confirm that you have a database backup.', 'vigilante' ), /* translators: 1: Hours, 2: Minutes */ 'underAttackRemaining' => __( '%1$dh %2$dm remaining', 'vigilante' ), 'underAttackLabel' => __( 'Under Attack', 'vigilante' ), 'standardLabel' => __( 'Standard', 'vigilante' ), 'maximumLabel' => __( 'Maximum Security', 'vigilante' ), 'deactivate' => __( 'Deactivate', 'vigilante' ), 'underAttackActivate' => __( 'Activate for 4 hours', 'vigilante' ), // Settings strings 'saveSettings' => __( 'Save Settings', 'vigilante' ), 'settingsResetDefaults' => __( 'Settings reset to defaults.', 'vigilante' ), 'confirmOverwrite' => __( 'This will overwrite your current settings.', 'vigilante' ), 'importFailed' => __( 'Could not import settings. Check the file and try again.', 'vigilante' ), /* translators: 1: tests passed, 2: total tests in this category */ 'testsCounter' => __( '%1$d/%2$d tests', 'vigilante' ), 'confirmResetAll' => __( 'This will reset ALL settings to defaults.', 'vigilante' ), 'couldNotDetermineSection' => __( 'Could not determine section.', 'vigilante' ), 'confirmResetSection' => __( 'Reset this section to default values? This cannot be undone.', 'vigilante' ), 'sectionResetDefaults' => __( 'Section reset to defaults.', 'vigilante' ), /* translators: %s: preset name */ 'confirmApplyPreset' => __( 'Apply the "%s" preset?', 'vigilante' ), // Scan strings 'scanFailed' => __( 'Scan failed', 'vigilante' ), /* translators: %s: error message */ 'scanError' => __( 'Scan error: %s', 'vigilante' ), 'runScanNow' => __( 'Run Scan Now', 'vigilante' ), 'confirmClearScan' => __( 'Are you sure you want to clear all scan results?', 'vigilante' ), 'clearing' => __( 'Clearing...', 'vigilante' ), 'scanResultsCleared' => __( 'Scan results cleared. Page will reload...', 'vigilante' ), 'failedClearResults' => __( 'Failed to clear results', 'vigilante' ), /* translators: %s: error message */ 'ajaxError' => __( 'AJAX Error: %s', 'vigilante' ), // Activity log popup strings 'logRequest' => __( 'Request', 'vigilante' ), 'logDate' => __( 'Date', 'vigilante' ), 'logMethod' => __( 'Method', 'vigilante' ), 'logType' => __( 'Type', 'vigilante' ), 'logAction' => __( 'Action', 'vigilante' ), 'logSeverity' => __( 'Severity', 'vigilante' ), 'logMessage' => __( 'Message', 'vigilante' ), 'logRequestUri' => __( 'Address', 'vigilante' ), 'logClient' => __( 'Client', 'vigilante' ), 'logUser' => __( 'User', 'vigilante' ), 'logIpAddress' => __( 'IP Address', 'vigilante' ), 'logUserAgent' => __( 'User Agent', 'vigilante' ), 'logIpLabel' => __( 'IP:', 'vigilante' ), 'logUaLabel' => __( 'UA:', 'vigilante' ), 'logWhitelist' => __( 'Whitelist', 'vigilante' ), 'logBlacklist' => __( 'Blacklist', 'vigilante' ), 'logInWhitelist' => __( 'In whitelist', 'vigilante' ), 'logInBlacklist' => __( 'In blacklist', 'vigilante' ), 'logAdded' => __( 'Added!', 'vigilante' ), 'logErrorAddingToList' => __( 'Error adding to list', 'vigilante' ), 'logRequestFailed' => __( 'Request failed', 'vigilante' ), // Activity log table strings 'noLogEntries' => __( 'No log entries found.', 'vigilante' ), 'view' => __( 'View', 'vigilante' ), 'confirmClearLogs' => __( 'This will delete all audit logs.', 'vigilante' ), // Export logs strings 'exporting' => __( 'Exporting...', 'vigilante' ), /* translators: %d: number of entries */ 'logsExported' => __( 'Logs exported (%d entries)', 'vigilante' ), 'noLogsToExport' => __( 'No logs to export', 'vigilante' ), 'exportFailed' => __( 'Export failed', 'vigilante' ), 'exportLogs' => __( 'Export Logs', 'vigilante' ), // Backup strings 'backupCreated' => __( 'Backup created successfully.', 'vigilante' ), 'createBackupNow' => __( 'Download Backup', 'vigilante' ), 'downloadBackup' => __( 'Download Backup (.zip)', 'vigilante' ), /* translators: %d: number of tables */ 'tablesCount' => __( '%d tables', 'vigilante' ), /* translators: 1: table count, 2: human-readable size */ 'dbTablesTotal' => __( '%1$d tables total (%2$s)', 'vigilante' ), /* translators: 1: selected count, 2: human-readable size */ 'dbTablesSelected' => __( '%1$d tables selected (%2$s)', 'vigilante' ), // Settings search strings 'searchNoResults' => __( 'No matching settings found.', 'vigilante' ), /* translators: %d: number of results that did not fit in the list. */ 'searchMoreResults' => __( '%d more results. Refine the search to see them.', 'vigilante' ), 'searchInTab' => __( 'in', 'vigilante' ), // Modules string /* translators: 1: enabled count, 2: total count */ 'modulesEnabled' => __( '%1$d / %2$d modules enabled', 'vigilante' ), // Activity log label maps for JS rendering 'eventTypeLabels' => array( 'login' => __( 'Login', 'vigilante' ), 'user' => __( 'User', 'vigilante' ), 'content' => __( 'Content', 'vigilante' ), 'plugin' => __( 'Plugin', 'vigilante' ), 'theme' => __( 'Theme', 'vigilante' ), 'settings' => __( 'Settings', 'vigilante' ), 'comment' => __( 'Comment', 'vigilante' ), 'media' => __( 'Media', 'vigilante' ), 'firewall' => __( 'Firewall', 'vigilante' ), 'file' => __( 'File', 'vigilante' ), 'security' => __( 'Security', 'vigilante' ), 'system' => __( 'System', 'vigilante' ), ), 'severityLabels' => array( 'info' => __( 'Info', 'vigilante' ), 'warning' => __( 'Warning', 'vigilante' ), 'critical' => __( 'Critical', 'vigilante' ), ), // Password reset strings 'noUsersFoundSearch' => __( 'No users found', 'vigilante' ), /* translators: %d: number of users */ 'confirmForceReset' => __( 'Force password reset for %d user(s)? A password reset email will be sent to each user.', 'vigilante' ), 'warningResettingSelf' => __( 'WARNING: You are including yourself. Your session will end and you will need to set a new password.', 'vigilante' ), 'processing' => __( 'Processing...', 'vigilante' ), 'anErrorOccurred' => __( 'An error occurred', 'vigilante' ), 'forceResetSelected' => __( 'Force Reset for Selected Users', 'vigilante' ), 'confirmForceResetAll' => __( 'This will force ALL users to reset their password. All users will receive a password reset email. Are you sure you want to continue?', 'vigilante' ), 'warningResettingSelfAll' => __( 'WARNING: You are including yourself. Your session will end immediately.', 'vigilante' ), 'forceResetAll' => __( 'Force Reset for ALL Users', 'vigilante' ), // Role-based password reset strings /* translators: %d: number of users */ 'confirmForceResetByRole' => __( 'Force password reset for %d user(s) with the selected roles? A password reset email will be sent to each user.', 'vigilante' ), 'noRolesSelected' => __( 'Please select at least one role.', 'vigilante' ), 'forceResetByRole' => __( 'Force Reset for Selected Roles', 'vigilante' ), // User approval strings 'confirmApprove' => __( 'Approve this user?', 'vigilante' ), 'approve' => __( 'Approve', 'vigilante' ), 'rejectReason' => __( 'Enter rejection reason (optional):', 'vigilante' ), 'reject' => __( 'Reject', 'vigilante' ), 'noPending' => __( 'No pending registrations.', 'vigilante' ), // Session management strings 'confirmRevoke' => __( 'Revoke this session?', 'vigilante' ), 'confirmRevokeAll' => __( 'Revoke all other sessions?', 'vigilante' ), 'revokeOthers' => __( 'Revoke All Other Sessions', 'vigilante' ), 'confirmRevokeAllUser' => __( 'Revoke ALL sessions for this user? They will be logged out everywhere.', 'vigilante' ), 'sessionsFor' => __( 'Sessions for:', 'vigilante' ), 'noSessions' => __( 'No active sessions', 'vigilante' ), 'revoke' => __( 'Revoke', 'vigilante' ), 'noUsers' => __( 'No users found', 'vigilante' ), // Time ago strings 'timeYear' => __( 'year', 'vigilante' ), 'timeYears' => __( 'years', 'vigilante' ), 'timeMonth' => __( 'month', 'vigilante' ), 'timeMonths' => __( 'months', 'vigilante' ), 'timeDay' => __( 'day', 'vigilante' ), 'timeDays' => __( 'days', 'vigilante' ), 'timeHour' => __( 'hour', 'vigilante' ), 'timeHours' => __( 'hours', 'vigilante' ), 'timeMinute' => __( 'minute', 'vigilante' ), 'timeMinutes' => __( 'minutes', 'vigilante' ), /* translators: %1$d: count, %2$s: time unit */ 'timeAgo' => __( '%1$d %2$s ago', 'vigilante' ), 'justNow' => __( 'Just now', 'vigilante' ), // Pagination strings /* translators: 1: first item number, 2: last item number, 3: total items */ 'paginationOf' => __( '%1$d–%2$d of %3$d', 'vigilante' ), 'paginationEmpty' => __( '0 items', 'vigilante' ), // Security Analyzer strings 'analyzerScanNow' => __( 'Scan now', 'vigilante' ), 'analyzerScanning' => __( 'Scanning…', 'vigilante' ), 'analyzerFastPhase' => __( 'Running fast checks…', 'vigilante' ), 'analyzerSlowPhase' => __( 'Running remote checks…', 'vigilante' ), 'analyzerScanComplete' => __( 'Security scan complete.', 'vigilante' ), 'analyzerScanFailed' => __( 'Security scan failed.', 'vigilante' ), 'analyzerShowDetails' => __( 'Show detailed breakdown', 'vigilante' ), 'analyzerHideDetails' => __( 'Hide detailed breakdown', 'vigilante' ), 'analyzerGoToSetting' => __( 'Go to setting', 'vigilante' ), 'analyzerNoData' => __( 'No data yet — run a scan to populate this category.', 'vigilante' ), 'analyzerJustNow' => __( 'just now', 'vigilante' ), 'analyzerAgo' => __( 'ago', 'vigilante' ), 'analyzerSettingsSaved' => __( 'Analyzer settings saved.', 'vigilante' ), 'analyzerLastScanJustNow' => __( 'Last scan just now', 'vigilante' ), 'analyzerQualityExcellent' => __( 'Excellent', 'vigilante' ), 'analyzerQualityGood' => __( 'Good', 'vigilante' ), 'analyzerQualityFair' => __( 'Fair', 'vigilante' ), 'analyzerQualityPoor' => __( 'Poor', 'vigilante' ), 'analyzerQualityCritical' => __( 'Critical', 'vigilante' ), 'analyzerPts' => __( 'pts', 'vigilante' ), 'analyzerLearnMore' => __( 'Learn more', 'vigilante' ), 'analyzerInfoAllClear' => __( 'All clear', 'vigilante' ), /* translators: %d: number of findings in an info-only category */ 'analyzerInfoFindings' => __( '%d findings', 'vigilante' ), ), ) ); // 2FA Admin assets wp_enqueue_style( 'vigilante-2fa-admin', VIGILANTE_ASSETS_URL . 'css/two-factor-admin.css', array( 'vigilante-admin' ), VIGILANTE_VERSION ); wp_enqueue_script( 'vigilante-2fa-admin', VIGILANTE_ASSETS_URL . 'js/two-factor-admin.js', array( 'jquery', 'vigilante-admin' ), VIGILANTE_VERSION, true ); } /** * Show admin notices */ public function show_admin_notices() { // Activation notice if ( get_transient( 'vigilante_activated' ) ) { ?>
0 ) { $ua_hours = floor( $ua_remaining / 3600 ); $ua_mins = floor( ( $ua_remaining % 3600 ) / 60 ); $dashboard_url = admin_url( 'admin.php?page=vigilante' ); ?> $server_key ) { if ( empty( $_SERVER[ $server_key ] ) ) { continue; } $candidate = sanitize_text_field( wp_unslash( $_SERVER[ $server_key ] ) ); if ( false !== strpos( $candidate, ',' ) ) { $parts = explode( ',', $candidate ); $candidate = trim( $parts[0] ); } if ( ! filter_var( $candidate, FILTER_VALIDATE_IP ) || $candidate === $remote ) { continue; } // Loopback forwarded IP = local development, not a real proxy. if ( '::1' === $candidate || 0 === strpos( $candidate, '127.' ) ) { continue; } $detected = $proxy_label; break; } if ( '' !== $detected ) { $firewall_url = admin_url( 'admin.php?page=vigilante&tab=firewall' ); $detail_message = sprintf( /* translators: %s: detected forwarded header name wrapped in a code tag. */ esc_html__( 'Requests are arriving with a %s header, but visitor IP detection is set to direct connection. The firewall is reading the proxy address instead of the real visitor IP, which affects the IP lists and rate limiting.', 'vigilante' ), '' . esc_html( strtoupper( $detected ) ) . ''
);
?>
current_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'dashboard';
if ( ! array_key_exists( $this->current_tab, $this->tabs ) ) {
$this->current_tab = 'dashboard';
}
?>
' . esc_html( $detail ) . '
'; } if ( $fix_link && in_array( $state, array( 'fail', 'warn' ), true ) ) { $html .= '' . esc_html__( 'Go to setting', 'vigilante' ) . ''; } elseif ( $fix_link && 'info' === $state ) { // Info rows (e.g. DNSBL lookups) get an external "Learn more" link instead. $is_external = 0 === strpos( $fix_link, 'http' ); $html .= '' . esc_html__( 'Learn more', 'vigilante' ) . ''; } $html .= '
|
';
printf(
/* translators: %d: Maximum lockout duration in hours */
esc_html__( 'Progressive lockout enabled (max: %d hours).', 'vigilante' ),
absint( ceil( $max_lockout / 3600 ) )
);
}
if ( $two_factor_enabled ) {
echo ' '; esc_html_e( 'Failed 2FA codes also count toward the lockout limit.', 'vigilante' ); } ?> |
|||||||||
|
|
$role_data ) :
$user_count = count( get_users( array( 'role' => $role_slug, 'fields' => 'ID' ) ) );
?>
|
|
|
display_name . ' (' . $user->user_email . ')' ); ?>
|
|
|
|
|
|
|
|
|
|
|
| ID, 32 ); ?> user_login ); ?> | user_email ); ?> | user_registered ); } ?> |
|
| created_at ); ?> | - | event_message ); ?> | user_login ?? '-' ); ?> | ip_address ); ?> |
|||
0 ) { printf( /* translators: 1: date and time of last scan, 2: formatted file count */ esc_html__( 'Last scan: %1$s (%2$s files scanned)', 'vigilante' ), esc_html( wp_date( $datetime_format, $last_scan ) ), esc_html( number_format_i18n( $scanned_total ) ) ); } else { printf( /* translators: %s: date and time of last scan */ esc_html__( 'Last scan: %s', 'vigilante' ), esc_html( wp_date( $datetime_format, $last_scan ) ) ); } if ( $closed_last_check > 0 && $closed_last_check !== (int) $last_scan ) { echo ' · '; printf( /* translators: %s: date and time of last closed plugins check */ esc_html__( 'Closed plugins last checked: %s', 'vigilante' ), esc_html( wp_date( $datetime_format, $closed_last_check ) ) ); } ?>
0 ) : ?>
|
|
|
+
-
|
critical_approval_locked() ) : ?> critical_approval_notice() ); ?> |
|
|
|