self::MODE_MONITOR, 'waf_enabled' => true, 'login_protection_enabled' => true, 'xmlrpc_block_pingback' => true, 'rest_api_protection_enabled'=> true, 'integrity_scan_enabled' => true, 'malware_scan_enabled' => true, 'upload_scan_enabled' => true, 'vuln_intel_enabled' => true, 'geoip_rir_enabled' => true, 'auto_update_all_enabled' => true, 'static_cache_enabled' => false, 'static_cache_ttl_secs' => 3600, 'cache_stale_grace_secs' => 600, 'cache_custom_exclude_paths' => array(), 'cache_gzip_enabled' => true, 'cache_gzip_level' => 6, 'cache_brotli_enabled' => true, 'cache_brotli_level' => 5, 'cache_min_compress_bytes' => 1024, 'cache_discovery_enabled' => true, 'cache_warming_enabled' => false, 'cache_warm_concurrency' => 4, 'cache_warm_batch_size' => 20, 'cache_warm_min_interval_secs' => 1, 'challenge_enabled' => true, 'login_attempt_threshold' => 5, 'login_attempt_window_secs' => 600, 'login_lockout_secs' => 900, 'exceptions' => array(), 'cloud_connected' => false, // Off by default -- ANIS Cloud shares blocked visitors' IP addresses // with an external service, so it requires the site owner's explicit // opt-in (Settings checkbox or the first-run admin notice) rather than // connecting silently on activation. WordPress.org's guidelines // require clear consent before any such external connection; this is // still zero-configuration in the sense that mattered before -- there // is no server address, license key, or tier field to fill in, only // a single "enable" action. 'anis_enabled' => false, 'anis_base_url' => 'https://anis.weboria.eu', 'anis_license_key' => '', 'backup_schedule_mode' => 'automatic', 'backup_interval_hours' => 24, 'backup_retention_count' => 5, ); public static function all() { $stored = get_option( self::OPTION, array() ); return wp_parse_args( $stored, self::$defaults ); } public static function get( $key, $fallback = null ) { $all = self::all(); return array_key_exists( $key, $all ) ? $all[ $key ] : $fallback; } public static function update( array $partial ) { $all = array_merge( self::all(), $partial ); update_option( self::OPTION, $all, true ); return $all; } public static function mode() { return self::get( 'mode', self::MODE_MONITOR ); } public static function is_monitor_only() { return self::mode() === self::MODE_MONITOR; } public static function is_strict() { return self::mode() === self::MODE_STRICT; } public static function has_exception( $type, $value ) { foreach ( self::get( 'exceptions', array() ) as $exception ) { if ( ( $exception['type'] ?? '' ) === $type && ( $exception['value'] ?? '' ) === $value ) { return true; } } return false; } public static function add_exception( $type, $value, $note = '' ) { if ( self::has_exception( $type, $value ) ) { return self::all(); } $exceptions = self::get( 'exceptions', array() ); $exceptions[] = array( 'type' => $type, 'value' => $value, 'note' => $note, 'added_at' => current_time( 'mysql', true ), ); return self::update( array( 'exceptions' => $exceptions ) ); } public static function remove_exception( $type, $value ) { $exceptions = array_values( array_filter( self::get( 'exceptions', array() ), function ( $e ) use ( $type, $value ) { return ! ( ( $e['type'] ?? '' ) === $type && ( $e['value'] ?? '' ) === $value ); } ) ); return self::update( array( 'exceptions' => $exceptions ) ); } }