PluginProbe
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… / 2.11.8
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… v2.11.8
3.0.0 2.11.12 2.11.11 2.11.10 2.11.9 2.11.7 2.11.8 2.11.6 2.11.5 2.11.4 2.11.3 2.11.1 2.11.2 2.11.0 2.10.5 2.10.4 2.10.3 2.10.2 2.10.1 2.10.0 2.9.9 2.9.8 2.9.6 2.9.7 2.9.5 All 88 releases
← All changes | admin/class-admin.php +1236 -236 2.9.52.11.8 View file →
@@ -27,8 +27,9 @@
27 27
28 28 use Vigilante_Admin_Ajax;
29 29 use Vigilante_Admin_Analyzer_Ajax;
30 30 use Vigilante_Admin_Audit_Alerts_Ajax;
31 + use Vigilante_Admin_Recovery_Ajax;
31 32
32 33 /**
33 34 * Settings instance
34 35 *
@@ -182,8 +183,13 @@
182 183 add_action( 'wp_ajax_vigilante_analyzer_history', array( $this, 'ajax_analyzer_history' ) );
183 184 add_action( 'wp_ajax_vigilante_analyzer_dismiss_notice', array( $this, 'ajax_analyzer_dismiss_notice' ) );
184 185 add_action( 'wp_ajax_vigilante_analyzer_save_settings', array( $this, 'ajax_analyzer_save_settings' ) );
185 186
187 + // Security Headers settings recovery (2.10.0)
188 + add_action( 'wp_ajax_vigilante_headers_recovery_restore', array( $this, 'ajax_headers_recovery_restore' ) );
189 + add_action( 'wp_ajax_vigilante_headers_recovery_undo', array( $this, 'ajax_headers_recovery_undo' ) );
190 + add_action( 'wp_ajax_vigilante_headers_recovery_dismiss', array( $this, 'ajax_headers_recovery_dismiss' ) );
191 +
186 192 // Shared "Send test email" handler — Notification settings, File Integrity, Audit Alerts (v2.8.0)
187 193 add_action( 'wp_ajax_vigilante_send_test_email', array( $this, 'ajax_send_test_email' ) );
188 194
189 195 // Run migrations on admin load
@@ -267,9 +273,29 @@
267 273 if ( ! class_exists( 'Vigilante_File_Integrity' ) ) {
268 274 require_once VIGILANTE_INCLUDES_DIR . 'class-file-integrity.php';
269 275 }
270 276 $fi = new Vigilante_File_Integrity( $this->settings, $this->database, $this->activity_log );
271 - $fi->regenerate_all_baselines();
277 +
278 + /*
279 + * Only when there is nothing on record. This migration exists to
280 + * create the baseline that did not exist, never to discard the one
281 + * the owner approved: rebuilding it from the files takes whatever
282 + * is on disk right now as approved, so a wp-config.php modified and
283 + * awaiting review would be blessed in silence.
284 + *
285 + * And this is not theory. vigilante_db_version is written on two
286 + * different scales into the same option: this file counts in plugin
287 + * versions (2.11.0) and Vigilante_Database counts in schema
288 + * versions, currently 1.4.0 (class-database.php:322 and :380). For
289 + * version_compare, 1.4.0 is LOWER than 1.14.0, so any site whose
290 + * option was last written by the schema runs this migration again.
291 + * Measured on the Multisite install on 10 sep 2026: one of the three
292 + * sites was sitting on 1.4.0.
293 + */
294 + if ( ! $fi->get_critical_files_baseline() ) {
295 + $fi->regenerate_all_baselines();
296 + }
297 +
272 298 update_option( 'vigilante_db_version', '1.14.0' );
273 299 }
274 300
275 301 // 2.0.0: Move hide_server_signature and remove_fingerprinting_headers
@@ -343,8 +369,103 @@
343 369 }
344 370
345 371 update_option( 'vigilante_db_version', '2.9.3' );
346 372 }
373 +
374 + /*
375 + * 2.9.8: the mixed content handling changes shape. "Upgrade Insecure
376 + * Requests" becomes a setting of its own, and Fix Mixed Content ships
377 + * off, where before it shipped on and carried the directive with it.
378 + * Both have to be written down for sites that are updating, so their
379 + * pages keep loading exactly what they loaded yesterday.
380 + *
381 + * Read the RAW stored options, not get_section(): that one merges the
382 + * defaults, so a site that never stored the key would be read with the
383 + * new default and silently lose the behaviour it had. Absent means the
384 + * site was running on the old default, which was on.
385 + */
386 + if ( version_compare( $db_version, '2.9.8', '<' ) ) {
387 + $raw = get_option( Vigilante_Settings::OPTION_NAME, array() );
388 + $stored = ( is_array( $raw ) && isset( $raw['security_headers'] ) && is_array( $raw['security_headers'] ) ) ? $raw['security_headers'] : array();
389 + $had_fix = array_key_exists( 'fix_mixed_content', $stored ) ? ! empty( $stored['fix_mixed_content'] ) : true;
390 +
391 + /*
392 + * Merge, never replace. update_section() overwrites the whole
393 + * section, so passing just these two keys wiped every other header
394 + * setting the site had stored (HSTS, CSP, cross-origin policies,
395 + * the HTTPS switches, Server Identity) and left the screen showing
396 + * factory defaults while the .htaccess kept serving the old values.
397 + */
398 + $this->settings->update_section(
399 + 'security_headers',
400 + array_merge(
401 + $stored,
402 + array(
403 + 'fix_mixed_content' => $had_fix,
404 + 'upgrade_insecure_requests' => $had_fix,
405 + )
406 + )
407 + );
408 +
409 + update_option( 'vigilante_db_version', '2.9.8' );
410 + }
411 +
412 + /*
413 + * 2.9.9: drop the settings that no code has read for versions.
414 + *
415 + * They were carried in the defaults and therefore written into every
416 + * saved configuration, they show up in an exported configuration, and
417 + * anyone reading them assumes a feature exists behind them. Removing
418 + * them from the defaults is not enough: the stored copies survive, so
419 + * they are swept here too. Nothing reads them, so nothing changes.
420 + */
421 + if ( version_compare( $db_version, '2.9.9', '<' ) ) {
422 + $raw = get_option( Vigilante_Settings::OPTION_NAME, array() );
423 + $dead = array(
424 + 'firewall' => array( 'country_blocking', 'protected_file_extensions' ),
425 + 'file_integrity' => array( 'suspicious_patterns' ),
426 + 'backup' => array( 'auto_backup', 'backup_before_update' ),
427 + 'advanced' => array( 'block_author_archives', 'disable_embeds', 'uninstall_cleanup', 'debug_mode' ),
428 + );
429 +
430 + $changed = false;
431 + foreach ( $dead as $section => $keys ) {
432 + if ( ! isset( $raw[ $section ] ) || ! is_array( $raw[ $section ] ) ) {
433 + continue;
434 + }
435 + foreach ( $keys as $key ) {
436 + if ( array_key_exists( $key, $raw[ $section ] ) ) {
437 + unset( $raw[ $section ][ $key ] );
438 + $changed = true;
439 + }
440 + }
441 + }
442 +
443 + if ( $changed ) {
444 + update_option( Vigilante_Settings::OPTION_NAME, $raw );
445 + }
446 +
447 + update_option( 'vigilante_db_version', '2.9.9' );
448 + }
449 +
450 + /*
451 + * 2.11.0: security release (audit of 28 Aug 2026). Runs here and not
452 + * from Vigilante_Database::needs_update(): this option is shared with
453 + * that class, and on any updated site it already holds a plugin version
454 + * (2.9.9 or later), so a bump of DB_VERSION would never fire.
455 + * create_tables() widens the email code column through dbDelta (varchar
456 + * 6 to 64, the code is stored hashed since 2.11.0) and purge_for_2_11_0()
457 + * does what dbDelta cannot: it empties the trusted devices, which were
458 + * identified by User-Agent until now (S1), and the pending email codes,
459 + * stored in clear until now (S11). Every remembered device asks for the
460 + * second factor once more after this update, and the changelog says so.
461 + */
462 + if ( version_compare( $db_version, '2.11.0', '<' ) ) {
463 + $this->database->create_tables();
464 + $this->database->purge_for_2_11_0();
465 +
466 + update_option( 'vigilante_db_version', '2.11.0' );
467 + }
347 468 }
348 469
349 470 /**
350 471 * Migration: Remove orphaned email fields from saved options
@@ -1104,96 +1225,215 @@
1104 1225 );
1105 1226 }
1106 1227
1107 1228 /**
1108 - * Build the settings search index
1229 + * Index behind the settings search box.
1109 1230 *
1110 - * Flat list of searchable entries consumed by the client-side search.
1111 - * Each entry contains tab + section metadata, a translated label and an
1112 - * English fallback so locales with partial translation still match.
1231 + * Each entry points at one settings row. The search matches on the label,
1232 + * on its English original and on 'keywords', which are extra terms someone
1233 + * might type instead of the label itself.
1113 1234 *
1235 + * Those keywords are wrapped in _x() with the context "settings search
1236 + * keywords" so every locale can supply its own: the source strings are in
1237 + * English, and a Spanish user typing "contrasena" or a German one typing
1238 + * "Kennwort" only reaches the password settings if that locale translated
1239 + * them. Translators can add, drop or replace terms freely, one per space;
1240 + * they are never displayed, only matched against what the user types.
1241 + *
1242 + * The list is maintained by hand, so a new settings row needs an entry here
1243 + * or it cannot be found. It had drifted to 68 of 131 rows before 2.9.7.
1244 + *
1114 1245 * @return array
1115 1246 */
1116 1247 private function get_search_index() {
1117 1248 return array(
1118 1249 // Firewall - Main
1119 - 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' => 'bots robots malos bloquear bad block crawlers arañas scrapers' ),
1120 - 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' => 'malicioso maliciosas peticiones ataques sqli xss rfi lfi ataque exploit injection inyección' ),
1121 - 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' => 'limitar tasa velocidad throttle peticiones minuto abuso flood' ),
1122 - 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' => 'fuerza bruta brute force contraseñas ataque login diccionario' ),
1123 - 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' => 'lista blanca permitida permitidas whitelist allowlist ip direcciones permitir' ),
1124 - 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' => 'lista negra bloqueada bloqueadas blacklist blocklist denylist ip direcciones bloquear' ),
1125 - 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' => 'ua user agent agente usuario navegador lista blanca permitida whitelist allowlist' ),
1126 - 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' => 'ua user agent agente usuario navegador lista negra bloqueada blacklist blocklist denylist' ),
1250 + 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' ) ),
1251 + 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' ) ),
1252 + 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' ) ),
1253 + 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' ) ),
1254 + 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' ) ),
1255 + 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' ) ),
1256 + 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' ) ),
1257 + 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' ) ),
1127 1258 // Firewall - Server Protection
1128 - 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' => 'directorio navegación listado listing indexing indexado carpetas' ),
1129 - 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' => 'proteger wp-config configuración config archivo' ),
1130 - 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' => 'proteger wp-cron cron tareas programadas scheduled tasks bloquear block dos abuse spam htaccess' ),
1131 - 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' => 'proteger wp-includes includes core núcleo archivos' ),
1132 - 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' => 'php uploads subidas archivos bloquear ejecución' ),
1133 - 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' => 'sensibles sensitive files archivos readme license htaccess log' ),
1134 - 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' => 'métodos http limit limitar trace options put delete verbs verbos' ),
1259 + 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' ) ),
1260 + 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' ) ),
1261 + 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' ) ),
1262 + 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' ) ),
1263 + 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' ) ),
1264 + 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' ) ),
1265 + 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' ) ),
1135 1266 // Security Headers
1136 - 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' => 'xframe clickjacking iframe cabeceras headers' ),
1137 - 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' => 'mime sniffing nosniff content type cabeceras headers' ),
1138 - 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' => 'referer referrer política privacidad cabeceras headers' ),
1139 - array( 'tab' => 'headers', 'tab_label' => __( 'Security Headers', 'vigilante' ), 'section' => __( 'HSTS', 'vigilante' ), 'anchor' => 'vigilante-section-headers-main', 'label' => __( 'HSTS', 'vigilante' ), 'label_en' => 'HSTS', 'keywords' => 'strict transport security ssl tls https forzar cabeceras headers' ),
1140 - 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' => 'csp política seguridad contenido xss scripts inline eval cabeceras headers' ),
1141 - 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' => 'firma servidor server signature identidad apache nginx ocultar hide fingerprint protección servidor' ),
1142 - 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' => 'fingerprint huella identificación cabeceras headers x-powered-by server ocultar eliminar protección servidor' ),
1267 + 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' ) ),
1268 + 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' ) ),
1269 + 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' ) ),
1270 + 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' ) ),
1271 + 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' ) ),
1272 + 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' ) ),
1273 + 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' ) ),
1274 + // Security Headers - Cross-Origin Policies
1275 + 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' ) ),
1276 + 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' ) ),
1277 + 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' ) ),
1143 1278 // Login Security
1144 - 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' => 'url personalizada login acceso entrar wp-login wp-admin slug ocultar esconder' ),
1145 - 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' => '2fa doble factor autenticación totp google authenticator mfa' ),
1146 - array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( '2FA', 'vigilante' ), 'label_en' => '2FA', 'keywords' => '2fa doble factor autenticación totp google authenticator mfa two factor' ),
1147 - 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' => 'intentos fallidos login acceso failed attempts bloqueo bloqueos contraseña errónea' ),
1148 - array( 'tab' => 'login', 'tab_label' => __( 'Login Security', 'vigilante' ), 'section' => __( 'Login Protection', 'vigilante' ), 'anchor' => 'vigilante-section-login-main', 'label' => __( 'Lockout', 'vigilante' ), 'label_en' => 'Lockout', 'keywords' => 'bloqueo bloqueado lockout baneo ban duración login' ),
1279 + 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' ) ),
1280 + 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' ) ),
1281 + 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' ) ),
1282 + 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' ) ),
1283 + 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' ) ),
1149 1284 // REST API
1150 - 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' => 'modo acceso rest api público privado autenticado' ),
1151 - 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' => 'enumeración usuarios users block bloquear autores author slug ?author' ),
1152 - 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' => 'jsonp desactivar deshabilitar disable callback' ),
1285 + 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' ) ),
1286 + 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' ) ),
1287 + 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' ) ),
1153 1288 // User Security
1154 - 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' => 'nombre usuario username admin reservado prohibido protección' ),
1155 - 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' => 'fortaleza fuerza contraseña password débil fuerte complejidad requisitos' ),
1156 - 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' => 'monitorización administradores admin supervisión alertas cambios' ),
1157 - 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' => 'aprobación registro registration moderación nuevos usuarios signup' ),
1158 - 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' => 'sesiones limits límite concurrentes sessions simultáneas' ),
1159 - 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' => 'expiración caducidad contraseña password cambiar renovar rotación' ),
1160 - 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' => 'verificación correo email confirmación validación' ),
1289 + 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' ) ),
1290 + 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' ) ),
1291 + 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' ) ),
1292 + 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' ) ),
1293 + 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' ) ),
1294 + 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' ) ),
1295 + 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' ) ),
1161 1296 // WP Hardening
1162 - 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' => 'base datos database db mysql fortalecer hardening endurecer' ),
1163 - 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' => 'prefijo base datos database db mysql tabla tablas wp_ cambiar renombrar' ),
1164 - 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' => 'desactivar deshabilitar disable edición editor archivos file edit disallow' ),
1165 - 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' => 'desactivar deshabilitar disable instalación plugins temas themes install disallow' ),
1166 - 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' => 'forzar ssl tls https admin administración certificado' ),
1167 - 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' => 'desactivar deshabilitar disable wp cron tareas programadas scheduled real server crontab pseudo' ),
1168 - 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' => 'comentarios comments spam protección honeypot autores url' ),
1169 - 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' => 'limpieza cabeceras headers meta tags wordpress generator rsd wlwmanifest' ),
1170 - 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' => 'eliminar quitar versión wordpress wp generator meta ocultar hide' ),
1171 - 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' => 'eliminar quitar versión wordpress wp ver query string assets recursos urls scripts styles css js cache busting' ),
1172 - array( 'tab' => 'wp-hardening', 'tab_label' => __( 'WP Hardening', 'vigilante' ), 'section' => __( 'Header Cleanup', 'vigilante' ), 'anchor' => 'vigilante-section-hardening-headers', 'label' => __( 'Disable XML-RPC', 'vigilante' ), 'label_en' => 'Disable XML-RPC', 'keywords' => 'xmlrpc xml-rpc desactivar deshabilitar disable pingback trackback' ),
1173 - 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' => 'rss feed sindicación feeds ajustes configuración' ),
1297 + 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' ) ),
1298 + 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' ) ),
1299 + 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' ) ),
1300 + 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' ) ),
1301 + 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' ) ),
1302 + 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' ) ),
1303 + 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' ) ),
1304 + 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' ) ),
1305 + 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' ) ),
1306 + 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' ) ),
1307 + 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' ) ),
1308 + 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' ) ),
1174 1309 // File Integrity
1175 - 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' => 'integridad archivos monitorización supervisión hash checksum malware cambios' ),
1176 - 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' => 'escaneo escáner programación planificación horario frecuencia cron' ),
1177 - 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' => 'alerta instantánea inmediata notificación aviso email tiempo real' ),
1178 - 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' => 'ignorados ignorar excluir exclusiones archivos ignored exclude' ),
1310 + 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' ) ),
1311 + 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' ) ),
1312 + 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' ) ),
1313 + 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' ) ),
1179 1314 // Security Audit
1180 - 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' => 'retención días log registro conservación purga auditoría' ),
1181 - 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' => 'eventos log registro auditoría registrar capturar' ),
1182 - 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' => 'opciones seguimiento rastreo cambios ajustes options tracking' ),
1183 - 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' => 'exclusiones excluir ignorar usuarios roles ip filtros' ),
1184 - 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' => 'alertas avisos aviso notificaciones email correo mail auditoría audit seguridad warning critical destinatarios test prueba' ),
1185 - 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' => 'alertas inmediatas email correo mail aviso severidad crítico critical warning evento auditoría inmediato' ),
1186 - 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' => 'alertas umbral pico ráfaga email correo mail aviso categoría ventana threshold auditoría cortafuegos login' ),
1187 - 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' => 'actividad reciente log registro eventos últimos historial' ),
1315 + 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' ) ),
1316 + 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' ) ),
1317 + 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' ) ),
1318 + 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' ) ),
1319 + 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' ) ),
1320 + 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' ) ),
1321 + 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' ) ),
1322 + 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' ) ),
1188 1323 // Settings & Tools
1189 - 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' => 'notificaciones ajustes settings email correo avisos alertas' ),
1190 - 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' => 'destinatarios adicionales correo email cc copia recipients' ),
1191 - 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' => 'exportar export ajustes configuración settings json' ),
1192 - 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' => 'importar import ajustes configuración settings json' ),
1193 - 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' => 'restablecer resetear reset defaults predeterminados valores originales fábrica' ),
1194 - 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' => 'copia seguridad backup crear generar respaldo' ),
1195 - 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' => 'base datos database db mysql copia seguridad backup respaldo tablas exportar' ),
1324 + 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' ) ),
1325 + 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' ) ),
1326 + 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' ) ),
1327 + 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' ) ),
1328 + 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' ) ),
1329 + 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' ) ),
1330 + 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' ) ),
1331 + // Entradas anadidas en la 2.9.7 tras comprobar que el indice cubria 68 de
1332 + // las 131 filas de ajustes: buscar XML-RPC, por ejemplo, no devolvia nada.
1333 + // El indice se mantiene a mano, asi que al anadir una fila de ajustes hay
1334 + // que anadirla tambien aqui.
1335 + 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' ) ),
1336 + 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' ) ),
1337 + 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' ) ),
1338 + 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' ) ),
1339 + 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' ) ),
1340 + 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' ) ),
1341 + 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' ) ),
1342 + 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' ) ),
1343 + 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' ) ),
1344 + 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' ) ),
1345 + 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' ) ),
1346 + 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' ) ),
1347 + 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' ) ),
1348 + 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' ) ),
1349 + 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' ) ),
1350 + 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' ) ),
1351 + 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' ) ),
1352 + 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' ) ),
1353 + 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' ) ),
1354 + 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' ) ),
1355 + 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' ) ),
1356 + 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' ) ),
1357 + 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' ) ),
1358 + 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' ) ),
1359 + 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' ) ),
1360 + 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' ) ),
1361 + 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' ) ),
1362 + 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' ) ),
1363 + 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' ) ),
1364 + 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' ) ),
1365 + 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' ) ),
1366 + 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' ) ),
1367 + 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' ) ),
1368 + 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' ) ),
1369 + 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' ) ),
1370 + 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' ) ),
1371 + 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' ) ),
1372 + 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' ) ),
1373 + 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' ) ),
1374 + 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' ) ),
1375 + 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' ) ),
1376 + 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' ) ),
1377 + 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' ) ),
1378 + 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' ) ),
1379 + 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' ) ),
1380 + 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' ) ),
1381 + 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' ) ),
1382 + 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' ) ),
1383 + 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' ) ),
1384 + 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' ) ),
1385 + 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' ) ),
1386 + 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' ) ),
1387 + 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' ) ),
1388 + 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' ) ),
1389 + 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' ) ),
1390 + 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' ) ),
1391 + 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' ) ),
1392 + 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' ) ),
1393 + 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' ) ),
1394 + 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' ) ),
1395 + 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' ) ),
1396 + 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' ) ),
1397 + 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' ) ),
1398 + 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' ) ),
1399 + 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' ) ),
1400 + 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' ) ),
1401 + 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' ) ),
1402 + 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' ) ),
1403 + 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' ) ),
1404 + 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' ) ),
1405 + 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' ) ),
1406 + 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' ) ),
1407 + 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' ) ),
1408 + 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' ) ),
1409 + 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' ) ),
1410 + 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' ) ),
1411 + 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' ) ),
1412 + 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' ) ),
1413 + 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' ) ),
1414 + 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' ) ),
1415 + 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' ) ),
1416 + 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' ) ),
1417 + 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' ) ),
1418 + 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' ) ),
1419 + 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' ) ),
1420 + 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' ) ),
1421 + 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' ) ),
1422 + 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' ) ),
1423 + 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' ) ),
1424 + 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' ) ),
1425 + 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' ) ),
1426 + 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' ) ),
1427 + 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' ) ),
1428 + 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' ) ),
1429 + 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' ) ),
1430 + 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' ) ),
1431 + 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' ) ),
1432 + 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' ) ),
1433 + 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' ) ),
1434 + 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' ) ),
1435 + 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' ) ),
1196 1436 );
1197 1437 }
1198 1438
1199 1439 /**
@@ -1228,8 +1468,11 @@
1228 1468 'currentUserId' => get_current_user_id(),
1229 1469 'logoutUrl' => wp_logout_url( wp_login_url() ),
1230 1470 'adminUrl' => admin_url( 'admin.php?page=vigilante' ),
1231 1471 'searchIndex' => $this->get_search_index(),
1472 + // The scan repaints this table from JavaScript, so the same gate
1473 + // has to travel with it or half the screen keeps the dead button.
1474 + 'approvalLocked' => $this->critical_approval_locked(),
1232 1475 'underAttack' => array(
1233 1476 'active' => ( new Vigilante_Under_Attack( $this->settings, $this->activity_log ) )->is_active(),
1234 1477 'remaining' => ( new Vigilante_Under_Attack( $this->settings, $this->activity_log ) )->get_remaining_time(),
1235 1478 ),
@@ -1296,13 +1539,17 @@
1296 1539 'criticalConfigTitle' => __( 'Critical config files modified', 'vigilante' ),
1297 1540 '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' ),
1298 1541 'approve' => __( 'Approve', 'vigilante' ),
1299 1542 'approving' => __( 'Approving...', 'vigilante' ),
1543 + 'approvalLockedNotice' => $this->critical_approval_notice(),
1300 1544 'criticalApproved' => __( 'Change approved. Next scan will use the current state as baseline.', 'vigilante' ),
1301 1545 'reviewChanges' => __( 'Review changes', 'vigilante' ),
1302 1546 'hideChanges' => __( 'Hide changes', 'vigilante' ),
1303 1547 'changes' => __( 'Changes', 'vigilante' ),
1304 1548 'diffUnavailable' => __( 'Diff not available for this file (baseline was created before diff tracking was added). Approve to enable diff on future changes.', 'vigilante' ),
1549 + 'diffNetwork' => __( 'This file belongs to the whole network, so its line changes are only shown to network administrators, on the main site.', 'vigilante' ),
1550 + 'diffRescan' => __( 'Run a new scan to see the line changes of this file.', 'vigilante' ),
1551 + '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' ),
1305 1552 'diffEmpty' => __( 'No line-level changes detected (may be whitespace or reordering).', 'vigilante' ),
1306 1553 'diffLines' => __( 'lines', 'vigilante' ),
1307 1554 // Under Attack mode strings
1308 1555 'underAttackConfirmActivate' => __( 'Activate Under Attack mode? All visitors will see a verification page for the next 4 hours.', 'vigilante' ),
@@ -1358,8 +1605,9 @@
1358 1605 'logType' => __( 'Type', 'vigilante' ),
1359 1606 'logAction' => __( 'Action', 'vigilante' ),
1360 1607 'logSeverity' => __( 'Severity', 'vigilante' ),
1361 1608 'logMessage' => __( 'Message', 'vigilante' ),
1609 + 'logRequestUri' => __( 'Address', 'vigilante' ),
1362 1610 'logClient' => __( 'Client', 'vigilante' ),
1363 1611 'logUser' => __( 'User', 'vigilante' ),
1364 1612 'logIpAddress' => __( 'IP Address', 'vigilante' ),
1365 1613 'logUserAgent' => __( 'User Agent', 'vigilante' ),
@@ -1394,8 +1642,10 @@
1394 1642 /* translators: 1: selected count, 2: human-readable size */
1395 1643 'dbTablesSelected' => __( '%1$d tables selected (%2$s)', 'vigilante' ),
1396 1644 // Settings search strings
1397 1645 'searchNoResults' => __( 'No matching settings found.', 'vigilante' ),
1646 + /* translators: %d: number of results that did not fit in the list. */
1647 + 'searchMoreResults' => __( '%d more results. Refine the search to see them.', 'vigilante' ),
1398 1648 'searchInTab' => __( 'in', 'vigilante' ),
1399 1649 // Modules string
1400 1650 /* translators: 1: enabled count, 2: total count */
1401 1651 'modulesEnabled' => __( '%1$d / %2$d modules enabled', 'vigilante' ),
@@ -1562,8 +1812,21 @@
1562 1812 </p>
1563 1813 <p>
1564 1814 <em><?php esc_html_e( 'Vigilant has applied the Maximum preset plus extra hardening on top of your previous configuration. Any changes you make to Vigilant settings while this mode is active will be reverted when it ends.', 'vigilante' ); ?></em>
1565 1815 </p>
1816 + <?php
1817 + // The cache-bypass rules could not be written (a host where
1818 + // WordPress cannot write files by itself, a held lock, a
1819 + // failed read-back): show them, so they can be added by hand.
1820 + $ua_instance = new Vigilante_Under_Attack( $this->settings, $this->activity_log );
1821 + if ( $ua_instance->cache_rules_missing() ) :
1822 + ?>
1823 + <p>
1824 + <strong><?php esc_html_e( 'The cache-bypass rules could not be written to your .htaccess.', 'vigilante' ); ?></strong>
1825 + <?php esc_html_e( 'Without them a page cache may keep serving stored pages during the attack. Add this block at the top of the .htaccess in your site root (the activity log records why it was not written):', 'vigilante' ); ?>
1826 + </p>
1827 + <textarea readonly rows="9" class="large-text code" onclick="this.select();"><?php echo esc_textarea( Vigilante_Under_Attack::get_cache_bypass_block() ); ?></textarea>
1828 + <?php endif; ?>
1566 1829 </div>
1567 1830 <?php
1568 1831 }
1569 1832 }
@@ -1665,9 +1928,9 @@
1665 1928 </h1>
1666 1929 <div class="vigilante-search-wrapper">
1667 1930 <div class="vigilante-search-input-wrap">
1668 1931 <span class="vigilante-search-icon dashicons dashicons-search" aria-hidden="true"></span>
1669 - <input type="search" id="vigilante-settings-search" class="vigilante-settings-search" placeholder="<?php esc_attr_e( 'Search settings…', 'vigilante' ); ?>" autocomplete="off">
1932 + <input type="search" id="vigilante-settings-search" class="vigilante-settings-search" aria-label="<?php esc_attr_e( 'Search settings', 'vigilante' ); ?>" placeholder="<?php esc_attr_e( 'Search settings…', 'vigilante' ); ?>" autocomplete="off">
1670 1933 <span class="vigilante-search-shortcut" aria-hidden="true">/</span>
1671 1934 </div>
1672 1935 <div id="vigilante-settings-search-results" class="vigilante-search-results" hidden role="listbox"></div>
1673 1936 </div>
@@ -1749,8 +2012,204 @@
1749 2012 <?php
1750 2013 }
1751 2014
1752 2015 /**
2016 + * Values to display for a section that this site does not control
2017 + *
2018 + * On a subsite the stored options are its own copy, which nothing acts on:
2019 + * wp-config.php and .htaccess are written from the main site. Painting the
2020 + * local copy describes a configuration that is not running, so a subsite
2021 + * admin sees a box ticked here and the constant absent from the file, or the
2022 + * other way round. Read the main site's values instead, which are the ones in
2023 + * force, and fall back to the local ones if they cannot be read.
2024 + *
2025 + * @since 2.9.8
2026 + *
2027 + * @param string $section Settings section.
2028 + * @return array
2029 + */
2030 + private function get_section_for_display( $section ) {
2031 + $local = $this->settings->get_section( $section );
2032 +
2033 + if ( ! $this->shared_files_locked() ) {
2034 + return $local;
2035 + }
2036 +
2037 + // shared_files_locked() is only true on multisite, where get_blog_option() exists.
2038 + $main = get_blog_option( get_main_site_id(), Vigilante_Settings::OPTION_NAME, array() );
2039 +
2040 + if ( ! is_array( $main ) || empty( $main[ $section ] ) || ! is_array( $main[ $section ] ) ) {
2041 + return $local;
2042 + }
2043 +
2044 + return wp_parse_args( $main[ $section ], $local );
2045 + }
2046 +
2047 + /**
2048 + * Whether the sections that write wp-config.php and .htaccess are read-only here
2049 + *
2050 + * True on a network when this is not the main site, or the user is not a
2051 + * network administrator. See Vigilante_Settings::can_write_shared_files().
2052 + *
2053 + * @since 2.9.8
2054 + *
2055 + * @return bool
2056 + */
2057 + private function shared_files_locked() {
2058 + return ! Vigilante_Settings::can_write_shared_files();
2059 + }
2060 +
2061 + /**
2062 + * Whether this is the main site and the user cannot change what it builds the shared files from
2063 + *
2064 + * See Vigilante_Settings::get_main_site_file_settings(). On a subsite those
2065 + * settings only act on that site, so they are never locked there.
2066 + *
2067 + * @since 2.11.6
2068 + *
2069 + * @return bool
2070 + */
2071 + private function main_site_files_locked() {
2072 + return $this->shared_files_locked() && Vigilante_Settings::owns_shared_files();
2073 + }
2074 +
2075 + /**
2076 + * Sentence added to a bulk change when some settings were left as they were
2077 + *
2078 + * Importing a file, applying a preset and restoring the defaults touch every
2079 + * section at once, so the user is told that the shared file settings did
2080 + * not move.
2081 + *
2082 + * @since 2.11.6
2083 + *
2084 + * @return string Empty when the user can change every setting.
2085 + */
2086 + private function locked_file_settings_message() {
2087 + if ( ! Vigilante_Settings::get_locked_file_settings() ) {
2088 + return '';
2089 + }
2090 +
2091 + return ' ' . __( 'The settings that end up in wp-config.php or .htaccess were left as they were.', 'vigilante' ) . ' ' . Vigilante_Settings::get_shared_files_notice();
2092 + }
2093 +
2094 + /**
2095 + * Print the shared-files notice for a section that cannot be edited here
2096 + *
2097 + * @since 2.9.8
2098 + */
2099 + private function render_shared_files_notice() {
2100 + if ( ! $this->shared_files_locked() ) {
2101 + return;
2102 + }
2103 + ?>
2104 + <div class="notice notice-info inline" style="margin:10px 0 16px;padding:8px 12px;">
2105 + <p style="margin:0;"><?php echo esc_html( Vigilante_Settings::get_shared_files_notice() ); ?></p>
2106 + </div>
2107 + <?php
2108 + }
2109 +
2110 + /**
2111 + * Acting on another user's account needs permission over that user
2112 + *
2113 + * Since 2.10.3 the handlers behind these tools ask for edit_user over the
2114 + * target, which is the rule WordPress itself applies. On a network the core
2115 + * grants edit_user only to network administrators, so for anybody else these
2116 + * controls do nothing. Better to say so than to paint a button that silently
2117 + * skips every user.
2118 + *
2119 + * @since 2.10.4
2120 + * @return bool
2121 + */
2122 + private function forwarded_chain_readings() {
2123 + // Shown, not decided on: the firewall resolves the address elsewhere.
2124 + $chain = Vigilante_IP_Utils::trusted_forwarded_for();
2125 +
2126 + if ( '' === $chain ) {
2127 + return array();
2128 + }
2129 +
2130 + $public = array();
2131 +
2132 + foreach ( explode( ',', $chain ) as $entry ) {
2133 + $address = Vigilante_IP_Utils::unmap_ipv4( trim( $entry ) );
2134 +
2135 + if ( filter_var( $address, FILTER_VALIDATE_IP ) && ! Vigilante_IP_Utils::is_own_network( $address ) ) {
2136 + $public[] = $address;
2137 + }
2138 + }
2139 +
2140 + if ( count( $public ) < 2 ) {
2141 + return array();
2142 + }
2143 +
2144 + return array(
2145 + 'now' => Vigilante_IP_Utils::client_from_chain( $chain ),
2146 + 'before' => $public[0],
2147 + );
2148 + }
2149 +
2150 + /**
2151 + * Whether the user tools of this screen are out of reach for this user
2152 + *
2153 + * @return bool
2154 + */
2155 + private function user_actions_locked() {
2156 + // On a single site edit_user maps to edit_users, which a custom role with
2157 + // manage_options may lack: since 2.11.8 approving and rejecting a pending
2158 + // registration ask for it, so the buttons have to say so there too.
2159 + return is_multisite() ? ! current_user_can( 'manage_network_users' ) : ! current_user_can( 'edit_users' );
2160 + }
2161 +
2162 + /**
2163 + * Print the notice for user tools that cannot be used from this site
2164 + *
2165 + * @since 2.10.4
2166 + */
2167 + private function render_user_actions_notice() {
2168 + if ( ! $this->user_actions_locked() ) {
2169 + return;
2170 + }
2171 + ?>
2172 + <div class="notice notice-info inline" style="margin:10px 0 16px;padding:8px 12px;">
2173 + <?php if ( is_multisite() ) : ?>
2174 + <p style="margin:0;"><?php esc_html_e( 'These tools act on user accounts, which on a network belong to the whole network rather than to one site. WordPress reserves that to network administrators, so they are managed from the network admin.', 'vigilante' ); ?></p>
2175 + <?php else : ?>
2176 + <p style="margin:0;"><?php esc_html_e( 'These tools act on other user accounts, and your role cannot edit users, so they are not available to you.', 'vigilante' ); ?></p>
2177 + <?php endif; ?>
2178 + </div>
2179 + <?php
2180 + }
2181 +
2182 + /**
2183 + * Approving a change to the shared config files needs the network
2184 + *
2185 + * Since 2.11.3 the handler behind the Approve button asks for
2186 + * manage_network_options, because the two files it approves, wp-config.php
2187 + * and the root .htaccess, belong to the installation, and so does the
2188 + * record of them. The button, though, went on being painted for everybody,
2189 + * so the administrator of a subsite saw the warning, saw the button,
2190 + * pressed it and got "Permission denied" with no explanation. That is
2191 + * exactly what user_actions_locked() above exists to avoid, one release
2192 + * later and one screen over. Flagged by @calzbert.
2193 + *
2194 + * @since 2.11.4
2195 + * @return bool
2196 + */
2197 + private function critical_approval_locked() {
2198 + return is_multisite() && ! current_user_can( 'manage_network_options' );
2199 + }
2200 +
2201 + /**
2202 + * The line that replaces the Approve button where it cannot be used
2203 + *
2204 + * @since 2.11.4
2205 + * @return string
2206 + */
2207 + private function critical_approval_notice() {
2208 + return __( 'These files belong to the whole network rather than to this site, so a change to them is approved from the network admin.', 'vigilante' );
2209 + }
2210 +
2211 + /**
1753 2212 * Check if module is disabled and render warning
1754 2213 *
1755 2214 * @param string $module_key Module key.
1756 2215 * @return bool True if disabled.
@@ -2294,14 +2753,15 @@
2294 2753
2295 2754 <?php $this->render_analyzer_widget( $analyzer_last_scan, $analyzer_history, $analyzer_categories_def, $analyzer_settings ); ?>
2296 2755
2297 2756 <div class="vigilante-modules-grid">
2298 - <h2><?php esc_html_e( 'Security Modules', 'vigilante' ); ?></h2>
2757 + <h2 id="vigilante-section-dashboard-modules"><?php esc_html_e( 'Security Modules', 'vigilante' ); ?></h2>
2299 2758 <p class="description"><?php esc_html_e( 'Enable or disable security modules. Each module controls a tab with detailed settings.', 'vigilante' ); ?></p>
2300 2759 <div class="vigilante-modules-list">
2301 2760 <?php foreach ( $options['modules'] as $module => $enabled ) :
2302 2761 $label = isset( $module_labels[ $module ] ) ? $module_labels[ $module ] : ucwords( str_replace( '_', ' ', $module ) );
2303 2762 $description = isset( $module_descriptions[ $module ] ) ? $module_descriptions[ $module ] : '';
2763 + $vg_module_locked = $this->main_site_files_locked() && in_array( $module, Vigilante_Settings::get_main_site_file_settings()['modules'], true );
2304 2764 ?>
2305 2765 <div class="vigilante-module-item <?php echo $enabled ? 'enabled' : 'disabled'; ?>">
2306 2766 <div class="vigilante-module-header">
2307 2767 <span class="vigilante-module-status"></span>
@@ -2314,8 +2774,9 @@
2314 2774 <input type="checkbox"
2315 2775 name="modules[<?php echo esc_attr( $module ); ?>]"
2316 2776 value="1"
2317 2777 <?php checked( $enabled ); ?>
2778 + <?php disabled( $vg_module_locked ); ?>
2318 2779 aria-label="<?php echo esc_attr( $toggle_label ); ?>"
2319 2780 data-module="<?php echo esc_attr( $module ); ?>">
2320 2781 <span class="vigilante-toggle-slider"></span>
2321 2782 </label>
@@ -2322,8 +2783,11 @@
2322 2783 </div>
2323 2784 <?php if ( $description ) : ?>
2324 2785 <p class="vigilante-module-desc"><?php echo esc_html( $description ); ?></p>
2325 2786 <?php endif; ?>
2787 + <?php if ( $vg_module_locked ) : ?>
2788 + <p class="vigilante-module-desc"><?php esc_html_e( 'On the main site of a network this module also writes files every site shares, so only a network administrator can switch it.', 'vigilante' ); ?></p>
2789 + <?php endif; ?>
2326 2790 </div>
2327 2791 <?php endforeach; ?>
2328 2792 </div>
2329 2793 </div>
@@ -2355,9 +2819,9 @@
2355 2819 $ua_remaining_hours = floor( $ua_remaining / 3600 );
2356 2820 $ua_remaining_mins = floor( ( $ua_remaining % 3600 ) / 60 );
2357 2821 ?>
2358 2822 <div class="vigilante-preset-card vigilante-under-attack-card <?php echo $ua_active ? 'vigilante-under-attack-active' : ''; ?>">
2359 - <h3>
2823 + <h3 id="vigilante-section-dashboard-under-attack">
2360 2824 <span class="dashicons dashicons-shield"></span>
2361 2825 <?php esc_html_e( 'Under Attack', 'vigilante' ); ?>
2362 2826 </h3>
2363 2827 <p><?php esc_html_e( 'Emergency mode. JavaScript challenge for all visitors, aggressive rate limiting, and restricted access. Auto-deactivates after 4 hours.', 'vigilante' ); ?></p>
@@ -2435,11 +2899,11 @@
2435 2899 </label>
2436 2900 </td>
2437 2901 </tr>
2438 2902 <tr>
2439 - <th scope="row"><?php esc_html_e( 'Additional Recipients', 'vigilante' ); ?></th>
2903 + <th scope="row"><label for="vigilante-f-email-additional-recipients"><?php esc_html_e( 'Additional Recipients', 'vigilante' ); ?></label></th>
2440 2904 <td>
2441 - <textarea name="email[additional_recipients]" rows="3" class="large-text code" placeholder="maintenance@example.com&#10;security@example.com"><?php echo esc_textarea( $additional ); ?></textarea>
2905 + <textarea id="vigilante-f-email-additional-recipients" name="email[additional_recipients]" rows="3" class="large-text code" placeholder="maintenance@example.com&#10;security@example.com"><?php echo esc_textarea( $additional ); ?></textarea>
2442 2906 <p class="description"><?php esc_html_e( 'One email per line.', 'vigilante' ); ?></p>
2443 2907 </td>
2444 2908 </tr>
2445 2909 <tr>
@@ -2614,9 +3078,9 @@
2614 3078
2615 3079 <div class="vigilante-tool-card">
2616 3080 <h3><?php esc_html_e( 'Import Settings', 'vigilante' ); ?></h3>
2617 3081 <p><?php esc_html_e( 'Import settings from a previously exported JSON file.', 'vigilante' ); ?></p>
2618 - <input type="file" id="vigilante-import-file" accept=".json" style="display: none;">
3082 + <input type="file" id="vigilante-import-file" aria-label="<?php esc_attr_e( 'Configuration file to import', 'vigilante' ); ?>" accept=".json" style="display: none;">
2619 3083 <button type="button" class="button vigilante-import-settings">
2620 3084 <?php esc_html_e( 'Import Settings', 'vigilante' ); ?>
2621 3085 </button>
2622 3086 </div>
@@ -2622,9 +3086,9 @@
2622 3086 </div>
2623 3087
2624 3088 <div class="vigilante-tool-card">
2625 3089 <h3><?php esc_html_e( 'Reset to Defaults', 'vigilante' ); ?></h3>
2626 - <p><?php esc_html_e( 'Reset all the Vigilant security settings to default values.', 'vigilante' ); ?></p>
3090 + <p><?php esc_html_e( 'Reset all the Vigilant security settings to default values. Your IP lists, custom login address, two-factor setup, scan exclusions and extra alert recipients are kept.', 'vigilante' ); ?></p>
2627 3091 <button type="button" class="button vigilante-reset-settings" style="color: #a00;">
2628 3092 <?php esc_html_e( 'Reset All Settings', 'vigilante' ); ?>
2629 3093 </button>
2630 3094 </div>
@@ -2631,13 +3095,19 @@
2631 3095
2632 3096 <div class="vigilante-tool-card">
2633 3097 <h3><?php esc_html_e( 'Download Config Backup', 'vigilante' ); ?></h3>
2634 3098 <p><?php esc_html_e( 'Download a ZIP backup of your wp-config.php and .htaccess (plus robots.txt if present) before making security changes. The archive is built on the fly and sent to your browser, so nothing is left on the server.', 'vigilante' ); ?></p>
3099 + <?php if ( $this->shared_files_locked() ) : ?>
3100 + <p class="description"><?php esc_html_e( 'Both files belong to the whole network, and wp-config.php carries the database credentials and the authentication salts of every site. The copy is taken from the main site.', 'vigilante' ); ?></p>
3101 + <?php else : ?>
2635 3102 <button type="button" class="button vigilante-create-backup">
2636 3103 <?php esc_html_e( 'Download Backup', 'vigilante' ); ?>
2637 3104 </button>
3105 + <?php endif; ?>
2638 3106 </div>
2639 3107
3108 + <?php if ( ! $this->shared_files_locked() ) : ?>
3109 +
2640 3110 <div class="vigilante-tool-card vigilante-tool-card-wide">
2641 3111 <h3><?php esc_html_e( 'Database Backup', 'vigilante' ); ?></h3>
2642 3112 <p><?php esc_html_e( 'Download a backup of your database as a ZIP file. Select which tables to include.', 'vigilante' ); ?></p>
2643 3113 <button type="button" class="button vigilante-db-backup-toggle">
@@ -2676,8 +3146,15 @@
2676 3146 </div>
2677 3147 </div>
2678 3148 </div>
2679 3149 </div>
3150 + <?php else : ?>
3151 + <div class="vigilante-tool-card vigilante-tool-card-wide">
3152 + <h3><?php esc_html_e( 'Database Backup', 'vigilante' ); ?></h3>
3153 + <p><?php esc_html_e( 'Download a backup of your database as a ZIP file. Select which tables to include.', 'vigilante' ); ?></p>
3154 + <p class="description"><?php esc_html_e( 'The database is shared by the whole network, so a backup taken here would carry every other site and all of the network users. The copy is taken from the main site.', 'vigilante' ); ?></p>
3155 + </div>
3156 + <?php endif; ?>
2680 3157 </div>
2681 3158 <?php
2682 3159 }
2683 3160
@@ -2700,14 +3177,21 @@
2700 3177 <?php esc_html_e( 'Full page caching systems that serve cached pages before PHP executes (Varnish, LiteSpeed Cache, NGINX FastCGI Cache, Cloudflare APO) may bypass PHP-level firewall rules for cached requests. The .htaccess rules will still apply on Apache/LiteSpeed servers.', 'vigilante' ); ?>
2701 3178 </p>
2702 3179 </div>
2703 3180
3181 + <?php $vg_main_locked = $this->main_site_files_locked(); ?>
3182 + <?php if ( $vg_main_locked ) : ?>
3183 + <div class="notice notice-info inline" style="margin:10px 0 16px;padding:8px 12px;">
3184 + <p style="margin:0;"><?php esc_html_e( 'On the main site of a network, blocking bad bots and bad query strings, the visitor IP detection and the two whitelists also build the .htaccess rules every site shares, so only a network administrator can change them.', 'vigilante' ); ?></p>
3185 + </div>
3186 + <?php endif; ?>
3187 +
2704 3188 <table class="form-table">
2705 3189 <tr>
2706 3190 <th scope="row"><?php esc_html_e( 'Block Bad Query Strings', 'vigilante' ); ?></th>
2707 3191 <td>
2708 3192 <label>
2709 - <input type="checkbox" name="firewall[block_bad_query_strings]" value="1" <?php checked( ! empty( $options['block_bad_query_strings'] ) ); ?>>
3193 + <input type="checkbox" name="firewall[block_bad_query_strings]" value="1" <?php disabled( $vg_main_locked ); ?> <?php checked( ! empty( $options['block_bad_query_strings'] ) ); ?>>
2710 3194 <?php esc_html_e( 'Block malicious query string patterns', 'vigilante' ); ?>
2711 3195 </label>
2712 3196 </td>
2713 3197 </tr>
@@ -2750,9 +3234,9 @@
2750 3234 <tr>
2751 3235 <th scope="row"><?php esc_html_e( 'Block Bad Bots', 'vigilante' ); ?></th>
2752 3236 <td>
2753 3237 <label>
2754 - <input type="checkbox" name="firewall[block_bad_bots]" value="1" <?php checked( ! empty( $options['block_bad_bots'] ) ); ?>>
3238 + <input type="checkbox" name="firewall[block_bad_bots]" value="1" <?php disabled( $vg_main_locked ); ?> <?php checked( ! empty( $options['block_bad_bots'] ) ); ?>>
2755 3239 <?php esc_html_e( 'Block known malicious bots and scanners', 'vigilante' ); ?>
2756 3240 </label>
2757 3241 </td>
2758 3242 </tr>
@@ -2769,11 +3253,11 @@
2769 3253 </label>
2770 3254 </td>
2771 3255 </tr>
2772 3256 <tr>
2773 - <th scope="row"><?php esc_html_e( 'Requests per Minute', 'vigilante' ); ?></th>
3257 + <th scope="row"><label for="vigilante-f-firewall-rate-limiting-requests-per-minute"><?php esc_html_e( 'Requests per Minute', 'vigilante' ); ?></label></th>
2774 3258 <td>
2775 - <input type="number" name="firewall[rate_limiting][requests_per_minute]" value="<?php echo esc_attr( $options['rate_limiting']['requests_per_minute'] ?? 120 ); ?>" min="10" max="500" class="small-text">
3259 + <input id="vigilante-f-firewall-rate-limiting-requests-per-minute" type="number" name="firewall[rate_limiting][requests_per_minute]" value="<?php echo esc_attr( $options['rate_limiting']['requests_per_minute'] ?? 120 ); ?>" min="10" max="500" class="small-text">
2776 3260 <p class="description">
2777 3261 <?php esc_html_e( 'Counts only PHP requests to WordPress (pages, admin-ajax, REST, login) from a single IP, not static assets like images, CSS or JS. 120/min suits most sites; sustained traffic above that from one IP is usually a bot. To allow a legitimate service, whitelist its IP instead of raising the limit.', 'vigilante' ); ?>
2778 3262 </p>
2779 3263 </td>
@@ -2778,11 +3262,11 @@
2778 3262 </p>
2779 3263 </td>
2780 3264 </tr>
2781 3265 <tr>
2782 - <th scope="row"><?php esc_html_e( 'Block Duration (seconds)', 'vigilante' ); ?></th>
3266 + <th scope="row"><label for="vigilante-f-firewall-rate-limiting-block-duration"><?php esc_html_e( 'Block Duration (seconds)', 'vigilante' ); ?></label></th>
2783 3267 <td>
2784 - <input type="number" name="firewall[rate_limiting][block_duration]" value="<?php echo esc_attr( $options['rate_limiting']['block_duration'] ?? 300 ); ?>" min="60" max="3600" class="small-text">
3268 + <input id="vigilante-f-firewall-rate-limiting-block-duration" type="number" name="firewall[rate_limiting][block_duration]" value="<?php echo esc_attr( $options['rate_limiting']['block_duration'] ?? 300 ); ?>" min="60" max="3600" class="small-text">
2785 3269 </td>
2786 3270 </tr>
2787 3271 <tr>
2788 3272 <th scope="row"><?php esc_html_e( 'Progressive Blocking', 'vigilante' ); ?></th>
@@ -2805,11 +3289,11 @@
2805 3289 </p>
2806 3290 </td>
2807 3291 </tr>
2808 3292 <tr>
2809 - <th scope="row"><?php esc_html_e( 'Maximum Block Duration', 'vigilante' ); ?></th>
3293 + <th scope="row"><label for="vigilante-f-firewall-rate-limiting-max-block-duration"><?php esc_html_e( 'Maximum Block Duration', 'vigilante' ); ?></label></th>
2810 3294 <td>
2811 - <select name="firewall[rate_limiting][max_block_duration]">
3295 + <select id="vigilante-f-firewall-rate-limiting-max-block-duration" name="firewall[rate_limiting][max_block_duration]">
2812 3296 <?php
2813 3297 $max_options = array(
2814 3298 3600 => __( '1 hour', 'vigilante' ),
2815 3299 21600 => __( '6 hours', 'vigilante' ),
@@ -2866,8 +3350,29 @@
2866 3350 </table>
2867 3351 </div>
2868 3352 <?php endif; ?>
2869 3353
3354 + <?php
3355 + // Since 2.11.8 X-Forwarded-For is read from its end, where the proxy
3356 + // writes. The administrator's own request shows whether that end is
3357 + // a CDN or a balancer for everybody here. Cross review of 2.11.8.
3358 + $xff_readings = $this->forwarded_chain_readings();
3359 + if ( $xff_readings ) :
3360 + ?>
3361 + <div id="vigilante-xff-chain-notice" class="notice notice-warning inline" style="margin:10px 0 16px;padding:8px 12px;">
3362 + <p style="margin:0;">
3363 + <?php
3364 + printf(
3365 + /* translators: 1: address Vigilant reads now, 2: address earlier versions read */
3366 + esc_html__( 'Your own request reaches the site with more than one public address in X-Forwarded-For. Vigilant reads the last one, %1$s, which is the one your proxy added; up to version 2.11.7 it read the first one, %2$s, which a visitor can write. If %1$s belongs to a CDN or a load balancer rather than to you, every visitor shares it for rate limiting, login lockouts and the IP lists: choose the header of that CDN in Visitor IP detection, such as CF-Connecting-IP for Cloudflare.', 'vigilante' ),
3367 + esc_html( $xff_readings['now'] ),
3368 + esc_html( $xff_readings['before'] )
3369 + );
3370 + ?>
3371 + </p>
3372 + </div>
3373 + <?php endif; ?>
3374 +
2870 3375 <h3><?php esc_html_e( 'IP Lists', 'vigilante' ); ?></h3>
2871 3376 <p class="description">
2872 3377 <?php
2873 3378 printf(
@@ -2878,12 +3383,12 @@
2878 3383 ?>
2879 3384 </p>
2880 3385 <table class="form-table">
2881 3386 <tr>
2882 - <th scope="row"><?php esc_html_e( 'Visitor IP detection', 'vigilante' ); ?></th>
3387 + <th scope="row"><label for="vigilante-f-firewall-trusted-proxy-header"><?php esc_html_e( 'Visitor IP detection', 'vigilante' ); ?></label></th>
2883 3388 <td>
2884 3389 <?php $proxy_header = $options['trusted_proxy_header'] ?? ''; ?>
2885 - <select name="firewall[trusted_proxy_header]">
3390 + <select id="vigilante-f-firewall-trusted-proxy-header" name="firewall[trusted_proxy_header]" <?php disabled( $vg_main_locked ); ?>>
2886 3391 <option value="" <?php selected( $proxy_header, '' ); ?>><?php esc_html_e( 'Direct connection, only REMOTE_ADDR (recommended)', 'vigilante' ); ?></option>
2887 3392 <option value="cf-connecting-ip" <?php selected( $proxy_header, 'cf-connecting-ip' ); ?>><?php esc_html_e( 'Behind Cloudflare (CF-Connecting-IP)', 'vigilante' ); ?></option>
2888 3393 <option value="x-forwarded-for" <?php selected( $proxy_header, 'x-forwarded-for' ); ?>><?php esc_html_e( 'Behind a reverse proxy or load balancer (X-Forwarded-For)', 'vigilante' ); ?></option>
2889 3394 <option value="x-real-ip" <?php selected( $proxy_header, 'x-real-ip' ); ?>><?php esc_html_e( 'Behind an nginx proxy (X-Real-IP)', 'vigilante' ); ?></option>
@@ -2893,13 +3398,13 @@
2893 3398 </p>
2894 3399 </td>
2895 3400 </tr>
2896 3401 <tr>
2897 - <th scope="row"><?php esc_html_e( 'IP Whitelist', 'vigilante' ); ?></th>
3402 + <th scope="row"><label for="vigilante-f-firewall-ip-whitelist"><?php esc_html_e( 'IP Whitelist', 'vigilante' ); ?></label></th>
2898 3403 <td>
2899 - <textarea name="firewall[ip_whitelist]" rows="4" class="large-text code" placeholder="192.168.1.50&#10;192.168.1.0/24&#10;192.168.1.*"><?php echo esc_textarea( implode( "\n", $options['ip_whitelist'] ?? array() ) ); ?></textarea>
3404 + <textarea id="vigilante-f-firewall-ip-whitelist" name="firewall[ip_whitelist]" <?php disabled( $vg_main_locked ); ?> rows="4" class="large-text code" placeholder="192.168.1.50&#10;192.168.1.0/24&#10;192.168.1.*"><?php echo esc_textarea( implode( "\n", $options['ip_whitelist'] ?? array() ) ); ?></textarea>
2900 3405 <p class="description">
2901 - <?php esc_html_e( 'One IP per line. These IPs will bypass firewall checks.', 'vigilante' ); ?>
3406 + <?php esc_html_e( 'One IP per line. These IPs bypass the firewall checks, and they also reach wp-admin when the login URL is hidden, so remote managers such as MainWP or ManageWP are not turned away with a 404. The hidden login form itself stays hidden for every IP, this one included.', 'vigilante' ); ?>
2902 3407 <br>
2903 3408 <?php
2904 3409 printf(
2905 3410 /* translators: 1: opening <code>, 2: closing </code>. Placeholders wrap the IP, CIDR and wildcard examples. */
@@ -2911,11 +3416,11 @@
2911 3416 </p>
2912 3417 </td>
2913 3418 </tr>
2914 3419 <tr>
2915 - <th scope="row"><?php esc_html_e( 'IP Blacklist', 'vigilante' ); ?></th>
3420 + <th scope="row"><label for="vigilante-f-firewall-ip-blacklist"><?php esc_html_e( 'IP Blacklist', 'vigilante' ); ?></label></th>
2916 3421 <td>
2917 - <textarea name="firewall[ip_blacklist]" rows="4" class="large-text code" placeholder="203.0.113.42&#10;203.0.113.0/24&#10;203.0.113.*"><?php echo esc_textarea( implode( "\n", $options['ip_blacklist'] ?? array() ) ); ?></textarea>
3422 + <textarea id="vigilante-f-firewall-ip-blacklist" name="firewall[ip_blacklist]" rows="4" class="large-text code" placeholder="203.0.113.42&#10;203.0.113.0/24&#10;203.0.113.*"><?php echo esc_textarea( implode( "\n", $options['ip_blacklist'] ?? array() ) ); ?></textarea>
2918 3423 <p class="description">
2919 3424 <?php esc_html_e( 'One IP per line. These IPs will be blocked immediately.', 'vigilante' ); ?>
2920 3425 <br>
2921 3426 <?php
@@ -2934,18 +3439,18 @@
2934 3439 <h3><?php esc_html_e( 'User-Agent Lists', 'vigilante' ); ?></h3>
2935 3440 <p><?php esc_html_e( 'Partial matching: enter a keyword and any User-Agent containing it will be matched.', 'vigilante' ); ?></p>
2936 3441 <table class="form-table">
2937 3442 <tr>
2938 - <th scope="row"><?php esc_html_e( 'User-Agent Whitelist', 'vigilante' ); ?></th>
3443 + <th scope="row"><label for="vigilante-f-firewall-ua-whitelist"><?php esc_html_e( 'User-Agent Whitelist', 'vigilante' ); ?></label></th>
2939 3444 <td>
2940 - <textarea name="firewall[ua_whitelist]" rows="4" class="large-text code"><?php echo esc_textarea( implode( "\n", $options['ua_whitelist'] ?? array() ) ); ?></textarea>
3445 + <textarea id="vigilante-f-firewall-ua-whitelist" name="firewall[ua_whitelist]" <?php disabled( $vg_main_locked ); ?> rows="4" class="large-text code"><?php echo esc_textarea( implode( "\n", $options['ua_whitelist'] ?? array() ) ); ?></textarea>
2941 3446 <p class="description"><?php esc_html_e( 'One User-Agent per line. These will bypass all firewall checks. Example: ManageWP, MainWP, UptimeRobot.', 'vigilante' ); ?></p>
2942 3447 </td>
2943 3448 </tr>
2944 3449 <tr>
2945 - <th scope="row"><?php esc_html_e( 'User-Agent Blacklist', 'vigilante' ); ?></th>
3450 + <th scope="row"><label for="vigilante-f-firewall-ua-blacklist"><?php esc_html_e( 'User-Agent Blacklist', 'vigilante' ); ?></label></th>
2946 3451 <td>
2947 - <textarea name="firewall[ua_blacklist]" rows="4" class="large-text code"><?php echo esc_textarea( implode( "\n", $options['ua_blacklist'] ?? array() ) ); ?></textarea>
3452 + <textarea id="vigilante-f-firewall-ua-blacklist" name="firewall[ua_blacklist]" rows="4" class="large-text code"><?php echo esc_textarea( implode( "\n", $options['ua_blacklist'] ?? array() ) ); ?></textarea>
2948 3453 <p class="description"><?php esc_html_e( 'One User-Agent per line. These will be blocked immediately.', 'vigilante' ); ?></p>
2949 3454 </td>
2950 3455 </tr>
2951 3456 </table>
@@ -2950,9 +3455,16 @@
2950 3455 </tr>
2951 3456 </table>
2952 3457 </div>
2953 3458
2954 - <div id="vigilante-section-firewall-server" class="vigilante-settings-section">
3459 + <?php
3460 + $vg_shared_locked = $this->shared_files_locked();
3461 + // Paint what is actually in force, not this site's unused copy.
3462 + $vg_local_options = $options;
3463 + $options = $this->get_section_for_display( 'firewall' );
3464 + ?>
3465 + <?php $this->render_shared_files_notice(); ?>
3466 + <div id="vigilante-section-firewall-server" class="vigilante-settings-section <?php echo $vg_shared_locked ? 'vigilante-form-disabled' : ''; ?>" <?php echo $vg_shared_locked ? 'inert' : ''; ?>>
2955 3467 <h2>
2956 3468 <?php esc_html_e( 'Server Protection', 'vigilante' ); ?>
2957 3469 <span class="vigilante-method-badge htaccess"><?php esc_html_e( 'HTACCESS', 'vigilante' ); ?></span>
2958 3470 </h2>
@@ -3033,8 +3545,9 @@
3033 3545 </td>
3034 3546 </tr>
3035 3547 </table>
3036 3548 </div>
3549 + <?php $options = $vg_local_options; ?>
3037 3550
3038 3551 <p class="submit vigilante-submit-buttons">
3039 3552 <button type="submit" class="button button-primary vigilante-save-btn" data-original-text="<?php esc_attr_e( 'Save Settings', 'vigilante' ); ?>">
3040 3553 <?php esc_html_e( 'Save Settings', 'vigilante' ); ?>
@@ -3065,18 +3578,18 @@
3065 3578 <p><?php esc_html_e( 'Brute force protection and WordPress login hardening.', 'vigilante' ); ?></p>
3066 3579
3067 3580 <table class="form-table">
3068 3581 <tr id="field-max-attempts">
3069 - <th scope="row"><?php esc_html_e( 'Max Login Attempts', 'vigilante' ); ?></th>
3582 + <th scope="row"><label for="vigilante-f-login-security-max-attempts"><?php esc_html_e( 'Max Login Attempts', 'vigilante' ); ?></label></th>
3070 3583 <td>
3071 - <input type="number" name="login_security[max_attempts]" value="<?php echo esc_attr( $options['max_attempts'] ?? 5 ); ?>" min="1" max="20" class="small-text">
3584 + <input id="vigilante-f-login-security-max-attempts" type="number" name="login_security[max_attempts]" value="<?php echo esc_attr( $options['max_attempts'] ?? 5 ); ?>" min="1" max="20" class="small-text">
3072 3585 <p class="description"><?php esc_html_e( 'Number of failed attempts before lockout.', 'vigilante' ); ?></p>
3073 3586 </td>
3074 3587 </tr>
3075 3588 <tr>
3076 - <th scope="row"><?php esc_html_e( 'Lockout Duration', 'vigilante' ); ?></th>
3589 + <th scope="row"><label for="vigilante-f-login-security-lockout-duration"><?php esc_html_e( 'Lockout Duration', 'vigilante' ); ?></label></th>
3077 3590 <td>
3078 - <input type="number" name="login_security[lockout_duration]" value="<?php echo esc_attr( ( $options['lockout_duration'] ?? 1800 ) / 60 ); ?>" min="1" max="1440" class="small-text">
3591 + <input id="vigilante-f-login-security-lockout-duration" type="number" name="login_security[lockout_duration]" value="<?php echo esc_attr( ( $options['lockout_duration'] ?? 1800 ) / 60 ); ?>" min="1" max="1440" class="small-text">
3079 3592 <?php esc_html_e( 'minutes', 'vigilante' ); ?>
3080 3593 </td>
3081 3594 </tr>
3082 3595 <tr>
@@ -3096,17 +3609,8 @@
3096 3609 <?php esc_html_e( 'Show generic error message instead of specific errors', 'vigilante' ); ?>
3097 3610 </label>
3098 3611 </td>
3099 3612 </tr>
3100 - <tr id="field-disable-xmlrpc">
3101 - <th scope="row"><?php esc_html_e( 'Disable XML-RPC', 'vigilante' ); ?></th>
3102 - <td>
3103 - <label>
3104 - <input type="checkbox" name="login_security[disable_xmlrpc]" value="1" <?php checked( ! empty( $options['disable_xmlrpc'] ) ); ?>>
3105 - <?php esc_html_e( 'Completely disable XML-RPC functionality', 'vigilante' ); ?>
3106 - </label>
3107 - </td>
3108 - </tr>
3109 3613 <tr>
3110 3614 <th scope="row"><?php esc_html_e( 'Disable Application Passwords', 'vigilante' ); ?></th>
3111 3615 <td>
3112 3616 <label>
@@ -3134,8 +3638,11 @@
3134 3638 </p>
3135 3639 <p class="description">
3136 3640 <?php esc_html_e( 'Direct access to wp-login.php and wp-admin will return a 404 error for non-logged users.', 'vigilante' ); ?>
3137 3641 </p>
3642 + <p class="description">
3643 + <?php esc_html_e( 'An IP in the firewall whitelist is still allowed into wp-admin, so remote managers keep working, but it does not get the login form: the hidden URL is the only way in for everyone.', 'vigilante' ); ?>
3644 + </p>
3138 3645 </div>
3139 3646 </td>
3140 3647 </tr>
3141 3648 </table>
@@ -3224,9 +3731,9 @@
3224 3731 $two_factor = $options['two_factor'] ?? array();
3225 3732 $two_factor_enabled = ! empty( $two_factor['enabled'] );
3226 3733 ?>
3227 3734 <div class="vigilante-settings-section vigilante-lockout-section">
3228 - <h2><?php esc_html_e( 'Login Protection Status', 'vigilante' ); ?></h2>
3735 + <h2 id="vigilante-section-login-status"><?php esc_html_e( 'Login Protection Status', 'vigilante' ); ?></h2>
3229 3736
3230 3737 <table class="form-table">
3231 3738 <tr>
3232 3739 <th scope="row"><?php esc_html_e( 'Current settings', 'vigilante' ); ?></th>
@@ -3386,9 +3893,9 @@
3386 3893 $excluded = $two_factor['excluded_users'] ?? array();
3387 3894 $method = $two_factor['method'] ?? 'email';
3388 3895 $grace_days = $two_factor['grace_period_days'] ?? 3;
3389 3896 ?>
3390 - <h3>
3897 + <h3 id="vigilante-section-login-2fa">
3391 3898 <?php esc_html_e( 'Two-Factor Authentication (2FA)', 'vigilante' ); ?>
3392 3899 <span class="vigilante-method-badge php"><?php esc_html_e( 'PHP', 'vigilante' ); ?></span>
3393 3900 <span class="vigilante-method-badge database"><?php esc_html_e( 'Database', 'vigilante' ); ?></span>
3394 3901 </h3>
@@ -3502,11 +4009,11 @@
3502 4009 </tr>
3503 4010
3504 4011 <!-- TOTP-specific: Grace period -->
3505 4012 <tr class="vigilante-2fa-totp-only" <?php echo 'totp' !== $method ? 'style="display:none;"' : ''; ?>>
3506 - <th scope="row"><?php esc_html_e( 'Grace period', 'vigilante' ); ?></th>
4013 + <th scope="row"><label for="vigilante-f-login-security-two-factor-grace-period-days"><?php esc_html_e( 'Grace period', 'vigilante' ); ?></label></th>
3507 4014 <td>
3508 - <input type="number"
4015 + <input id="vigilante-f-login-security-two-factor-grace-period-days" type="number"
3509 4016 name="login_security[two_factor][grace_period_days]"
3510 4017 value="<?php echo esc_attr( $grace_days ); ?>"
3511 4018 min="0" max="30" class="small-text">
3512 4019 <?php esc_html_e( 'days', 'vigilante' ); ?>
@@ -3515,11 +4022,11 @@
3515 4022 </tr>
3516 4023
3517 4024 <!-- Email-specific: Sender name -->
3518 4025 <tr class="vigilante-2fa-email-only" <?php echo 'email' !== $method ? 'style="display:none;"' : ''; ?>>
3519 - <th scope="row"><?php esc_html_e( 'Email sender name', 'vigilante' ); ?></th>
4026 + <th scope="row"><label for="vigilante-f-login-security-two-factor-email-from-name"><?php esc_html_e( 'Email sender name', 'vigilante' ); ?></label></th>
3520 4027 <td>
3521 - <input type="text"
4028 + <input id="vigilante-f-login-security-two-factor-email-from-name" type="text"
3522 4029 name="login_security[two_factor][email_from_name]"
3523 4030 value="<?php echo esc_attr( $two_factor['email_from_name'] ?? '' ); ?>"
3524 4031 class="regular-text vigilante-2fa-email-from"
3525 4032 placeholder="<?php echo esc_attr( get_bloginfo( 'name' ) ); ?>">
@@ -3587,14 +4094,140 @@
3587 4094
3588 4095 /**
3589 4096 * Render security headers tab
3590 4097 */
4098 + /**
4099 + * Offer back the header settings the 2.9.8 migration wiped.
4100 + *
4101 + * Rendered outside the settings form on purpose, so its buttons can never
4102 + * submit it, and only when there is something to actually change. Shows the
4103 + * difference before anything is written: nothing is applied that the owner
4104 + * has not seen first.
4105 + *
4106 + * @since 2.10.0
4107 + */
4108 + private function render_headers_recovery_offer() {
4109 + /*
4110 + * On a network the .htaccess belongs to every site and only the main one
4111 + * writes it, so this is not a decision a subsite gets to make. Its own
4112 + * security_headers options are inert anyway: what the network serves
4113 + * comes from the file the main site owns. Without this gate a subsite
4114 + * administrator was shown a Restore button that could only ever answer
4115 + * with a permission error, which is worse than showing nothing.
4116 + */
4117 + if ( ! Vigilante_Settings::can_write_shared_files() ) {
4118 + return;
4119 + }
4120 +
4121 + if ( ! Vigilante_Htaccess_Recovery::is_available() ) {
4122 + /*
4123 + * Already restored. Offer to take it back for as long as the previous
4124 + * section is still stored: a restore that cannot be undone is a second
4125 + * irreversible change on top of the one being repaired.
4126 + */
4127 + if ( Vigilante_Htaccess_Recovery::has_undo() ) {
4128 + ?>
4129 + <div class="notice notice-info inline" id="vigilante-headers-recovery-undo">
4130 + <p>
4131 + <?php esc_html_e( 'The Security Headers settings were restored from the copy Vigilant had kept of your .htaccess.', 'vigilante' ); ?>
4132 + <button type="button" class="button button-small" id="vigilante-recovery-undo">
4133 + <?php esc_html_e( 'Undo the restore', 'vigilante' ); ?>
4134 + </button>
4135 + </p>
4136 + </div>
4137 + <?php
4138 + }
4139 +
4140 + return;
4141 + }
4142 +
4143 + $rows = Vigilante_Htaccess_Recovery::get_diff( $this->settings );
4144 +
4145 + if ( empty( $rows ) ) {
4146 + return;
4147 + }
4148 +
4149 + $snapshot = Vigilante_Htaccess_Recovery::get_snapshot();
4150 + $taken = isset( $snapshot['time'] ) ? (int) $snapshot['time'] : 0;
4151 + $block = Vigilante_Htaccess_Recovery::get_raw_block();
4152 + ?>
4153 + <div class="vigilante-settings-section" id="vigilante-headers-recovery">
4154 + <h2><?php esc_html_e( 'Recover your previous header settings', 'vigilante' ); ?></h2>
4155 + <p>
4156 + <?php esc_html_e( 'Updating to 2.9.8 reset this tab to factory values: the migration replaced the whole section instead of merging into it. Your server kept sending the right headers, because the .htaccess had not been rewritten yet, so Vigilant saved a copy of that file before touching it. These are the settings it found in that copy.', 'vigilante' ); ?>
4157 + </p>
4158 + <?php if ( $taken ) : ?>
4159 + <p class="description">
4160 + <?php
4161 + printf(
4162 + /* translators: %s: date and time the .htaccess copy was taken. */
4163 + esc_html__( 'Copy taken on %s.', 'vigilante' ),
4164 + esc_html( wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $taken ) )
4165 + );
4166 + ?>
4167 + </p>
4168 + <?php endif; ?>
4169 +
4170 + <table class="widefat striped">
4171 + <thead>
4172 + <tr>
4173 + <th scope="col"><?php esc_html_e( 'Setting', 'vigilante' ); ?></th>
4174 + <th scope="col"><?php esc_html_e( 'Now', 'vigilante' ); ?></th>
4175 + <th scope="col"><?php esc_html_e( 'Would be restored to', 'vigilante' ); ?></th>
4176 + </tr>
4177 + </thead>
4178 + <tbody>
4179 + <?php foreach ( $rows as $row ) : ?>
4180 + <tr>
4181 + <th scope="row"><?php echo esc_html( $row['label'] ); ?></th>
4182 + <td><?php echo esc_html( $row['current'] ); ?></td>
4183 + <td>
4184 + <?php echo esc_html( $row['recovered'] ); ?>
4185 + <?php if ( ! empty( $row['detail'] ) ) : ?>
4186 + <br><span class="description"><?php echo esc_html( $row['detail'] ); ?></span>
4187 + <?php endif; ?>
4188 + </td>
4189 + </tr>
4190 + <?php endforeach; ?>
4191 + </tbody>
4192 + </table>
4193 +
4194 + <p class="description">
4195 + <?php esc_html_e( 'Only these settings are written. The .htaccess is then rebuilt from them, the same way saving this tab rebuilds it. The stored copy of the file is never written back, so nothing your host, your cache plugin or your CDN added to it is touched.', 'vigilante' ); ?>
4196 + </p>
4197 +
4198 + <?php if ( '' !== $block ) : ?>
4199 + <details>
4200 + <summary><?php esc_html_e( 'Show the saved .htaccess block', 'vigilante' ); ?></summary>
4201 + <textarea readonly rows="12" class="large-text code" onclick="this.select();"><?php echo esc_textarea( $block ); ?></textarea>
4202 + </details>
4203 + <?php endif; ?>
4204 +
4205 + <p class="submit vigilante-submit-buttons">
4206 + <button type="button" class="button button-primary" id="vigilante-recovery-restore">
4207 + <?php esc_html_e( 'Restore these settings', 'vigilante' ); ?>
4208 + </button>
4209 + <button type="button" class="button" id="vigilante-recovery-dismiss">
4210 + <?php esc_html_e( 'No thanks, keep what I have', 'vigilante' ); ?>
4211 + </button>
4212 + </p>
4213 + <div id="vigilante-recovery-result"></div>
4214 + </div>
4215 + <?php
4216 + }
4217 +
3591 4218 private function render_tab_headers() {
3592 4219 $is_disabled = $this->render_module_disabled_notice( 'security_headers' );
3593 - $options = $this->settings->get_section( 'security_headers' );
4220 + // Every setting on this tab ends up in .htaccess, so on a subsite the
4221 + // whole tab is somebody else's, values included.
4222 + $vg_shared_locked = $this->shared_files_locked();
4223 + $options = $this->get_section_for_display( 'security_headers' );
3594 4224 ?>
4225 + <?php $this->render_headers_recovery_offer(); ?>
4226 +
3595 4227 <form class="vigilante-settings-form <?php echo $is_disabled ? 'vigilante-form-disabled' : ''; ?>" data-section="security_headers" <?php echo $is_disabled ? 'inert' : ''; ?>>
3596 - <div id="vigilante-section-headers-main" class="vigilante-settings-section">
4228 + <?php $this->render_shared_files_notice(); ?>
4229 + <div id="vigilante-section-headers-main" class="vigilante-settings-section <?php echo $vg_shared_locked ? 'vigilante-form-disabled' : ''; ?>" <?php echo $vg_shared_locked ? 'inert' : ''; ?>>
3597 4230 <h2>
3598 4231 <?php esc_html_e( 'Security Headers', 'vigilante' ); ?>
3599 4232 <span class="vigilante-method-badge htaccess"><?php esc_html_e( 'HTACCESS', 'vigilante' ); ?></span>
3600 4233 </h2>
@@ -3601,11 +4234,11 @@
3601 4234 <p><?php esc_html_e( 'HTTP headers sent with every response via .htaccess (mod_headers).', 'vigilante' ); ?></p>
3602 4235
3603 4236 <table class="form-table">
3604 4237 <tr>
3605 - <th scope="row"><?php esc_html_e( 'X-Frame-Options', 'vigilante' ); ?></th>
4238 + <th scope="row"><label for="vigilante-f-security-headers-x-frame-options"><?php esc_html_e( 'X-Frame-Options', 'vigilante' ); ?></label></th>
3606 4239 <td>
3607 - <select name="security_headers[x_frame_options]">
4240 + <select id="vigilante-f-security-headers-x-frame-options" name="security_headers[x_frame_options]">
3608 4241 <option value="" <?php selected( empty( $options['x_frame_options'] ) ); ?>><?php esc_html_e( 'Disabled', 'vigilante' ); ?></option>
3609 4242 <option value="SAMEORIGIN" <?php selected( $options['x_frame_options'] ?? '', 'SAMEORIGIN' ); ?>>SAMEORIGIN</option>
3610 4243 <option value="DENY" <?php selected( $options['x_frame_options'] ?? '', 'DENY' ); ?>>DENY</option>
3611 4244 </select>
@@ -3621,11 +4254,11 @@
3621 4254 </label>
3622 4255 </td>
3623 4256 </tr>
3624 4257 <tr>
3625 - <th scope="row"><?php esc_html_e( 'Referrer-Policy', 'vigilante' ); ?></th>
4258 + <th scope="row"><label for="vigilante-f-security-headers-referrer-policy"><?php esc_html_e( 'Referrer-Policy', 'vigilante' ); ?></label></th>
3626 4259 <td>
3627 - <select name="security_headers[referrer_policy]">
4260 + <select id="vigilante-f-security-headers-referrer-policy" name="security_headers[referrer_policy]">
3628 4261 <option value="" <?php selected( empty( $options['referrer_policy'] ) ); ?>><?php esc_html_e( 'Disabled', 'vigilante' ); ?></option>
3629 4262 <option value="no-referrer" <?php selected( $options['referrer_policy'] ?? '', 'no-referrer' ); ?>>no-referrer</option>
3630 4263 <option value="strict-origin-when-cross-origin" <?php selected( $options['referrer_policy'] ?? '', 'strict-origin-when-cross-origin' ); ?>>strict-origin-when-cross-origin</option>
3631 4264 <option value="same-origin" <?php selected( $options['referrer_policy'] ?? '', 'same-origin' ); ?>>same-origin</option>
@@ -3633,65 +4266,119 @@
3633 4266 </td>
3634 4267 </tr>
3635 4268 </table>
3636 4269
3637 - <h3><?php esc_html_e( 'HSTS (HTTP Strict Transport Security)', 'vigilante' ); ?></h3>
4270 + <h3 id="vigilante-section-headers-csp"><?php esc_html_e( 'Content Security Policy', 'vigilante' ); ?></h3>
3638 4271 <table class="form-table">
3639 4272 <tr>
3640 - <th scope="row"><?php esc_html_e( 'Enable HSTS', 'vigilante' ); ?></th>
4273 + <th scope="row"><?php esc_html_e( 'Enable CSP', 'vigilante' ); ?></th>
3641 4274 <td>
3642 4275 <label>
3643 - <input type="checkbox" name="security_headers[hsts][enabled]" value="1" <?php checked( ! empty( $options['hsts']['enabled'] ) ); ?>>
3644 - <?php esc_html_e( 'Force HTTPS connections', 'vigilante' ); ?>
4276 + <input type="checkbox" name="security_headers[csp][enabled]" value="1" <?php checked( ! empty( $options['csp']['enabled'] ) ); ?>>
4277 + <?php esc_html_e( 'Enable Content Security Policy', 'vigilante' ); ?>
3645 4278 </label>
3646 - <p class="description"><?php esc_html_e( '&#9888; Warning: Only enable if your site fully supports HTTPS.', 'vigilante' ); ?></p>
3647 4279 </td>
3648 4280 </tr>
3649 4281 <tr>
3650 - <th scope="row"><?php esc_html_e( 'Max Age', 'vigilante' ); ?></th>
4282 + <th scope="row"><?php esc_html_e( 'Report Only Mode', 'vigilante' ); ?></th>
3651 4283 <td>
3652 - <select name="security_headers[hsts][max_age]">
3653 - <option value="86400" <?php selected( $options['hsts']['max_age'] ?? 31536000, 86400 ); ?>><?php esc_html_e( '1 day (testing)', 'vigilante' ); ?></option>
3654 - <option value="2592000" <?php selected( $options['hsts']['max_age'] ?? 31536000, 2592000 ); ?>><?php esc_html_e( '30 days', 'vigilante' ); ?></option>
3655 - <option value="31536000" <?php selected( $options['hsts']['max_age'] ?? 31536000, 31536000 ); ?>><?php esc_html_e( '1 year (recommended)', 'vigilante' ); ?></option>
3656 - <option value="63072000" <?php selected( $options['hsts']['max_age'] ?? 31536000, 63072000 ); ?>><?php esc_html_e( '2 years', 'vigilante' ); ?></option>
3657 - </select>
4284 + <label>
4285 + <input type="checkbox" name="security_headers[csp][report_only]" value="1" <?php checked( ! empty( $options['csp']['report_only'] ) ); ?>>
4286 + <?php esc_html_e( 'Report violations without blocking (for testing)', 'vigilante' ); ?>
4287 + </label>
3658 4288 </td>
3659 4289 </tr>
4290 + </table>
4291 +
4292 + <h3 id="vigilante-section-headers-force-https"><?php esc_html_e( 'HTTPS', 'vigilante' ); ?></h3>
4293 + <p class="description"><?php esc_html_e( 'HTTPS is strongly recommended, but Vigilant will not impose it. Enable only what your site already supports.', 'vigilante' ); ?></p>
4294 + <table class="form-table">
3660 4295 <tr>
3661 - <th scope="row"><?php esc_html_e( 'Include Subdomains', 'vigilante' ); ?></th>
4296 + <th scope="row"><?php esc_html_e( 'Redirect HTTP to HTTPS', 'vigilante' ); ?></th>
3662 4297 <td>
3663 4298 <label>
3664 - <input type="checkbox" name="security_headers[hsts][include_subdomains]" value="1" <?php checked( ! empty( $options['hsts']['include_subdomains'] ) ); ?>>
3665 - <?php esc_html_e( 'Apply HSTS to all subdomains', 'vigilante' ); ?>
4299 + <input type="checkbox" name="security_headers[redirect_http_to_https]" value="1" <?php checked( ! empty( $options['redirect_http_to_https'] ) ); ?>>
4300 + <?php esc_html_e( 'Send visitors arriving over HTTP to the HTTPS address', 'vigilante' ); ?>
3666 4301 </label>
4302 + <p class="description"><?php esc_html_e( 'Only applies when the site address is already an https:// one. On a site still published over HTTP it does nothing, so it cannot leave the site unreachable.', 'vigilante' ); ?></p>
3667 4303 </td>
3668 4304 </tr>
4305 + <tr>
4306 + <th scope="row"><?php esc_html_e( 'Fix Mixed Content', 'vigilante' ); ?></th>
4307 + <td>
4308 + <label>
4309 + <input type="checkbox" name="security_headers[fix_mixed_content]" value="1" <?php checked( ! empty( $options['fix_mixed_content'] ) ); ?>>
4310 + <?php esc_html_e( 'Rewrite this site http:// resources to https://', 'vigilante' ); ?>
4311 + </label>
4312 + <p class="description"><?php esc_html_e( 'Off by default. Only touches addresses of this same site, and only when the site is already served over HTTPS, so it cannot break an external resource. Useful right after moving a site to HTTPS, when old content still points at http:// addresses.', 'vigilante' ); ?></p>
4313 + </td>
4314 + </tr>
4315 + <tr id="field-upgrade-insecure-requests">
4316 + <th scope="row"><?php esc_html_e( 'Upgrade Insecure Requests', 'vigilante' ); ?></th>
4317 + <td>
4318 + <label>
4319 + <input type="checkbox" name="security_headers[upgrade_insecure_requests]" value="1" <?php checked( ! empty( $options['upgrade_insecure_requests'] ) ); ?>>
4320 + <?php esc_html_e( 'Ask browsers to upgrade every http:// request to https://', 'vigilante' ); ?>
4321 + </label>
4322 + <p class="description"><?php esc_html_e( '&#9888; Off by default. This one also covers resources hosted elsewhere: anything served from a domain with no HTTPS stops loading instead of loading insecurely. Turn it on once you know every external resource the site uses is available over HTTPS.', 'vigilante' ); ?></p>
4323 + </td>
4324 + </tr>
4325 + <tr>
4326 + <th scope="row"><?php esc_html_e( 'Rewrite Site Address on Activation', 'vigilante' ); ?></th>
4327 + <td>
4328 + <label>
4329 + <input type="checkbox" name="security_headers[force_https]" value="1" <?php checked( ! empty( $options['force_https'] ) ); ?>>
4330 + <?php esc_html_e( 'Change the WordPress and site addresses to https:// when the plugin is activated', 'vigilante' ); ?>
4331 + </label>
4332 + <p class="description"><?php esc_html_e( '&#9888; Off by default. This writes to the WordPress Address and Site Address settings, and turning the plugin off later does not undo it. It only runs on activation, and only when the site answers over HTTPS.', 'vigilante' ); ?></p>
4333 + </td>
4334 + </tr>
3669 4335 </table>
3670 4336
3671 - <h3><?php esc_html_e( 'Content Security Policy', 'vigilante' ); ?></h3>
4337 + <h3 id="vigilante-section-headers-hsts"><?php esc_html_e( 'HSTS (HTTP Strict Transport Security)', 'vigilante' ); ?></h3>
4338 + <?php $vig_home_https = ( 0 === strpos( (string) get_option( 'home' ), 'https://' ) ); ?>
4339 + <p class="description"><?php esc_html_e( 'Tells browsers to reach this site over HTTPS and never over HTTP, for as long as the max age below.', 'vigilante' ); ?></p>
4340 + <?php if ( ! $vig_home_https ) : ?>
4341 + <p class="description" style="color:#b32d2e"><strong><?php esc_html_e( 'Unavailable: the site address still starts with http://. Enabling HSTS on a site not published over HTTPS would make it unreachable in any browser that honours it.', 'vigilante' ); ?></strong></p>
4342 + <?php endif; ?>
3672 4343 <table class="form-table">
3673 4344 <tr>
3674 - <th scope="row"><?php esc_html_e( 'Enable CSP', 'vigilante' ); ?></th>
4345 + <th scope="row"><?php esc_html_e( 'Enable HSTS', 'vigilante' ); ?></th>
3675 4346 <td>
4347 + <?php if ( ! $vig_home_https ) : ?>
4348 + <?php /* A disabled checkbox is not submitted, and a boolean missing from the post is treated as unticked, so saving the tab would silently switch HSTS off. Carry the stored value instead. */ ?>
4349 + <input type="hidden" name="security_headers[hsts][enabled]" value="<?php echo ! empty( $options['hsts']['enabled'] ) ? '1' : '0'; ?>">
4350 + <?php endif; ?>
3676 4351 <label>
3677 - <input type="checkbox" name="security_headers[csp][enabled]" value="1" <?php checked( ! empty( $options['csp']['enabled'] ) ); ?>>
3678 - <?php esc_html_e( 'Enable Content Security Policy', 'vigilante' ); ?>
4352 + <input type="checkbox" name="security_headers[hsts][enabled]" value="1" <?php checked( ! empty( $options['hsts']['enabled'] ) ); ?> <?php disabled( ! $vig_home_https ); ?>>
4353 + <?php esc_html_e( 'Send the Strict-Transport-Security header', 'vigilante' ); ?>
3679 4354 </label>
4355 + <p class="description"><?php esc_html_e( '&#9888; Hard to undo: browsers remember it for the whole max age even if you turn it off later, so a site that loses its certificate stays unreachable until it expires. Start with a short max age.', 'vigilante' ); ?></p>
3680 4356 </td>
3681 4357 </tr>
3682 4358 <tr>
3683 - <th scope="row"><?php esc_html_e( 'Report Only Mode', 'vigilante' ); ?></th>
4359 + <th scope="row"><label for="vigilante-f-security-headers-hsts-max-age"><?php esc_html_e( 'Max Age', 'vigilante' ); ?></label></th>
3684 4360 <td>
4361 + <select id="vigilante-f-security-headers-hsts-max-age" name="security_headers[hsts][max_age]">
4362 + <option value="86400" <?php selected( $options['hsts']['max_age'] ?? 31536000, 86400 ); ?>><?php esc_html_e( '1 day (testing)', 'vigilante' ); ?></option>
4363 + <option value="2592000" <?php selected( $options['hsts']['max_age'] ?? 31536000, 2592000 ); ?>><?php esc_html_e( '30 days', 'vigilante' ); ?></option>
4364 + <option value="31536000" <?php selected( $options['hsts']['max_age'] ?? 31536000, 31536000 ); ?>><?php esc_html_e( '1 year (recommended)', 'vigilante' ); ?></option>
4365 + <option value="63072000" <?php selected( $options['hsts']['max_age'] ?? 31536000, 63072000 ); ?>><?php esc_html_e( '2 years', 'vigilante' ); ?></option>
4366 + </select>
4367 + </td>
4368 + </tr>
4369 + <tr>
4370 + <th scope="row"><?php esc_html_e( 'Include Subdomains', 'vigilante' ); ?></th>
4371 + <td>
3685 4372 <label>
3686 - <input type="checkbox" name="security_headers[csp][report_only]" value="1" <?php checked( ! empty( $options['csp']['report_only'] ) ); ?>>
3687 - <?php esc_html_e( 'Report violations without blocking (for testing)', 'vigilante' ); ?>
4373 + <input type="checkbox" name="security_headers[hsts][include_subdomains]" value="1" <?php checked( ! empty( $options['hsts']['include_subdomains'] ) ); ?>>
4374 + <?php esc_html_e( 'Apply HSTS to all subdomains', 'vigilante' ); ?>
3688 4375 </label>
3689 4376 </td>
3690 4377 </tr>
3691 4378 </table>
3692 4379
3693 - <h3><?php esc_html_e( 'Server Identity', 'vigilante' ); ?></h3>
4380 + <h3 id="vigilante-section-headers-fingerprint"><?php esc_html_e( 'Server Identity', 'vigilante' ); ?></h3>
3694 4381 <p class="description"><?php esc_html_e( 'Hide identifying information that servers expose in responses.', 'vigilante' ); ?></p>
3695 4382 <table class="form-table">
3696 4383 <tr>
3697 4384 <th scope="row"><?php esc_html_e( 'Server Signature', 'vigilante' ); ?></th>
@@ -3713,9 +4400,57 @@
3713 4400 </tr>
3714 4401 </table>
3715 4402 </div>
3716 4403
4404 + <?php $vg_cop = ( isset( $options['cross_origin_policies'] ) && is_array( $options['cross_origin_policies'] ) ) ? $options['cross_origin_policies'] : array(); ?>
4405 + <div id="vigilante-section-headers-cross-origin" class="vigilante-settings-section <?php echo $vg_shared_locked ? 'vigilante-form-disabled' : ''; ?>" <?php echo $vg_shared_locked ? 'inert' : ''; ?>>
4406 + <h2>
4407 + <?php esc_html_e( 'Cross-Origin Policies', 'vigilante' ); ?>
4408 + <span class="vigilante-method-badge htaccess"><?php esc_html_e( 'HTACCESS', 'vigilante' ); ?></span>
4409 + </h2>
4410 + <p><?php esc_html_e( 'Control how other origins may open, embed or fetch your site. Vigilant already sends these headers with the values below.', 'vigilante' ); ?></p>
4411 +
4412 + <table class="form-table">
4413 + <tr>
4414 + <th scope="row"><label for="vigilante-f-security-headers-coop"><?php esc_html_e( 'Cross-Origin-Opener-Policy (COOP)', 'vigilante' ); ?></label></th>
4415 + <td>
4416 + <select id="vigilante-f-security-headers-coop" name="security_headers[cross_origin_policies][opener_policy]">
4417 + <option value="" <?php selected( empty( $vg_cop['opener_policy'] ) ); ?>><?php esc_html_e( 'Disabled (header not sent)', 'vigilante' ); ?></option>
4418 + <option value="unsafe-none" <?php selected( $vg_cop['opener_policy'] ?? '', 'unsafe-none' ); ?>>unsafe-none</option>
4419 + <option value="same-origin-allow-popups" <?php selected( $vg_cop['opener_policy'] ?? '', 'same-origin-allow-popups' ); ?>><?php esc_html_e( 'same-origin-allow-popups (recommended)', 'vigilante' ); ?></option>
4420 + <option value="same-origin" <?php selected( $vg_cop['opener_policy'] ?? '', 'same-origin' ); ?>>same-origin</option>
4421 + </select>
4422 + <p class="description"><?php esc_html_e( '&#9432; Cuts the link between your site and a window from another origin that opened it. Side effect: external tools that open your site in a new tab and talk to it through window.opener, such as Google Tag Assistant, will report that they cannot connect. Pick unsafe-none or Disabled if you need those tools.', 'vigilante' ); ?></p>
4423 + </td>
4424 + </tr>
4425 + <tr>
4426 + <th scope="row"><label for="vigilante-f-security-headers-coep"><?php esc_html_e( 'Cross-Origin-Embedder-Policy (COEP)', 'vigilante' ); ?></label></th>
4427 + <td>
4428 + <select id="vigilante-f-security-headers-coep" name="security_headers[cross_origin_policies][embedder_policy]">
4429 + <option value="unsafe-none" <?php selected( ( $vg_cop['embedder_policy'] ?? 'unsafe-none' ), 'unsafe-none' ); ?>><?php esc_html_e( 'unsafe-none (header not sent)', 'vigilante' ); ?></option>
4430 + <option value="credentialless" <?php selected( $vg_cop['embedder_policy'] ?? '', 'credentialless' ); ?>>credentialless</option>
4431 + <option value="require-corp" <?php selected( $vg_cop['embedder_policy'] ?? '', 'require-corp' ); ?>>require-corp</option>
4432 + </select>
4433 + <p class="description"><?php esc_html_e( '&#9432; Requires every cross-origin resource to opt in. require-corp can block third-party images, fonts, videos and embeds that do not send their own CORP or CORS headers.', 'vigilante' ); ?></p>
4434 + </td>
4435 + </tr>
4436 + <tr>
4437 + <th scope="row"><label for="vigilante-f-security-headers-corp"><?php esc_html_e( 'Cross-Origin-Resource-Policy (CORP)', 'vigilante' ); ?></label></th>
4438 + <td>
4439 + <select id="vigilante-f-security-headers-corp" name="security_headers[cross_origin_policies][resource_policy]">
4440 + <option value="" <?php selected( empty( $vg_cop['resource_policy'] ) ); ?>><?php esc_html_e( 'Disabled (header not sent)', 'vigilante' ); ?></option>
4441 + <option value="same-site" <?php selected( $vg_cop['resource_policy'] ?? '', 'same-site' ); ?>>same-site</option>
4442 + <option value="same-origin" <?php selected( $vg_cop['resource_policy'] ?? '', 'same-origin' ); ?>>same-origin</option>
4443 + <option value="cross-origin" <?php selected( $vg_cop['resource_policy'] ?? '', 'cross-origin' ); ?>><?php esc_html_e( 'cross-origin (recommended)', 'vigilante' ); ?></option>
4444 + </select>
4445 + <p class="description"><?php esc_html_e( '&#9432; Declares who may load resources from this site. same-origin stops hotlinking, but it also breaks CDNs, feed readers and any external service that fetches your images or files.', 'vigilante' ); ?></p>
4446 + </td>
4447 + </tr>
4448 + </table>
4449 + </div>
4450 +
3717 4451 <p class="submit vigilante-submit-buttons">
4452 + <?php if ( ! $vg_shared_locked ) : ?>
3718 4453 <button type="submit" class="button button-primary vigilante-save-btn" data-original-text="<?php esc_attr_e( 'Save Settings', 'vigilante' ); ?>">
3719 4454 <?php esc_html_e( 'Save Settings', 'vigilante' ); ?>
3720 4455 </button>
3721 4456 <button type="button" class="button vigilante-reset-section-btn" data-original-text="<?php esc_attr_e( 'Reset to Defaults', 'vigilante' ); ?>">
@@ -3720,8 +4455,10 @@
3720 4455 </button>
3721 4456 <button type="button" class="button vigilante-reset-section-btn" data-original-text="<?php esc_attr_e( 'Reset to Defaults', 'vigilante' ); ?>">
3722 4457 <?php esc_html_e( 'Reset to Defaults', 'vigilante' ); ?>
3723 4458 </button>
4459 + <?php endif; ?>
4460 + <?php /* Testing what the server actually sends is read-only and useful from any site of a network. */ ?>
3724 4461 <button type="button" class="button vigilante-test-headers">
3725 4462 <?php esc_html_e( 'Test Headers', 'vigilante' ); ?>
3726 4463 </button>
3727 4464 </p>
@@ -3747,11 +4484,11 @@
3747 4484 <p><?php esc_html_e( 'Control access to WordPress REST API endpoints.', 'vigilante' ); ?></p>
3748 4485
3749 4486 <table class="form-table">
3750 4487 <tr>
3751 - <th scope="row"><?php esc_html_e( 'Access Mode', 'vigilante' ); ?></th>
4488 + <th scope="row"><label for="vigilante-f-rest-api-security-mode"><?php esc_html_e( 'Access Mode', 'vigilante' ); ?></label></th>
3752 4489 <td>
3753 - <select name="rest_api_security[mode]">
4490 + <select id="vigilante-f-rest-api-security-mode" name="rest_api_security[mode]">
3754 4491 <option value="open" <?php selected( $options['mode'] ?? 'selective', 'open' ); ?>><?php esc_html_e( 'Open - Allow all requests', 'vigilante' ); ?></option>
3755 4492 <option value="selective" <?php selected( $options['mode'] ?? 'selective', 'selective' ); ?>><?php esc_html_e( 'Selective - Protect sensitive endpoints', 'vigilante' ); ?></option>
3756 4493 <option value="authenticated_only" <?php selected( $options['mode'] ?? 'selective', 'authenticated_only' ); ?>><?php esc_html_e( 'Authenticated - Require login for all', 'vigilante' ); ?></option>
3757 4494 </select>
@@ -3830,14 +4567,14 @@
3830 4567 <?php
3831 4568 $pw_policy = wp_parse_args(
3832 4569 ( isset( $options['password_policy'] ) && is_array( $options['password_policy'] ) ) ? $options['password_policy'] : array(),
3833 4570 array(
3834 - 'require_uppercase' => true,
3835 - 'require_lowercase' => true,
3836 - 'require_number' => true,
3837 - 'require_special' => true,
4571 + 'require_uppercase' => false,
4572 + 'require_lowercase' => false,
4573 + 'require_number' => false,
4574 + 'require_special' => false,
3838 4575 'block_common' => true,
3839 - 'block_username' => false,
4576 + 'block_username' => true,
3840 4577 'affected_roles' => array(),
3841 4578 )
3842 4579 );
3843 4580 $pw_policy_roles = (array) $pw_policy['affected_roles'];
@@ -3851,11 +4588,11 @@
3851 4588 </label>
3852 4589 </td>
3853 4590 </tr>
3854 4591 <tr>
3855 - <th scope="row"><?php esc_html_e( 'Minimum Password Length', 'vigilante' ); ?></th>
4592 + <th scope="row"><label for="vigilante-f-user-security-min-password-length"><?php esc_html_e( 'Minimum Password Length', 'vigilante' ); ?></label></th>
3856 4593 <td>
3857 - <input type="number" name="user_security[min_password_length]" value="<?php echo esc_attr( $options['min_password_length'] ?? 12 ); ?>" min="6" max="32" class="small-text">
4594 + <input id="vigilante-f-user-security-min-password-length" type="number" name="user_security[min_password_length]" value="<?php echo esc_attr( $options['min_password_length'] ?? 12 ); ?>" min="6" max="32" class="small-text">
3858 4595 <?php esc_html_e( 'characters', 'vigilante' ); ?>
3859 4596 </td>
3860 4597 </tr>
3861 4598 <tr>
@@ -4008,11 +4745,11 @@
4008 4745 <p class="description"><?php esc_html_e( 'Disable on high-traffic sites to avoid email overload.', 'vigilante' ); ?></p>
4009 4746 </td>
4010 4747 </tr>
4011 4748 <tr>
4012 - <th scope="row"><?php esc_html_e( 'Auto-reject After', 'vigilante' ); ?></th>
4749 + <th scope="row"><label for="vigilante-f-user-security-registration-approval-auto-reject-days"><?php esc_html_e( 'Auto-reject After', 'vigilante' ); ?></label></th>
4013 4750 <td>
4014 - <input type="number" name="user_security[registration_approval][auto_reject_days]" value="<?php echo esc_attr( $registration['auto_reject_days'] ?? 0 ); ?>" min="0" max="365" class="small-text">
4751 + <input id="vigilante-f-user-security-registration-approval-auto-reject-days" type="number" name="user_security[registration_approval][auto_reject_days]" value="<?php echo esc_attr( $registration['auto_reject_days'] ?? 0 ); ?>" min="0" max="365" class="small-text">
4015 4752 <?php esc_html_e( 'days (0 = never)', 'vigilante' ); ?>
4016 4753 <p class="description"><?php esc_html_e( 'Automatically reject pending registrations after this many days.', 'vigilante' ); ?></p>
4017 4754 </td>
4018 4755 </tr>
@@ -4037,18 +4774,18 @@
4037 4774 </label>
4038 4775 </td>
4039 4776 </tr>
4040 4777 <tr>
4041 - <th scope="row"><?php esc_html_e( 'Maximum Sessions', 'vigilante' ); ?></th>
4778 + <th scope="row"><label for="vigilante-f-user-security-session-limits-max-sessions"><?php esc_html_e( 'Maximum Sessions', 'vigilante' ); ?></label></th>
4042 4779 <td>
4043 - <input type="number" name="user_security[session_limits][max_sessions]" value="<?php echo esc_attr( $session_limits['max_sessions'] ?? 3 ); ?>" min="1" max="10" class="small-text">
4780 + <input id="vigilante-f-user-security-session-limits-max-sessions" type="number" name="user_security[session_limits][max_sessions]" value="<?php echo esc_attr( $session_limits['max_sessions'] ?? 3 ); ?>" min="1" max="10" class="small-text">
4044 4781 <?php esc_html_e( 'sessions per user', 'vigilante' ); ?>
4045 4782 </td>
4046 4783 </tr>
4047 4784 <tr>
4048 - <th scope="row"><?php esc_html_e( 'When Limit Exceeded', 'vigilante' ); ?></th>
4785 + <th scope="row"><label for="vigilante-f-user-security-session-limits-behavior"><?php esc_html_e( 'When Limit Exceeded', 'vigilante' ); ?></label></th>
4049 4786 <td>
4050 - <select name="user_security[session_limits][behavior]">
4787 + <select id="vigilante-f-user-security-session-limits-behavior" name="user_security[session_limits][behavior]">
4051 4788 <option value="block_new" <?php selected( ( $session_limits['behavior'] ?? 'close_oldest' ), 'block_new' ); ?>><?php esc_html_e( 'Block new login', 'vigilante' ); ?></option>
4052 4789 <option value="close_oldest" <?php selected( ( $session_limits['behavior'] ?? 'close_oldest' ), 'close_oldest' ); ?>><?php esc_html_e( 'Close oldest session', 'vigilante' ); ?></option>
4053 4790 </select>
4054 4791 <p class="description"><?php esc_html_e( '"Close oldest" is recommended for security - ensures attackers cannot lock out legitimate users.', 'vigilante' ); ?></p>
@@ -4084,27 +4821,27 @@
4084 4821 </label>
4085 4822 </td>
4086 4823 </tr>
4087 4824 <tr>
4088 - <th scope="row"><?php esc_html_e( 'Expire After', 'vigilante' ); ?></th>
4825 + <th scope="row"><label for="vigilante-f-user-security-password-expiration-expire-days"><?php esc_html_e( 'Expire After', 'vigilante' ); ?></label></th>
4089 4826 <td>
4090 - <input type="number" name="user_security[password_expiration][expire_days]" value="<?php echo esc_attr( $password_exp['expire_days'] ?? 90 ); ?>" min="7" max="365" class="small-text">
4827 + <input id="vigilante-f-user-security-password-expiration-expire-days" type="number" name="user_security[password_expiration][expire_days]" value="<?php echo esc_attr( $password_exp['expire_days'] ?? 90 ); ?>" min="7" max="365" class="small-text">
4091 4828 <?php esc_html_e( 'days', 'vigilante' ); ?>
4092 4829 <p class="description"><?php esc_html_e( 'PCI-DSS recommends 90 days.', 'vigilante' ); ?></p>
4093 4830 </td>
4094 4831 </tr>
4095 4832 <tr>
4096 - <th scope="row"><?php esc_html_e( 'Warning Period', 'vigilante' ); ?></th>
4833 + <th scope="row"><label for="vigilante-f-user-security-password-expiration-warning-days"><?php esc_html_e( 'Warning Period', 'vigilante' ); ?></label></th>
4097 4834 <td>
4098 - <input type="number" name="user_security[password_expiration][warning_days]" value="<?php echo esc_attr( $password_exp['warning_days'] ?? 14 ); ?>" min="1" max="30" class="small-text">
4835 + <input id="vigilante-f-user-security-password-expiration-warning-days" type="number" name="user_security[password_expiration][warning_days]" value="<?php echo esc_attr( $password_exp['warning_days'] ?? 14 ); ?>" min="1" max="30" class="small-text">
4099 4836 <?php esc_html_e( 'days before expiration', 'vigilante' ); ?>
4100 4837 <p class="description"><?php esc_html_e( 'Show warning notice this many days before password expires.', 'vigilante' ); ?></p>
4101 4838 </td>
4102 4839 </tr>
4103 4840 <tr>
4104 - <th scope="row"><?php esc_html_e( 'Password History', 'vigilante' ); ?></th>
4841 + <th scope="row"><label for="vigilante-f-user-security-password-expiration-password-history"><?php esc_html_e( 'Password History', 'vigilante' ); ?></label></th>
4105 4842 <td>
4106 - <input type="number" name="user_security[password_expiration][password_history]" value="<?php echo esc_attr( $password_exp['password_history'] ?? 3 ); ?>" min="0" max="24" class="small-text">
4843 + <input id="vigilante-f-user-security-password-expiration-password-history" type="number" name="user_security[password_expiration][password_history]" value="<?php echo esc_attr( $password_exp['password_history'] ?? 3 ); ?>" min="0" max="24" class="small-text">
4107 4844 <?php esc_html_e( 'passwords to remember', 'vigilante' ); ?>
4108 4845 <p class="description"><?php esc_html_e( 'Prevent reusing recent passwords. Set to 0 to disable.', 'vigilante' ); ?></p>
4109 4846 </td>
4110 4847 </tr>
@@ -4186,11 +4923,11 @@
4186 4923 </label>
4187 4924 </td>
4188 4925 </tr>
4189 4926 <tr>
4190 - <th scope="row"><?php esc_html_e( 'Link Expiration', 'vigilante' ); ?></th>
4927 + <th scope="row"><label for="vigilante-f-user-security-email-verification-token-expiry-hours"><?php esc_html_e( 'Link Expiration', 'vigilante' ); ?></label></th>
4191 4928 <td>
4192 - <input type="number" name="user_security[email_verification][token_expiry_hours]" value="<?php echo esc_attr( $email_verify['token_expiry_hours'] ?? 24 ); ?>" min="1" max="168" class="small-text">
4929 + <input id="vigilante-f-user-security-email-verification-token-expiry-hours" type="number" name="user_security[email_verification][token_expiry_hours]" value="<?php echo esc_attr( $email_verify['token_expiry_hours'] ?? 24 ); ?>" min="1" max="168" class="small-text">
4193 4930 <?php esc_html_e( 'hours', 'vigilante' ); ?>
4194 4931 </td>
4195 4932 </tr>
4196 4933 <tr>
@@ -4202,11 +4939,11 @@
4202 4939 </label>
4203 4940 </td>
4204 4941 </tr>
4205 4942 <tr>
4206 - <th scope="row"><?php esc_html_e( 'Auto-delete Unverified', 'vigilante' ); ?></th>
4943 + <th scope="row"><label for="vigilante-f-user-security-email-verification-auto-delete-days"><?php esc_html_e( 'Auto-delete Unverified', 'vigilante' ); ?></label></th>
4207 4944 <td>
4208 - <input type="number" name="user_security[email_verification][auto_delete_days]" value="<?php echo esc_attr( $email_verify['auto_delete_days'] ?? 7 ); ?>" min="0" max="365" class="small-text">
4945 + <input id="vigilante-f-user-security-email-verification-auto-delete-days" type="number" name="user_security[email_verification][auto_delete_days]" value="<?php echo esc_attr( $email_verify['auto_delete_days'] ?? 7 ); ?>" min="0" max="365" class="small-text">
4209 4946 <?php esc_html_e( 'days (0 = never)', 'vigilante' ); ?>
4210 4947 <p class="description"><?php esc_html_e( 'Automatically delete users who never verify their email.', 'vigilante' ); ?></p>
4211 4948 </td>
4212 4949 </tr>
@@ -4230,8 +4967,11 @@
4230 4967 <h2 class="vigilante-tools-header">
4231 4968 <?php esc_html_e( 'User security tools', 'vigilante' ); ?>
4232 4969 </h2>
4233 4970
4971 + <?php $this->render_user_actions_notice(); ?>
4972 + <?php if ( ! $this->user_actions_locked() ) : ?>
4973 +
4234 4974 <!-- Force Password Reset -->
4235 4975 <div class="vigilante-tool-box">
4236 4976 <h3><?php esc_html_e( 'Force password reset', 'vigilante' ); ?></h3>
4237 4977 <p class="description"><?php esc_html_e( 'Force users to reset their password. Useful after a security incident. Users will receive an email with a reset link.', 'vigilante' ); ?></p>
@@ -4367,9 +5107,9 @@
4367 5107 <?php
4368 5108 $user_security = new Vigilante_User_Security( $this->settings, $this->activity_log );
4369 5109 $pending_users = $user_security->get_pending_users();
4370 5110 ?>
4371 - <div class="vigilante-tool-box vigilante-pending-users-section">
5111 + <div id="vigilante-section-users-pending" class="vigilante-tool-box vigilante-pending-users-section">
4372 5112 <h3>
4373 5113 <?php esc_html_e( 'Pending registrations', 'vigilante' ); ?>
4374 5114 <?php if ( count( $pending_users ) > 0 ) : ?>
4375 5115 <span class="vigilante-badge vigilante-badge-warning"><?php echo esc_html( count( $pending_users ) ); ?></span>
@@ -4386,8 +5126,9 @@
4386 5126 <span class="dashicons dashicons-yes-alt"></span>
4387 5127 <p><?php esc_html_e( 'No pending registrations.', 'vigilante' ); ?></p>
4388 5128 </div>
4389 5129 <?php else : ?>
5130 + <?php $this->render_user_actions_notice(); ?>
4390 5131 <table class="wp-list-table widefat fixed striped vigilante-pending-users-table">
4391 5132 <thead>
4392 5133 <tr>
4393 5134 <th><?php esc_html_e( 'User', 'vigilante' ); ?></th>
@@ -4416,12 +5157,12 @@
4416 5157 }
4417 5158 ?>
4418 5159 </td>
4419 5160 <td>
4420 - <button type="button" class="button button-small vigilante-approve-user" data-user-id="<?php echo esc_attr( $pending_user->ID ); ?>">
5161 + <button type="button" class="button button-small vigilante-approve-user" data-user-id="<?php echo esc_attr( $pending_user->ID ); ?>" <?php disabled( $this->user_actions_locked() ); ?>>
4421 5162 <?php esc_html_e( 'Approve', 'vigilante' ); ?>
4422 5163 </button>
4423 - <button type="button" class="button button-small vigilante-reject-user" data-user-id="<?php echo esc_attr( $pending_user->ID ); ?>" style="color: #d63638;">
5164 + <button type="button" class="button button-small vigilante-reject-user" data-user-id="<?php echo esc_attr( $pending_user->ID ); ?>" style="color: #d63638;" <?php disabled( $this->user_actions_locked() ); ?>>
4424 5165 <?php esc_html_e( 'Reject', 'vigilante' ); ?>
4425 5166 </button>
4426 5167 </td>
4427 5168 </tr>
@@ -4543,8 +5284,10 @@
4543 5284 </button>
4544 5285 </p>
4545 5286 </div>
4546 5287 </div>
5288 +
5289 + <?php endif; ?>
4547 5290 </div>
4548 5291 <?php
4549 5292 }
4550 5293
@@ -4556,9 +5299,11 @@
4556 5299 $options = $this->settings->get_section( 'wp_hardening' );
4557 5300 ?>
4558 5301 <form class="vigilante-settings-form <?php echo $is_disabled ? 'vigilante-form-disabled' : ''; ?>" data-section="wp_hardening" <?php echo $is_disabled ? 'inert' : ''; ?>>
4559 5302 <!-- Database Hardening (outside form save flow - uses its own AJAX action) -->
4560 - <div id="vigilante-section-hardening-database" class="vigilante-settings-section">
5303 + <?php $vg_shared_locked = $this->shared_files_locked(); ?>
5304 + <?php $this->render_shared_files_notice(); ?>
5305 + <div id="vigilante-section-hardening-database" class="vigilante-settings-section <?php echo $vg_shared_locked ? 'vigilante-form-disabled' : ''; ?>" <?php echo $vg_shared_locked ? 'inert' : ''; ?>>
4561 5306 <h2>
4562 5307 <?php esc_html_e( 'Database Hardening', 'vigilante' ); ?>
4563 5308 <span class="vigilante-method-badge database"><?php esc_html_e( 'Database', 'vigilante' ); ?></span>
4564 5309 <span class="vigilante-method-badge config"><?php esc_html_e( 'WP-CONFIG', 'vigilante' ); ?></span>
@@ -4570,8 +5315,14 @@
4570 5315 $current_prefix = $db_prefix->get_current_prefix();
4571 5316 $is_default = $db_prefix->is_default_prefix();
4572 5317 ?>
4573 5318
5319 + <?php if ( is_multisite() && ! $vg_shared_locked ) : ?>
5320 + <div class="notice notice-warning inline" style="margin:10px 0 16px;padding:8px 12px;">
5321 + <p style="margin:0;"><?php esc_html_e( 'Network-wide operation: it renames the tables of every site in the network and rewrites the wp-config.php they all share. Back up the whole database first, not just the main site.', 'vigilante' ); ?></p>
5322 + </div>
5323 + <?php endif; ?>
5324 +
4574 5325 <table class="form-table">
4575 5326 <tr>
4576 5327 <th scope="row"><?php esc_html_e( 'Current prefix', 'vigilante' ); ?></th>
4577 5328 <td>
@@ -4626,9 +5377,16 @@
4626 5377 </table>
4627 5378 </div>
4628 5379
4629 5380 <!-- wp-config Security -->
4630 - <div id="vigilante-section-hardening-wpconfig" class="vigilante-settings-section">
5381 + <?php
5382 + $vg_shared_locked = $this->shared_files_locked();
5383 + // Paint what is actually in force, not this site's unused copy.
5384 + $vg_local_options = $options;
5385 + $options = $this->get_section_for_display( 'wp_hardening' );
5386 + ?>
5387 + <?php $this->render_shared_files_notice(); ?>
5388 + <div id="vigilante-section-hardening-wpconfig" class="vigilante-settings-section <?php echo $vg_shared_locked ? 'vigilante-form-disabled' : ''; ?>" <?php echo $vg_shared_locked ? 'inert' : ''; ?>>
4631 5389 <h2>
4632 5390 <?php esc_html_e( 'wp-config.php Security', 'vigilante' ); ?>
4633 5391 <span class="vigilante-method-badge config"><?php esc_html_e( 'WP-CONFIG', 'vigilante' ); ?></span>
4634 5392 </h2>
@@ -4693,10 +5451,40 @@
4693 5451 </td>
4694 5452 </tr>
4695 5453 </table>
4696 5454 </div>
5455 + <?php $options = $vg_local_options; ?>
4697 5456
4698 5457 <!-- Comment Security -->
5458 + <div id="vigilante-section-hardening-xmlrpc" class="vigilante-settings-section">
5459 + <h2>
5460 + <?php esc_html_e( 'XML-RPC', 'vigilante' ); ?>
5461 + <span class="vigilante-method-badge php"><?php esc_html_e( 'PHP', 'vigilante' ); ?></span>
5462 + </h2>
5463 + <p><?php esc_html_e( 'The legacy remote interface. It is what the WordPress mobile app, Jetpack and remote managers talk to, and also the way pingback amplification and password guessing reach a site.', 'vigilante' ); ?></p>
5464 +
5465 + <table class="form-table">
5466 + <tr id="field-disable-xmlrpc">
5467 + <th scope="row"><label for="vigilante-f-wp-hardening-xmlrpc-mode"><?php esc_html_e( 'XML-RPC access', 'vigilante' ); ?></label></th>
5468 + <td>
5469 + <?php $vig_xmlrpc_mode = Vigilante_Comment_Security::resolve_xmlrpc_mode( $this->settings ); ?>
5470 + <select id="vigilante-f-wp-hardening-xmlrpc-mode" name="wp_hardening[xmlrpc_mode]">
5471 + <option value="none" <?php selected( $vig_xmlrpc_mode, 'none' ); ?>>
5472 + <?php esc_html_e( 'Leave XML-RPC enabled', 'vigilante' ); ?>
5473 + </option>
5474 + <option value="pingback" <?php selected( $vig_xmlrpc_mode, 'pingback' ); ?>>
5475 + <?php esc_html_e( 'Block the pingback methods only', 'vigilante' ); ?>
5476 + </option>
5477 + <option value="full" <?php selected( $vig_xmlrpc_mode, 'full' ); ?>>
5478 + <?php esc_html_e( 'Disable XML-RPC completely (recommended)', 'vigilante' ); ?>
5479 + </option>
5480 + </select>
5481 + <p class="description"><?php esc_html_e( 'Disable it completely unless something still needs it, such as the WordPress mobile app, Jetpack or a remote manager; in that case block only the pingback methods, which closes the amplification vector and leaves the rest working. Pingbacks are also covered by the Comment Security setting just below, which additionally closes them for comments.', 'vigilante' ); ?></p>
5482 + </td>
5483 + </tr>
5484 + </table>
5485 + </div>
5486 +
4699 5487 <div id="vigilante-section-hardening-comments" class="vigilante-settings-section">
4700 5488 <h2>
4701 5489 <?php esc_html_e( 'Comment Security', 'vigilante' ); ?>
4702 5490 <span class="vigilante-method-badge php"><?php esc_html_e( 'PHP', 'vigilante' ); ?></span>
@@ -4738,10 +5526,10 @@
4738 5526 <label>
4739 5527 <input type="checkbox" name="wp_hardening[close_old_comments]" value="1" <?php checked( ! empty( $options['close_old_comments'] ) ); ?>>
4740 5528 <?php esc_html_e( 'Automatically close comments on old posts after', 'vigilante' ); ?>
4741 5529 </label>
4742 - <input type="number" name="wp_hardening[close_comments_after_days]" value="<?php echo esc_attr( $options['close_comments_after_days'] ?? 30 ); ?>" min="1" max="365" class="small-text">
4743 - <?php esc_html_e( 'days', 'vigilante' ); ?>
5530 + <input type="number" id="vigilante-f-wp-hardening-close-comments-after-days" name="wp_hardening[close_comments_after_days]" value="<?php echo esc_attr( $options['close_comments_after_days'] ?? 30 ); ?>" min="1" max="365" class="small-text">
5531 + <label for="vigilante-f-wp-hardening-close-comments-after-days"><?php esc_html_e( 'days', 'vigilante' ); ?></label>
4744 5532 </td>
4745 5533 </tr>
4746 5534 <tr>
4747 5535 <th scope="row"><?php esc_html_e( 'Honeypot Protection', 'vigilante' ); ?></th>
@@ -4893,13 +5681,13 @@
4893 5681 <table class="form-table">
4894 5682 <tr>
4895 5683 <th scope="row"><?php esc_html_e( 'Retention', 'vigilante' ); ?></th>
4896 5684 <td>
4897 - <input type="number" name="activity_log[retention_days]" value="<?php echo esc_attr( $options['retention_days'] ?? 30 ); ?>" min="7" max="365" class="small-text">
4898 - <?php esc_html_e( 'days', 'vigilante' ); ?>
5685 + <input type="number" id="vigilante-f-activity-log-retention-days" name="activity_log[retention_days]" value="<?php echo esc_attr( $options['retention_days'] ?? 30 ); ?>" min="7" max="365" class="small-text">
5686 + <label for="vigilante-f-activity-log-retention-days"><?php esc_html_e( 'days', 'vigilante' ); ?></label>
4899 5687 &nbsp;&nbsp;
4900 - <input type="number" name="activity_log[max_entries]" value="<?php echo esc_attr( $options['max_entries'] ?? 10000 ); ?>" min="100" max="100000" step="100" class="small-text">
4901 - <?php esc_html_e( 'max entries', 'vigilante' ); ?>
5688 + <input type="number" id="vigilante-f-activity-log-max-entries" name="activity_log[max_entries]" value="<?php echo esc_attr( $options['max_entries'] ?? 10000 ); ?>" min="100" max="100000" step="100" class="small-text">
5689 + <label for="vigilante-f-activity-log-max-entries"><?php esc_html_e( 'max entries', 'vigilante' ); ?></label>
4902 5690 <p class="description"><?php esc_html_e( 'Whichever limit is reached first takes effect. Changes apply immediately on save; daily maintenance also enforces these limits automatically.', 'vigilante' ); ?></p>
4903 5691 </td>
4904 5692 </tr>
4905 5693 <tr>
@@ -4924,14 +5712,14 @@
4924 5712 </div>
4925 5713 </td>
4926 5714 </tr>
4927 5715 <tr>
4928 - <th scope="row"><?php esc_html_e( 'Option Tracking', 'vigilante' ); ?></th>
5716 + <th scope="row"><label for="vigilante-f-activity-log-tracked-options"><?php esc_html_e( 'Option Tracking', 'vigilante' ); ?></label></th>
4929 5717 <td>
4930 5718 <p class="description" style="margin-top:0;"><?php esc_html_e( 'When "WordPress option changes" is enabled, Vigilant tracks ~30 core WordPress settings (site URL, admin email, registration, active plugins, theme, comments, privacy, etc.). Use the field below to track additional options from other plugins.', 'vigilante' ); ?></p>
4931 5719 <br>
4932 5720 <label><?php esc_html_e( 'Additional options to track:', 'vigilante' ); ?></label><br>
4933 - <textarea name="activity_log[tracked_options]" rows="3" cols="50" class="regular-text code" placeholder="woocommerce_&#10;seopress_&#10;wpforms_"><?php echo esc_textarea( implode( "\n", $options['tracked_options'] ?? array() ) ); ?></textarea>
5721 + <textarea id="vigilante-f-activity-log-tracked-options" name="activity_log[tracked_options]" rows="3" cols="50" class="regular-text code" placeholder="woocommerce_&#10;seopress_&#10;wpforms_"><?php echo esc_textarea( implode( "\n", $options['tracked_options'] ?? array() ) ); ?></textarea>
4934 5722 <p class="description"><?php esc_html_e( 'One option name per line. Use a trailing underscore to match all options with that prefix (e.g. "woocommerce_" tracks all WooCommerce settings).', 'vigilante' ); ?></p>
4935 5723 </td>
4936 5724 </tr>
4937 5725 <tr>
@@ -4938,15 +5726,15 @@
4938 5726 <th scope="row"><?php esc_html_e( 'Exclusions', 'vigilante' ); ?></th>
4939 5727 <td>
4940 5728 <div style="display:grid; grid-template-columns:repeat(auto-fit, minmax(220px, 1fr)); gap:16px; max-width:600px;">
4941 5729 <div>
4942 - <label><?php esc_html_e( 'Excluded user IDs:', 'vigilante' ); ?></label><br>
4943 - <textarea name="activity_log[excluded_users]" rows="3" cols="25"><?php echo esc_textarea( implode( "\n", $options['excluded_users'] ?? array() ) ); ?></textarea>
5730 + <label for="vigilante-f-activity-log-excluded-users"><?php esc_html_e( 'Excluded user IDs:', 'vigilante' ); ?></label><br>
5731 + <textarea id="vigilante-f-activity-log-excluded-users" name="activity_log[excluded_users]" rows="3" cols="25"><?php echo esc_textarea( implode( "\n", $options['excluded_users'] ?? array() ) ); ?></textarea>
4944 5732 <p class="description"><?php esc_html_e( 'One user ID per line. Actions by these users will not be logged.', 'vigilante' ); ?></p>
4945 5733 </div>
4946 5734 <div>
4947 - <label><?php esc_html_e( 'Excluded IPs:', 'vigilante' ); ?></label><br>
4948 - <textarea name="activity_log[excluded_ips]" rows="3" cols="25"><?php echo esc_textarea( implode( "\n", $options['excluded_ips'] ?? array() ) ); ?></textarea>
5735 + <label for="vigilante-f-activity-log-excluded-ips"><?php esc_html_e( 'Excluded IPs:', 'vigilante' ); ?></label><br>
5736 + <textarea id="vigilante-f-activity-log-excluded-ips" name="activity_log[excluded_ips]" rows="3" cols="25"><?php echo esc_textarea( implode( "\n", $options['excluded_ips'] ?? array() ) ); ?></textarea>
4949 5737 <p class="description"><?php esc_html_e( 'One IP per line. Requests from these IPs will not be logged.', 'vigilante' ); ?></p>
4950 5738 </div>
4951 5739 </div>
4952 5740 </td>
@@ -4995,11 +5783,11 @@
4995 5783 <p class="description"><?php esc_html_e( 'Sends one email per event type, then waits for the cooldown below before repeating, so a burst of the same event is a single notice.', 'vigilante' ); ?></p>
4996 5784 </td>
4997 5785 </tr>
4998 5786 <tr>
4999 - <th scope="row"><?php esc_html_e( 'Alert on severity', 'vigilante' ); ?></th>
5787 + <th scope="row"><label for="vigilante-f-audit-alerts-immediate-min-severity"><?php esc_html_e( 'Alert on severity', 'vigilante' ); ?></label></th>
5000 5788 <td>
5001 - <select name="audit_alerts[immediate][min_severity]">
5789 + <select id="vigilante-f-audit-alerts-immediate-min-severity" name="audit_alerts[immediate][min_severity]">
5002 5790 <option value="critical" <?php selected( $alert_severity, 'critical' ); ?>><?php esc_html_e( 'Critical only (recommended)', 'vigilante' ); ?></option>
5003 5791 <option value="warning" <?php selected( $alert_severity, 'warning' ); ?>><?php esc_html_e( 'Warning and Critical', 'vigilante' ); ?></option>
5004 5792 </select>
5005 5793 <p class="description"><?php esc_html_e( 'A new administrator, a closed plugin or a privilege escalation are all logged as Critical, so "Critical only" already covers them.', 'vigilante' ); ?></p>
@@ -5015,11 +5803,11 @@
5015 5803 <p class="description"><?php esc_html_e( 'Catches an attack in progress, e.g. hundreds of firewall blocks or login failures in an hour.', 'vigilante' ); ?></p>
5016 5804 </td>
5017 5805 </tr>
5018 5806 <tr>
5019 - <th scope="row"><?php esc_html_e( 'Time window', 'vigilante' ); ?></th>
5807 + <th scope="row"><label for="vigilante-f-audit-alerts-threshold-window"><?php esc_html_e( 'Time window', 'vigilante' ); ?></label></th>
5020 5808 <td>
5021 - <select name="audit_alerts[threshold][window]">
5809 + <select id="vigilante-f-audit-alerts-threshold-window" name="audit_alerts[threshold][window]">
5022 5810 <option value="30m" <?php selected( $alert_window, '30m' ); ?>><?php esc_html_e( '30 minutes', 'vigilante' ); ?></option>
5023 5811 <option value="1h" <?php selected( $alert_window, '1h' ); ?>><?php esc_html_e( '1 hour', 'vigilante' ); ?></option>
5024 5812 <option value="6h" <?php selected( $alert_window, '6h' ); ?>><?php esc_html_e( '6 hours', 'vigilante' ); ?></option>
5025 5813 <option value="24h" <?php selected( $alert_window, '24h' ); ?>><?php esc_html_e( '24 hours', 'vigilante' ); ?></option>
@@ -5046,10 +5834,10 @@
5046 5834 </tr>
5047 5835 <tr>
5048 5836 <th scope="row"><?php esc_html_e( "Don't repeat alerts", 'vigilante' ); ?></th>
5049 5837 <td>
5050 - <input type="number" name="audit_alerts[cooldown_minutes]" value="<?php echo esc_attr( isset( $alerts['cooldown_minutes'] ) ? (int) $alerts['cooldown_minutes'] : 60 ); ?>" min="0" max="1440" class="small-text">
5051 - <?php esc_html_e( 'minutes', 'vigilante' ); ?>
5838 + <input type="number" id="vigilante-f-audit-alerts-cooldown-minutes" name="audit_alerts[cooldown_minutes]" value="<?php echo esc_attr( isset( $alerts['cooldown_minutes'] ) ? (int) $alerts['cooldown_minutes'] : 60 ); ?>" min="0" max="1440" class="small-text">
5839 + <label for="vigilante-f-audit-alerts-cooldown-minutes"><?php esc_html_e( 'minutes', 'vigilante' ); ?></label>
5052 5840 <p class="description"><?php esc_html_e( 'After an alert, Vigilant waits this long before sending another about the same thing: the same event type for immediate alerts, or the same category for threshold alerts. This prevents a flood during a sustained attack. Applies to both alert types above.', 'vigilante' ); ?></p>
5053 5841 </td>
5054 5842 </tr>
5055 5843
@@ -5122,10 +5910,10 @@
5122 5910 $ua_blacklist = $firewall_options['ua_blacklist'] ?? array();
5123 5911 ?>
5124 5912
5125 5913 <div class="vigilante-log-filters">
5126 - <input type="text" id="vigilante-log-search" size="1" placeholder="<?php esc_attr_e( 'Search logs (min. 3 characters)...', 'vigilante' ); ?>" class="vigilante-log-search-input">
5127 - <select id="vigilante-log-type-filter">
5914 + <input type="text" id="vigilante-log-search" aria-label="<?php esc_attr_e( 'Search the activity log', 'vigilante' ); ?>" size="1" placeholder="<?php esc_attr_e( 'Search logs (min. 3 characters)...', 'vigilante' ); ?>" class="vigilante-log-search-input">
5915 + <select id="vigilante-log-type-filter" aria-label="<?php esc_attr_e( 'Filter the log by event type', 'vigilante' ); ?>">
5128 5916 <option value=""><?php esc_html_e( 'All Types', 'vigilante' ); ?></option>
5129 5917 <option value="login"><?php esc_html_e( 'Login', 'vigilante' ); ?></option>
5130 5918 <option value="user"><?php esc_html_e( 'User', 'vigilante' ); ?></option>
5131 5919 <option value="content"><?php esc_html_e( 'Content', 'vigilante' ); ?></option>
@@ -5138,15 +5926,15 @@
5138 5926 <option value="file"><?php esc_html_e( 'File', 'vigilante' ); ?></option>
5139 5927 <option value="security"><?php esc_html_e( 'Security', 'vigilante' ); ?></option>
5140 5928 <option value="system"><?php esc_html_e( 'System', 'vigilante' ); ?></option>
5141 5929 </select>
5142 - <select id="vigilante-log-severity-filter">
5930 + <select id="vigilante-log-severity-filter" aria-label="<?php esc_attr_e( 'Filter the log by severity', 'vigilante' ); ?>">
5143 5931 <option value=""><?php esc_html_e( 'All Severities', 'vigilante' ); ?></option>
5144 5932 <option value="info"><?php esc_html_e( 'Info', 'vigilante' ); ?></option>
5145 5933 <option value="warning"><?php esc_html_e( 'Warning', 'vigilante' ); ?></option>
5146 5934 <option value="critical"><?php esc_html_e( 'Critical', 'vigilante' ); ?></option>
5147 5935 </select>
5148 - <select id="vigilante-log-method-filter">
5936 + <select id="vigilante-log-method-filter" aria-label="<?php esc_attr_e( 'Filter the log by HTTP method', 'vigilante' ); ?>">
5149 5937 <option value=""><?php esc_html_e( 'All Methods', 'vigilante' ); ?></option>
5150 5938 <option value="GET">GET</option>
5151 5939 <option value="POST">POST</option>
5152 5940 <option value="PUT">PUT</option>
@@ -5213,8 +6001,9 @@
5213 6001 'user' => (string) ( $log->user_login ?? '' ),
5214 6002 'ip' => $ip_val,
5215 6003 'user_agent' => $ua_val,
5216 6004 'request_method' => (string) $request_method,
6005 + 'request_uri' => Vigilante_Activity_Log::extract_request_uri( $log->extra_data ?? '' ),
5217 6006 'date' => (string) ( $log->created_at ?? '' ),
5218 6007 'severity' => (string) ( $log->severity ?? 'info' ),
5219 6008 'is_ip_whitelisted' => ( '' !== $ip_val && in_array( $ip_val, $ip_whitelist, true ) ),
5220 6009 'is_ip_blacklisted' => ( '' !== $ip_val && in_array( $ip_val, $ip_blacklist, true ) ),
@@ -5267,8 +6056,13 @@
5267 6056 */
5268 6057 private function render_tab_file_integrity() {
5269 6058 $is_disabled = $this->render_module_disabled_notice( 'file_integrity' );
5270 6059 $options = $this->settings->get_section( 'file_integrity' );
6060 + // On the main site of a network the critical-file scan is the network's
6061 + // canary for a change to wp-config.php or the root .htaccess, so a
6062 + // main-site admin without network rights cannot turn it off. Since
6063 + // 2.11.8; see Vigilante_Settings::get_main_site_file_settings().
6064 + $vg_main_locked = $this->main_site_files_locked();
5271 6065 $last_scan = get_option( 'vigilante_last_integrity_scan' );
5272 6066 $last_results = get_option( 'vigilante_last_integrity_results' );
5273 6067 $ignored_files = get_option( 'vigilante_ignored_files', array() );
5274 6068
@@ -5321,11 +6115,11 @@
5321 6115 </label>
5322 6116 </td>
5323 6117 </tr>
5324 6118 <tr>
5325 - <th scope="row"><?php esc_html_e( 'Scan Frequency', 'vigilante' ); ?></th>
6119 + <th scope="row"><label for="vigilante-f-file-integrity-scan-frequency"><?php esc_html_e( 'Scan Frequency', 'vigilante' ); ?></label></th>
5326 6120 <td>
5327 - <select name="file_integrity[scan_frequency]">
6121 + <select id="vigilante-f-file-integrity-scan-frequency" name="file_integrity[scan_frequency]">
5328 6122 <option value="daily" <?php selected( $options['scan_frequency'] ?? 'daily', 'daily' ); ?>><?php esc_html_e( 'Daily', 'vigilante' ); ?></option>
5329 6123 <option value="weekly" <?php selected( $options['scan_frequency'] ?? 'daily', 'weekly' ); ?>><?php esc_html_e( 'Weekly', 'vigilante' ); ?></option>
5330 6124 </select>
5331 6125 </td>
@@ -5330,11 +6124,11 @@
5330 6124 </select>
5331 6125 </td>
5332 6126 </tr>
5333 6127 <tr>
5334 - <th scope="row"><?php esc_html_e( 'Email Notifications', 'vigilante' ); ?></th>
6128 + <th scope="row"><label for="vigilante-f-file-integrity-notify-level"><?php esc_html_e( 'Email Notifications', 'vigilante' ); ?></label></th>
5335 6129 <td>
5336 - <select name="file_integrity[notify_level]">
6130 + <select id="vigilante-f-file-integrity-notify-level" name="file_integrity[notify_level]">
5337 6131 <option value="all" <?php selected( $notify_level, 'all' ); ?>><?php esc_html_e( 'All issues (modified + suspicious)', 'vigilante' ); ?></option>
5338 6132 <option value="suspicious_only" <?php selected( $notify_level, 'suspicious_only' ); ?>><?php esc_html_e( 'Suspicious files only', 'vigilante' ); ?></option>
5339 6133 <option value="disabled" <?php selected( $notify_level, 'disabled' ); ?>><?php esc_html_e( 'Disabled', 'vigilante' ); ?></option>
5340 6134 </select>
@@ -5394,10 +6188,13 @@
5394 6188 <?php esc_html_e( 'Uploads directory (detect PHP files, double extensions, .htaccess)', 'vigilante' ); ?>
5395 6189 </label>
5396 6190 <br>
5397 6191 <label>
5398 - <input type="checkbox" name="file_integrity[scan_critical_config]" value="1" <?php checked( $options['scan_critical_config'] ?? true ); ?>>
6192 + <input type="checkbox" name="file_integrity[scan_critical_config]" value="1" <?php disabled( $vg_main_locked ); ?> <?php checked( $options['scan_critical_config'] ?? true ); ?>>
5399 6193 <?php esc_html_e( 'Critical config files (wp-config.php, .htaccess baseline monitoring)', 'vigilante' ); ?>
6194 + <?php if ( $vg_main_locked ) : ?>
6195 + <span class="description" style="display:block;margin-left:24px;"><?php echo esc_html( Vigilante_Settings::get_shared_files_notice() ); ?></span>
6196 + <?php endif; ?>
5400 6197 </label>
5401 6198 <br>
5402 6199 <label>
5403 6200 <input type="checkbox" name="file_integrity[check_closed_plugins]" value="1" <?php checked( $options['check_closed_plugins'] ?? true ); ?>>
@@ -5406,19 +6203,30 @@
5406 6203 </fieldset>
5407 6204 </td>
5408 6205 </tr>
5409 6206 <tr>
5410 - <th scope="row"><?php esc_html_e( 'Excluded Paths', 'vigilante' ); ?></th>
6207 + <th scope="row"><label for="vigilante-f-file-integrity-excluded-paths"><?php esc_html_e( 'Excluded Paths', 'vigilante' ); ?></label></th>
5411 6208 <td>
5412 - <textarea name="file_integrity[excluded_paths]" rows="4" class="large-text code" placeholder="wp-content/cache&#10;wp-content/languages"><?php echo esc_textarea( implode( "\n", $options['excluded_paths'] ?? array() ) ); ?></textarea>
5413 - <p class="description"><?php esc_html_e( 'One path per line (relative to WordPress root). Files within these paths will be skipped during scans.', 'vigilante' ); ?></p>
6209 + <textarea id="vigilante-f-file-integrity-excluded-paths" name="file_integrity[excluded_paths]" rows="4" class="large-text code" placeholder="wp-content/cache&#10;wp-content/languages"><?php echo esc_textarea( implode( "\n", $options['excluded_paths'] ?? array() ) ); ?></textarea>
6210 + <p class="description"><?php esc_html_e( 'One path per line, relative to the WordPress root. A path such as wp-content/cache excludes exactly that folder and everything under it. A name on its own, such as cache, excludes any folder called exactly that, wherever it is.', 'vigilante' ); ?></p>
5414 6211 </td>
5415 6212 </tr>
5416 6213 <tr>
5417 - <th scope="row"><?php esc_html_e( 'Excluded Extensions', 'vigilante' ); ?></th>
6214 + <th scope="row"><label for="vigilante-f-file-integrity-excluded-extensions"><?php esc_html_e( 'Excluded Extensions', 'vigilante' ); ?></label></th>
5418 6215 <td>
5419 - <textarea name="file_integrity[excluded_extensions]" rows="3" class="large-text code" placeholder=".log&#10;.po&#10;.mo&#10;.pot"><?php echo esc_textarea( implode( "\n", $options['excluded_extensions'] ?? array() ) ); ?></textarea>
5420 - <p class="description"><?php esc_html_e( 'One extension per line (e.g. .log, .po, .mo). Files with these extensions will be skipped. Useful to avoid false positives from translation or log files.', 'vigilante' ); ?></p>
6216 + <textarea id="vigilante-f-file-integrity-excluded-extensions" name="file_integrity[excluded_extensions]" rows="3" class="large-text code" placeholder=".log&#10;.po&#10;.mo&#10;.pot"><?php echo esc_textarea( implode( "\n", $options['excluded_extensions'] ?? array() ) ); ?></textarea>
6217 + <p class="description">
6218 + <?php esc_html_e( 'One extension per line (e.g. .log, .po, .mo). Files with these extensions will be skipped. Useful to avoid false positives from translation or log files.', 'vigilante' ); ?>
6219 + <br>
6220 + <?php
6221 + printf(
6222 + /* translators: 1: opening <code>, 2: closing </code>. Placeholders wrap the scoped-extension example. */
6223 + esc_html__( 'An extension on its own applies to the whole site. To limit it to one folder, write it as %1$swp-content/languages/*.json%2$s, which leaves the same extension watched everywhere else.', 'vigilante' ),
6224 + '<code>',
6225 + '</code>'
6226 + ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- HTML tags are hardcoded.
6227 + ?>
6228 + </p>
5421 6229 </td>
5422 6230 </tr>
5423 6231 </table>
5424 6232 </div>
@@ -5665,9 +6473,15 @@
5665 6473 $crit_diff = $crit_item['diff'] ?? array();
5666 6474 $crit_id = sanitize_html_class( $crit_file );
5667 6475 $added_count = is_array( $crit_diff ) ? count( $crit_diff['added'] ?? array() ) : 0;
5668 6476 $removed_count = is_array( $crit_diff ) ? count( $crit_diff['removed'] ?? array() ) : 0;
5669 - $diff_unavailable = is_array( $crit_diff ) && ! empty( $crit_diff['unavailable'] );
6477 + // The lines of a shared file are for whoever approves it. Results
6478 + // stored before 2.11.8 on the main site still carry them, so the
6479 + // screen asks too, not only the scan that wrote them.
6480 + $diff_network = ( is_array( $crit_diff ) && ! empty( $crit_diff['network'] ) ) || $this->critical_approval_locked();
6481 + $diff_rescan = is_array( $crit_diff ) && ! empty( $crit_diff['rescan'] );
6482 + $diff_redaction = is_array( $crit_diff ) && ! empty( $crit_diff['redaction'] );
6483 + $diff_unavailable = $diff_network || ( is_array( $crit_diff ) && ! empty( $crit_diff['unavailable'] ) );
5670 6484 ?>
5671 6485 <tr>
5672 6486 <td><code style="color: #e36210;"><?php echo esc_html( $crit_file ); ?></code></td>
5673 6487 <td>
@@ -5690,18 +6504,36 @@
5690 6504 <td>
5691 6505 <button type="button" class="button button-small vigilante-toggle-critical-content" data-target="vigilante-critical-content-<?php echo esc_attr( $crit_id ); ?>" data-label-show="<?php esc_attr_e( 'Review changes', 'vigilante' ); ?>" data-label-hide="<?php esc_attr_e( 'Hide changes', 'vigilante' ); ?>">
5692 6506 <?php esc_html_e( 'Review changes', 'vigilante' ); ?>
5693 6507 </button>
5694 - <button type="button" class="button button-small button-primary vigilante-approve-critical-file" data-file="<?php echo esc_attr( $crit_file ); ?>">
5695 - <?php esc_html_e( 'Approve', 'vigilante' ); ?>
5696 - </button>
6508 + <?php if ( $this->critical_approval_locked() ) : ?>
6509 + <span class="description" style="display:block;margin-top:4px;">
6510 + <?php echo esc_html( $this->critical_approval_notice() ); ?>
6511 + </span>
6512 + <?php else : ?>
6513 + <button type="button" class="button button-small button-primary vigilante-approve-critical-file" data-file="<?php echo esc_attr( $crit_file ); ?>">
6514 + <?php esc_html_e( 'Approve', 'vigilante' ); ?>
6515 + </button>
6516 + <?php endif; ?>
5697 6517 </td>
5698 6518 </tr>
5699 6519 <tr id="vigilante-critical-content-<?php echo esc_attr( $crit_id ); ?>" class="vigilante-critical-content-row" style="display:none;">
5700 6520 <td colspan="3" style="padding: 0;">
5701 6521 <div class="vigilante-critical-content" style="max-height: 400px; overflow: auto; background: #fff; padding: 10px; font-size: 12px; line-height: 1.5; font-family: Consolas, Monaco, monospace; border-top: 1px solid #c3c4c7;">
5702 - <?php if ( $diff_unavailable ) : ?>
6522 + <?php if ( $diff_network ) : ?>
5703 6523 <p style="color: #50575e; font-style: italic; margin: 0;">
6524 + <?php esc_html_e( 'This file belongs to the whole network, so its line changes are only shown to network administrators, on the main site.', 'vigilante' ); ?>
6525 + </p>
6526 + <?php elseif ( $diff_rescan ) : ?>
6527 + <p style="color: #50575e; font-style: italic; margin: 0;">
6528 + <?php esc_html_e( 'Run a new scan to see the line changes of this file.', 'vigilante' ); ?>
6529 + </p>
6530 + <?php elseif ( $diff_redaction ) : ?>
6531 + <p style="color: #50575e; font-style: italic; margin: 0;">
6532 + <?php esc_html_e( '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' ); ?>
6533 + </p>
6534 + <?php elseif ( $diff_unavailable ) : ?>
6535 + <p style="color: #50575e; font-style: italic; margin: 0;">
5704 6536 <?php esc_html_e( 'Diff not available for this file (baseline was created before diff tracking was added). Approve to enable diff on future changes.', 'vigilante' ); ?>
5705 6537 </p>
5706 6538 <?php elseif ( empty( $crit_diff['added'] ) && empty( $crit_diff['removed'] ) ) : ?>
5707 6539 <p style="color: #50575e; font-style: italic; margin: 0;">
@@ -5729,9 +6561,9 @@
5729 6561 <?php endif; ?>
5730 6562
5731 6563 <?php if ( $has_closed ) : ?>
5732 6564 <div class="vigilante-file-list vigilante-closed-plugins">
5733 - <h3 style="color: #d63638;"><?php esc_html_e( 'Closed + Removed Plugins', 'vigilante' ); ?></h3>
6565 + <h3 id="vigilante-section-fi-closed-plugins" style="color: #d63638;"><?php esc_html_e( 'Closed + Removed Plugins', 'vigilante' ); ?></h3>
5734 6566 <p class="description" style="color: #d63638;">
5735 6567 <?php esc_html_e( '&#9888; Warning: These plugins have been closed in the WordPress.org repository. Closures usually indicate malware, security issues, guideline violations, or supply chain attacks. Uninstall and replace as soon as possible.', 'vigilante' ); ?>
5736 6568 </p>
5737 6569 <table class="wp-list-table widefat striped">
@@ -5940,8 +6772,15 @@
5940 6772 if ( ! current_user_can( 'manage_options' ) ) {
5941 6773 wp_die( esc_html__( 'Permission denied.', 'vigilante' ), 403 );
5942 6774 }
5943 6775
6776 + // The archive carries wp-config.php, which a whole network shares. On a
6777 + // network manage_options is held by every subsite administrator, so the
6778 + // same gate the writers use applies here.
6779 + if ( ! Vigilante_Settings::can_write_shared_files() ) {
6780 + wp_die( esc_html( Vigilante_Settings::get_shared_files_notice() ), 403 );
6781 + }
6782 +
5944 6783 $backup_manager = new Vigilante_Backup_Manager();
5945 6784 $result = $backup_manager->stream_files_zip();
5946 6785
5947 6786 // stream_files_zip() exits on success; only a WP_Error returns here.
@@ -6030,8 +6869,29 @@
6030 6869
6031 6870 // Read ONLY saved options from database (not merged with defaults)
6032 6871 $saved_options = get_option( Vigilante_Settings::OPTION_NAME, array() );
6033 6872
6873 + // What is stored before this request changes anything: the shared file
6874 + // settings this user may not change are put back from here (2.11.6).
6875 + $stored_options = $saved_options;
6876 + $locked = Vigilante_Settings::get_locked_file_settings();
6877 +
6878 + if ( isset( $locked[ $section ] ) && true === $locked[ $section ] ) {
6879 + wp_send_json_error( Vigilante_Settings::get_shared_files_notice() );
6880 + }
6881 +
6882 + // A module switch is a single key, so refusing says more than a success
6883 + // that changed nothing, and the dashboard puts the toggle back.
6884 + if ( 'modules' === $section && isset( $locked['modules'], $data['modules'] ) && is_array( $locked['modules'] ) && is_array( $data['modules'] ) ) {
6885 + foreach ( array_keys( $data['modules'] ) as $vg_module ) {
6886 + if ( in_array( sanitize_key( $vg_module ), $locked['modules'], true ) ) {
6887 + wp_send_json_error( Vigilante_Settings::get_shared_files_notice() );
6888 + }
6889 + }
6890 + }
6891 +
6892 + $rejected_ips = array();
6893 +
6034 6894 // Handle modules
6035 6895 if ( 'modules' === $section && isset( $data['modules'] ) ) {
6036 6896 if ( ! isset( $saved_options['modules'] ) ) {
6037 6897 $saved_options['modules'] = array();
@@ -6049,9 +6909,16 @@
6049 6909 $current_section = isset( $saved_options[ $section ] ) ? $saved_options[ $section ] : array();
6050 6910
6051 6911 // Process the submitted data
6052 6912 $processed = $this->process_section_data( $data[ $section ], $section_defaults, $current_section );
6053 -
6913 +
6914 + // The IP boxes are free text and, until 2.9.9, whatever was typed
6915 + // went straight into the option. An entry the matcher can never
6916 + // match still sits in a security list looking like protection,
6917 + // so the ones that cannot match are dropped and reported back
6918 + // instead of being stored in silence.
6919 + $rejected_ips = $this->filter_ip_lists( $section, $processed );
6920 +
6054 6921 // Save the processed section
6055 6922 $saved_options[ $section ] = $processed;
6056 6923
6057 6924 // Clear active preset when any section settings change
@@ -6061,8 +6928,10 @@
6061 6928
6062 6929 // Clear cache before saving
6063 6930 wp_cache_delete( Vigilante_Settings::OPTION_NAME, 'options' );
6064 6931
6932 + $saved_options = Vigilante_Settings::keep_locked_file_settings( $saved_options, $stored_options );
6933 +
6065 6934 // Save to database
6066 6935 update_option( Vigilante_Settings::OPTION_NAME, $saved_options );
6067 6936
6068 6937 // Clear the settings cache
@@ -6111,12 +6980,59 @@
6111 6980 $login_url_result['sent']
6112 6981 );
6113 6982 }
6114 6983
6984 + if ( ! empty( $rejected_ips ) ) {
6985 + $message .= ' ' . sprintf(
6986 + /* translators: %s: comma separated list of the entries that were not saved. */
6987 + _n(
6988 + 'This entry is not a valid IP, CIDR range or wildcard, so it was not saved: %s',
6989 + 'These entries are not valid IPs, CIDR ranges or wildcards, so they were not saved: %s',
6990 + count( $rejected_ips ),
6991 + 'vigilante'
6992 + ),
6993 + implode( ', ', array_map( 'esc_html', $rejected_ips ) )
6994 + );
6995 + }
6996 +
6115 6997 wp_send_json_success( $message );
6116 6998 }
6117 -
6999 +
6118 7000 /**
7001 + * Keep only the IP patterns the matcher can actually match
7002 + *
7003 + * @since 2.9.9
7004 + *
7005 + * @param string $section Section being saved.
7006 + * @param array $processed Section data, edited in place.
7007 + * @return array Entries that were dropped, for the message back to the user.
7008 + */
7009 + private function filter_ip_lists( $section, &$processed ) {
7010 + $lists = array(
7011 + 'firewall' => array( 'ip_whitelist', 'ip_blacklist' ),
7012 + 'login_security' => array( 'ip_whitelist' ),
7013 + );
7014 +
7015 + if ( ! isset( $lists[ $section ] ) ) {
7016 + return array();
7017 + }
7018 +
7019 + $rejected = array();
7020 +
7021 + foreach ( $lists[ $section ] as $key ) {
7022 + if ( ! isset( $processed[ $key ] ) || ! is_array( $processed[ $key ] ) ) {
7023 + continue;
7024 + }
7025 +
7026 + $split = Vigilante_IP_Utils::split_list( $processed[ $key ] );
7027 + $processed[ $key ] = $split['valid'];
7028 + $rejected = array_merge( $rejected, $split['rejected'] );
7029 + }
7030 +
7031 + return array_values( array_unique( $rejected ) );
7032 + }
7033 +
7034 + /**
6119 7035 * Send 2FA enable notifications to users
6120 7036 *
6121 7037 * @return array Result with 'sent' and 'failed' counts.
6122 7038 */
@@ -6438,13 +7354,27 @@
6438 7354
6439 7355 // Sanitize imported data recursively
6440 7356 $imported = map_deep( $imported, 'sanitize_text_field' );
6441 7357
6442 - // Validate structure
6443 - $defaults = $this->settings->get_default_options();
6444 - $merged = array_replace_recursive( $defaults, $imported );
7358 + // Validate structure: only sections and keys of the schema survive, and
7359 + // every value takes the type of its default. Until 2.11.0 this was an
7360 + // array_replace_recursive() of the file over the defaults, so any key in
7361 + // the file, known or not, landed in vigilante_options (S7). Sections
7362 + // the file does not carry keep their defaults; a section it does carry
7363 + // replaces the default one whole, because validate_options() has
7364 + // already filled in whatever the file left out.
7365 + $defaults = $this->settings->get_default_options();
7366 + $validated = $this->settings->validate_options( $imported );
7367 + $merged = $defaults;
6445 7368
7369 + foreach ( $validated as $section => $data ) {
7370 + if ( is_array( $data ) ) {
7371 + $merged[ $section ] = $data;
7372 + }
7373 + }
7374 +
6446 7375 // Save
7376 + $merged = Vigilante_Settings::keep_locked_file_settings( $merged, get_option( Vigilante_Settings::OPTION_NAME, array() ) );
6447 7377 update_option( Vigilante_Settings::OPTION_NAME, $merged );
6448 7378 $this->settings->clear_cache();
6449 7379
6450 7380 // Re-evaluate the active preset marker. The imported config may match
@@ -6467,9 +7397,9 @@
6467 7397 if ( ! wp_next_scheduled( 'vigilante_under_attack_post_scan' ) ) {
6468 7398 wp_schedule_single_event( time() + 5, 'vigilante_under_attack_post_scan' );
6469 7399 }
6470 7400
6471 - wp_send_json_success( __( 'Settings imported successfully.', 'vigilante' ) );
7401 + wp_send_json_success( __( 'Settings imported successfully.', 'vigilante' ) . $this->locked_file_settings_message() );
6472 7402 }
6473 7403
6474 7404 /**
6475 7405 * Detect whether a vigilante_options array matches a known preset.
@@ -6572,9 +7502,11 @@
6572 7502 $preset = isset( $_POST['preset'] ) ? sanitize_key( $_POST['preset'] ) : '';
6573 7503
6574 7504 // Handle reset to defaults
6575 7505 if ( 'reset' === $preset ) {
6576 - $defaults = $this->settings->get_default_options();
7506 + $stored_options = get_option( Vigilante_Settings::OPTION_NAME, array() );
7507 + $defaults = Vigilante_Settings::get_defaults_preserving_user_data( $stored_options );
7508 + $defaults = Vigilante_Settings::keep_locked_file_settings( $defaults, $stored_options );
6577 7509 update_option( Vigilante_Settings::OPTION_NAME, $defaults );
6578 7510 $this->settings->clear_cache();
6579 7511
6580 7512 // Clear active preset
@@ -6582,9 +7514,9 @@
6582 7514
6583 7515 // Apply file changes after reset
6584 7516 $this->apply_all_file_changes( $defaults );
6585 7517
6586 - wp_send_json_success( __( 'Settings reset to defaults.', 'vigilante' ) );
7518 + wp_send_json_success( __( 'Settings reset to defaults.', 'vigilante' ) . $this->locked_file_settings_message() );
6587 7519 return;
6588 7520 }
6589 7521
6590 7522 $presets = $this->settings->get_presets();
@@ -6605,13 +7537,14 @@
6605 7537 $current = get_option( Vigilante_Settings::OPTION_NAME, array() );
6606 7538 if ( ! is_array( $current ) ) {
6607 7539 $current = array();
6608 7540 }
6609 - // Make sure all known keys exist before merging — array_replace_recursive
6610 - // does not invent keys that are missing on both sides.
6611 - $current = array_replace_recursive( $this->settings->get_default_options(), $current );
7541 + // Make sure all known keys exist before merging — the merge does not
7542 + // invent keys that are missing on both sides.
7543 + $current = Vigilante_Settings::merge_preset( $this->settings->get_default_options(), $current );
6612 7544
6613 - $merged = array_replace_recursive( $current, $preset_options );
7545 + $merged = Vigilante_Settings::merge_preset( $current, $preset_options );
7546 + $merged = Vigilante_Settings::keep_locked_file_settings( $merged, get_option( Vigilante_Settings::OPTION_NAME, array() ) );
6614 7547
6615 7548 update_option( Vigilante_Settings::OPTION_NAME, $merged );
6616 7549 $this->settings->clear_cache();
6617 7550
@@ -6620,9 +7553,9 @@
6620 7553
6621 7554 // Apply file changes after preset
6622 7555 $this->apply_all_file_changes( $merged );
6623 7556
6624 - wp_send_json_success( __( 'Preset applied successfully.', 'vigilante' ) );
7557 + wp_send_json_success( __( 'Preset applied successfully.', 'vigilante' ) . $this->locked_file_settings_message() );
6625 7558 }
6626 7559
6627 7560 /**
6628 7561 * AJAX: Reset a specific section to defaults
@@ -6639,11 +7572,13 @@
6639 7572 if ( empty( $section ) ) {
6640 7573 wp_send_json_error( __( 'No section specified.', 'vigilante' ) );
6641 7574 }
6642 7575
6643 - // Get current options and defaults
7576 + // Get current options and defaults. get_defaults_preserving_user_data()
7577 + // applies the tweaks a fresh installation gets, so the button and a new
7578 + // install agree, and keeps whatever the owner typed in.
6644 7579 $current_options = $this->settings->get_all_options();
6645 - $defaults = $this->settings->get_default_options();
7580 + $defaults = Vigilante_Settings::get_defaults_preserving_user_data( $current_options );
6646 7581
6647 7582 // Check if section exists in defaults
6648 7583 if ( ! isset( $defaults[ $section ] ) ) {
6649 7584 wp_send_json_error( __( 'Invalid section.', 'vigilante' ) );
@@ -6648,11 +7583,27 @@
6648 7583 if ( ! isset( $defaults[ $section ] ) ) {
6649 7584 wp_send_json_error( __( 'Invalid section.', 'vigilante' ) );
6650 7585 }
6651 7586
6652 - // Reset only this section to defaults
6653 - $current_options[ $section ] = $defaults[ $section ];
7587 + $new_values = $defaults[ $section ];
6654 7588
7589 + /*
7590 + * On a subsite, the settings written to wp-config.php and .htaccess are
7591 + * the main site's business. Resetting the local copy of those would only
7592 + * make this screen disagree with the file, so they are carried over
7593 + * untouched, and a section that is nothing but shared settings is not
7594 + * reset at all. On the main site, a user without network rights keeps
7595 + * the ones the shared files are built from as well (2.11.6).
7596 + */
7597 + $locked = Vigilante_Settings::get_locked_file_settings();
7598 +
7599 + if ( isset( $locked[ $section ] ) && true === $locked[ $section ] ) {
7600 + wp_send_json_error( Vigilante_Settings::get_shared_files_notice() );
7601 + }
7602 +
7603 + $current_options[ $section ] = $new_values;
7604 + $current_options = Vigilante_Settings::keep_locked_file_settings( $current_options, get_option( Vigilante_Settings::OPTION_NAME, array() ) );
7605 +
6655 7606 // Save
6656 7607 update_option( Vigilante_Settings::OPTION_NAME, $current_options );
6657 7608 $this->settings->clear_cache();
6658 7609
@@ -6734,8 +7685,19 @@
6734 7685 // Save new results
6735 7686 update_option( 'vigilante_last_integrity_scan', time() );
6736 7687 update_option( 'vigilante_last_integrity_results', $results );
6737 7688
7689 + // On the main site the scan does compute the lines of wp-config.php and
7690 + // .htaccess, for the network administrator. Somebody without network
7691 + // rights gets the change and its sizes, not the lines.
7692 + if ( $this->critical_approval_locked() && ! empty( $results['modified'] ) && is_array( $results['modified'] ) ) {
7693 + foreach ( $results['modified'] as $index => $item ) {
7694 + if ( is_array( $item ) && 'critical_config' === ( $item['type'] ?? '' ) ) {
7695 + $results['modified'][ $index ]['diff'] = Vigilante_File_Integrity::network_only_diff();
7696 + }
7697 + }
7698 + }
7699 +
6738 7700 wp_send_json_success( array(
6739 7701 'message' => __( 'Scan completed.', 'vigilante' ),
6740 7702 'results' => $results,
6741 7703 'ignored_count' => count( get_option( 'vigilante_ignored_files', array() ) ),
@@ -6769,11 +7731,41 @@
6769 7731 if ( ! current_user_can( 'manage_options' ) ) {
6770 7732 wp_send_json_error( __( 'Permission denied.', 'vigilante' ) );
6771 7733 }
6772 7734
7735 + $results = get_option( 'vigilante_last_integrity_results' );
7736 + $scanned_at = get_option( 'vigilante_last_integrity_scan' );
7737 +
6773 7738 delete_option( 'vigilante_last_integrity_results' );
6774 7739 delete_option( 'vigilante_last_integrity_scan' );
6775 7740
7741 + /*
7742 + * A pending change to wp-config.php or the root .htaccess is closed by
7743 + * approving it, which takes the network. Clearing the results was one
7744 + * more way to close it without, until the next scan: the ignore list was
7745 + * shut in 2.11.8 and this button was left open, found by the cross
7746 + * review of 2.11.8. So for somebody who cannot approve, those entries
7747 + * stay and everything else goes.
7748 + */
7749 + if ( $this->critical_approval_locked() && is_array( $results ) && ! empty( $results['modified'] ) && is_array( $results['modified'] ) ) {
7750 + $critical = array_values(
7751 + array_filter(
7752 + $results['modified'],
7753 + function ( $item ) {
7754 + return is_array( $item ) && 'critical_config' === ( $item['type'] ?? '' );
7755 + }
7756 + )
7757 + );
7758 +
7759 + if ( $critical ) {
7760 + $results['modified'] = $critical;
7761 + $results['suspicious'] = array();
7762 + $results['extra'] = array();
7763 + update_option( 'vigilante_last_integrity_results', $results );
7764 + update_option( 'vigilante_last_integrity_scan', $scanned_at ? $scanned_at : time() );
7765 + }
7766 + }
7767 +
6776 7768 if ( $this->database ) {
6777 7769 $this->database->clear_file_hashes();
6778 7770 }
6779 7771
@@ -6799,8 +7791,14 @@
6799 7791 if ( empty( $file ) ) {
6800 7792 wp_send_json_error( __( 'No file specified.', 'vigilante' ) );
6801 7793 }
6802 7794
7795 + // A change to a shared file is closed by approving it, and approving it
7796 + // takes the network. Ignoring it would close the same warning without.
7797 + if ( $this->critical_approval_locked() && in_array( $file, array( 'wp-config.php', '.htaccess' ), true ) ) {
7798 + wp_send_json_error( $this->critical_approval_notice() );
7799 + }
7800 +
6803 7801 $file_integrity = new Vigilante_File_Integrity( $this->settings, $this->database );
6804 7802 $file_integrity->ignore_file( $file );
6805 7803
6806 7804 // Also remove the file from stored scan results so UI updates
@@ -6864,12 +7862,14 @@
6864 7862 if ( ! is_array( $raw_files ) ) {
6865 7863 wp_send_json_error( __( 'Invalid request.', 'vigilante' ) );
6866 7864 }
6867 7865
6868 - $files = array();
7866 + $files = array();
7867 + $shared = $this->critical_approval_locked() ? array( 'wp-config.php', '.htaccess' ) : array();
6869 7868 foreach ( $raw_files as $f ) {
6870 7869 $clean = sanitize_text_field( $f );
6871 - if ( '' !== $clean ) {
7870 + // Same rule as ajax_ignore_file() for the two shared files.
7871 + if ( '' !== $clean && ! in_array( $clean, $shared, true ) ) {
6872 7872 $files[] = $clean;
6873 7873 }
6874 7874 }
6875 7875