'', self::INITIAL_VERSION => '', self::REPORT_TTL => self::DEFAULT_REPORT_TTL, self::SCAN_LIMIT => self::DEFAULT_SCAN_LIMIT, self::SCAN_POST_TYPES => array(), ); } /** * Normalises a raw settings array. * * Missing or malformed values fall back to their defaults and unknown keys * are dropped. * * @param array $raw Raw settings. * @return array{version: string, initial_version: string, report_ttl: int, scan_limit: int, scan_post_types: string[]} */ public static function sanitize( array $raw ): array { $settings = self::defaults(); foreach ( array( self::VERSION, self::INITIAL_VERSION ) as $key ) { if ( is_string( $raw[ $key ] ?? null ) ) { $settings[ $key ] = trim( $raw[ $key ] ); } } if ( is_numeric( $raw[ self::REPORT_TTL ] ?? null ) ) { $settings[ self::REPORT_TTL ] = max( self::MIN_REPORT_TTL, (int) $raw[ self::REPORT_TTL ] ); } if ( is_numeric( $raw[ self::SCAN_LIMIT ] ?? null ) ) { $settings[ self::SCAN_LIMIT ] = max( 1, (int) $raw[ self::SCAN_LIMIT ] ); } if ( is_array( $raw[ self::SCAN_POST_TYPES ] ?? null ) ) { $types = array(); foreach ( $raw[ self::SCAN_POST_TYPES ] as $type ) { // Same character set as sanitize_key(), which post type slugs follow. $type = is_string( $type ) ? preg_replace( '/[^a-z0-9_\-]/', '', strtolower( $type ) ) : ''; if ( is_string( $type ) && '' !== $type ) { $types[] = $type; } } $settings[ self::SCAN_POST_TYPES ] = array_values( array_unique( $types ) ); } return $settings; } /** * Merges changed settings into a stored array, keeping only explicit values. * * The result holds the sanitised value of every known key present in either * input, and nothing for keys that were never set, so defaults are never * written to the database. * * @param array $stored Currently stored (raw) settings. * @param array $values Settings to change. * @return array */ public static function merge( array $stored, array $values ): array { $merged = array_merge( $stored, $values ); return array_intersect_key( self::sanitize( $merged ), $merged ); } /** * Every setting, with defaults applied. * * @return array{version: string, initial_version: string, report_ttl: int, scan_limit: int, scan_post_types: string[]} */ public function all(): array { $stored = get_option( self::OPTION, array() ); return self::sanitize( is_array( $stored ) ? $stored : array() ); } /** * Merges values into the stored settings. * * @param array $values Settings to change, keyed by setting name. * @return bool Whether the stored option changed. */ public function update( array $values ): bool { $stored = get_option( self::OPTION, array() ); return update_option( self::OPTION, self::merge( is_array( $stored ) ? $stored : array(), $values ), true ); } /** * Installed plugin version ('' before the first activation). * * @return string */ public function version(): string { return $this->all()[ self::VERSION ]; } /** * Plugin version first installed on this site ('' before the first activation). * * @return string */ public function initial_version(): string { return $this->all()[ self::INITIAL_VERSION ]; } /** * Report cache lifetime, in seconds. * * @return int */ public function report_ttl(): int { return $this->all()[ self::REPORT_TTL ]; } /** * Maximum number of posts inspected in one scan. * * @return int */ public function scan_limit(): int { return $this->all()[ self::SCAN_LIMIT ]; } /** * Post types chosen for the scan; empty means "detect automatically". * * @return string[] */ public function scan_post_types(): array { return $this->all()[ self::SCAN_POST_TYPES ]; } }