|null */ private static $cache = null; /** * Default values. * * @return array */ public static function defaults(): array { return array( 'check_interval_days' => 7, 'rate_limit_per_hour' => 600, 'concurrency' => 2, 'sources' => array( 'post_content' => true, 'elementor' => true, 'menu' => true, 'widget' => true, 'comment' => false, ), 'include_images' => true, 'user_agent' => self::DEFAULT_USER_AGENT, 'extra_allowlist' => array(), 'internal_fast_path' => false, 'email_digest' => false, 'capability' => 'manage_options', 'log_404' => false, 'redirect_query' => 'ignore', 'retention_days' => 90, ); } /** * All settings merged over defaults. * * @return array */ public static function all(): array { if ( null === self::$cache ) { $stored = get_option( self::OPTION, array() ); if ( ! is_array( $stored ) ) { $stored = array(); } $defaults = self::defaults(); $merged = array_merge( $defaults, $stored ); $merged['sources'] = array_merge( $defaults['sources'], is_array( $merged['sources'] ) ? $merged['sources'] : array() ); self::$cache = $merged; } return self::$cache; } /** * One setting. * * @param string $key Setting key. * @return mixed */ public static function get( string $key ) { $all = self::all(); return $all[ $key ] ?? null; } /** * Whether a content source is enabled. * * @param string $source Source type. */ public static function source_enabled( string $source ): bool { $sources = self::get( 'sources' ); return is_array( $sources ) && ! empty( $sources[ $source ] ); } /** * Sanitize and save a (partial) settings array. * * @param array $input Raw input. * @return array The saved settings. */ public static function update( array $input ): array { $clean = self::sanitize( array_merge( self::all(), $input ) ); update_option( self::OPTION, $clean, true ); // Autoloaded: read on front-end 404s and redirects. self::$cache = null; return self::all(); } /** * Clear the per-request cache (used after external option changes). */ public static function flush(): void { self::$cache = null; } /** * Sanitize a full settings array. * * @param array $in Raw values. * @return array */ public static function sanitize( array $in ): array { $d = self::defaults(); $out = array(); $out['check_interval_days'] = self::clamp_int( $in['check_interval_days'] ?? $d['check_interval_days'], 1, 30 ); $out['rate_limit_per_hour'] = self::clamp_int( $in['rate_limit_per_hour'] ?? $d['rate_limit_per_hour'], 10, 20000 ); $out['concurrency'] = self::clamp_int( $in['concurrency'] ?? $d['concurrency'], 1, 4 ); $out['sources'] = array(); $sources_in = isset( $in['sources'] ) && is_array( $in['sources'] ) ? $in['sources'] : array(); foreach ( $d['sources'] as $source => $default ) { $out['sources'][ $source ] = isset( $sources_in[ $source ] ) ? (bool) $sources_in[ $source ] : (bool) $default; } $out['include_images'] = (bool) ( $in['include_images'] ?? $d['include_images'] ); $out['internal_fast_path'] = (bool) ( $in['internal_fast_path'] ?? $d['internal_fast_path'] ); $out['email_digest'] = (bool) ( $in['email_digest'] ?? $d['email_digest'] ); $out['log_404'] = (bool) ( $in['log_404'] ?? $d['log_404'] ); $ua = sanitize_text_field( (string) ( $in['user_agent'] ?? '' ) ); $out['user_agent'] = '' !== $ua ? substr( $ua, 0, 500 ) : $d['user_agent']; $allow = $in['extra_allowlist'] ?? array(); if ( is_string( $allow ) ) { $allow = preg_split( '/[\s,]+/', $allow ); } $out['extra_allowlist'] = array(); foreach ( (array) $allow as $host ) { $host = strtolower( trim( sanitize_text_field( (string) $host ) ) ); $host = preg_replace( '#^https?://#', '', $host ); $host = preg_replace( '#[/:].*$#', '', (string) $host ); if ( is_string( $host ) && '' !== $host && preg_match( '/^[a-z0-9.*-]+$/', $host ) ) { $out['extra_allowlist'][] = $host; } } $out['extra_allowlist'] = array_values( array_unique( $out['extra_allowlist'] ) ); $cap = (string) ( $in['capability'] ?? $d['capability'] ); $out['capability'] = in_array( $cap, self::ALLOWED_CAPABILITIES, true ) ? $cap : $d['capability']; $rq = (string) ( $in['redirect_query'] ?? $d['redirect_query'] ); $out['redirect_query'] = in_array( $rq, array( 'ignore', 'exact', 'pass' ), true ) ? $rq : $d['redirect_query']; $out['retention_days'] = self::clamp_int( $in['retention_days'] ?? $d['retention_days'], 7, 3650 ); return $out; } /** * Integer clamped to a range. * * @param mixed $value Raw value. * @param int $min Minimum. * @param int $max Maximum. */ private static function clamp_int( $value, int $min, int $max ): int { $value = is_numeric( $value ) ? (int) $value : $min; return max( $min, min( $max, $value ) ); } /** * Capability required to use the plugin screen. Filterable. */ public static function capability(): string { $cap = (string) self::get( 'capability' ); /** * Filters the capability required to use Unbreak's admin screen and REST API. * * @param string $cap Capability name. */ return (string) apply_filters( 'unbreak_capability', '' !== $cap ? $cap : 'manage_options' ); } }