*/ public static function defaults() { return array( 'case_sensitive' => false, 'ignore_separators' => true, 'warning_threshold' => 3, 'repeat_window' => DAY_IN_SECONDS, 'log_retention' => 365, 'delete_on_remove' => false, 'rate_limit_minute' => 10, 'rate_limit_hour' => 60, 'ip_source' => 'remote_addr', 'primary_color' => '#1d4ed8', ) + array_fill_keys( NovoVerify_Messages::option_keys(), '' ); } /** * Where the visitor IP is read from. Proxy headers can be faked, so they are opt-in. * * @return array Key => $_SERVER key. */ public static function ip_sources() { return array( 'remote_addr' => 'REMOTE_ADDR', 'cloudflare' => 'HTTP_CF_CONNECTING_IP', 'x_forwarded_for' => 'HTTP_X_FORWARDED_FOR', 'x_real_ip' => 'HTTP_X_REAL_IP', ); } /** * Returns all settings merged with defaults. * * @return array */ public static function all() { $stored = get_option( NovoVerify_Config::OPTION_SETTINGS, array() ); if ( ! is_array( $stored ) ) { $stored = array(); } return array_merge( self::defaults(), array_intersect_key( $stored, self::defaults() ) ); } /** * Returns one setting. * * @param string $key Setting key. * @return mixed|null */ public static function get( $key ) { $all = self::all(); return array_key_exists( $key, $all ) ? $all[ $key ] : null; } /** * Registers the option with the Settings API. */ public static function register() { register_setting( self::GROUP, NovoVerify_Config::OPTION_SETTINGS, array( 'type' => 'array', 'sanitize_callback' => array( __CLASS__, 'sanitize' ), 'default' => self::defaults(), 'show_in_rest' => false, ) ); } /** * Validates submitted settings. * * Safe to run twice (WordPress calls it twice when the option is first created): * values that are already clean pass through unchanged. * * @param mixed $input Submitted values. * @return array */ public static function sanitize( $input ) { $input = is_array( $input ) ? $input : array(); $current = self::all(); $output = $current; $from_form = ! empty( $input['_submitted'] ); foreach ( array( 'case_sensitive', 'ignore_separators', 'delete_on_remove' ) as $key ) { if ( $from_form ) { $output[ $key ] = ! empty( $input[ $key ] ); } elseif ( isset( $input[ $key ] ) ) { $output[ $key ] = (bool) $input[ $key ]; } } $numbers = array( 'warning_threshold' => array( 1, 1000 ), 'rate_limit_minute' => array( 0, 1000 ), 'rate_limit_hour' => array( 0, 100000 ), 'log_retention' => array( 0, 3650 ), ); foreach ( $numbers as $key => $range ) { if ( isset( $input[ $key ] ) && is_numeric( $input[ $key ] ) ) { $output[ $key ] = self::clamp( $input[ $key ], $range[0], $range[1] ); } } // The form asks for hours; the stored value is in seconds. if ( isset( $input['repeat_window_hours'] ) && is_numeric( $input['repeat_window_hours'] ) ) { $output['repeat_window'] = self::clamp( $input['repeat_window_hours'], 0, 720 ) * HOUR_IN_SECONDS; } elseif ( isset( $input['repeat_window'] ) && is_numeric( $input['repeat_window'] ) ) { $output['repeat_window'] = self::clamp( $input['repeat_window'], 0, 720 * HOUR_IN_SECONDS ); } if ( isset( $input['ip_source'] ) && array_key_exists( (string) $input['ip_source'], self::ip_sources() ) ) { $output['ip_source'] = (string) $input['ip_source']; } if ( isset( $input['primary_color'] ) ) { $color = sanitize_hex_color( (string) $input['primary_color'] ); $output['primary_color'] = $color ? $color : self::defaults()['primary_color']; } foreach ( NovoVerify_Codes::results() as $result ) { $title_key = 'message_' . $result . '_title'; $message_key = 'message_' . $result; if ( isset( $input[ $title_key ] ) ) { $output[ $title_key ] = mb_substr( sanitize_text_field( (string) $input[ $title_key ] ), 0, 200 ); } if ( isset( $input[ $message_key ] ) ) { $output[ $message_key ] = mb_substr( sanitize_textarea_field( (string) $input[ $message_key ] ), 0, 1000 ); } } return array_intersect_key( $output, self::defaults() ); } /** * Limits an integer to a range. * * @param mixed $value Value. * @param int $min Minimum. * @param int $max Maximum. * @return int */ private static function clamp( $value, $min, $max ) { return max( $min, min( $max, (int) $value ) ); } /** * Registers custom result texts with Polylang so they can be translated there. */ public static function register_polylang_strings() { if ( ! function_exists( 'pll_register_string' ) ) { return; } $settings = self::all(); foreach ( NovoVerify_Messages::option_keys() as $key ) { if ( '' !== trim( (string) $settings[ $key ] ) ) { $multiline = false === strpos( $key, '_title' ); pll_register_string( 'novoverify_' . $key, (string) $settings[ $key ], 'NovoVerify', $multiline ); } } } }