|null */ private static ?array $cache = null; /** * @return array */ public static function defaults(): array { return array( 'schema' => self::SCHEMA, 'cookie_name' => 'wpat_attribution', 'retention_days' => 90, 'custom_params' => array(), 'public_debug' => false, 'server_backup' => true, ); } /** * All settings, with anything missing or invalid replaced by its default. * * @return array */ public static function all(): array { if ( null === self::$cache ) { $stored = get_option( self::OPTION, array() ); self::$cache = self::normalize( is_array( $stored ) ? $stored : array() ); } return self::$cache; } /** * A single setting value. * * @return mixed */ public static function get( string $key ) { return self::all()[ $key ] ?? null; } /** * Persist settings and refresh the request cache. * * @param array $values Raw values, typically straight from a form. * @return array The sanitised values that were stored. */ public static function save( array $values ): array { // Read before the write, so listeners can compare old and new. $previous = self::all(); $clean = self::sanitize( $values ); update_option( self::OPTION, $clean ); self::$cache = $clean; /** * Fires after the plugin's settings have been stored. * * @param array $settings The values that were stored. * @param array $previous The values they replaced. */ do_action( 'wpat_settings_saved', $clean, $previous ); return $clean; } /** * Force the next read to hit the database again. */ public static function flush_cache(): void { self::$cache = null; } /** * Normalise a stored settings array. * * @param array $values Stored values. * @return array */ public static function normalize( array $values ): array { $merged = $values + self::defaults(); return array( 'schema' => self::SCHEMA, 'cookie_name' => self::clean_cookie_name( (string) $merged['cookie_name'] ), 'retention_days' => self::clamp_retention( $merged['retention_days'] ), 'custom_params' => self::sanitize_custom_params( $merged['custom_params'] ), 'public_debug' => (bool) $merged['public_debug'], 'server_backup' => (bool) $merged['server_backup'], ); } /** * Normalise a settings form submission. * * @param array $values Raw form values. * @return array */ public static function sanitize( array $values ): array { return self::normalize( array( 'cookie_name' => $values['cookie_name'] ?? '', 'retention_days' => $values['retention_days'] ?? self::defaults()['retention_days'], 'custom_params' => $values['custom_params'] ?? array(), 'public_debug' => ! empty( $values['public_debug'] ), 'server_backup' => ! empty( $values['server_backup'] ), ) ); } /** * Keep only the characters a cookie name may contain, falling back to the * default when nothing usable is left. */ private static function clean_cookie_name( string $name ): string { $clean = (string) preg_replace( '/[^A-Za-z0-9_-]/', '', $name ); return '' !== $clean ? $clean : self::defaults()['cookie_name']; } /** * Hold retention inside the supported range. * * @param mixed $days Raw value. */ private static function clamp_retention( $days ): int { return min( self::MAX_RETENTION, max( self::MIN_RETENTION, (int) $days ) ); } /** * Normalise the custom parameter list. * * @param mixed $raw Array of names or a newline separated string. * @return string[] */ private static function sanitize_custom_params( $raw ): array { $names = is_string( $raw ) ? preg_split( '/[\r\n,]+/', $raw ) : (array) $raw; $clean = array(); foreach ( (array) $names as $name ) { $name = strtolower( trim( (string) $name ) ); $name = (string) preg_replace( '/[^a-z0-9_-]/', '', $name ); if ( '' === $name || strlen( $name ) > 40 ) { continue; } if ( in_array( $name, Fields::RESERVED_KEYS, true ) ) { continue; } if ( isset( Fields::utm_params()[ $name ] ) ) { continue; } $clean[ $name ] = $name; } return array_slice( array_values( $clean ), 0, self::PARAM_LIMIT ); } }