*/ public static function defaults(): array { $defaults = [ 'company_name' => '', 'phone' => '', 'email' => '', 'address' => '', 'social' => [ 'facebook' => '', 'instagram' => '', 'youtube' => '', 'x' => '', ], 'colors' => [ 'primary' => '#fe9a00', 'secondary' => '#16a34a', ], 'notifications' => [ 'enabled' => true, 'recipient' => '', 'subject' => '', ], 'integrations' => [], ]; /** @var array $filtered */ $filtered = (array) apply_filters( 'fxlc_settings_defaults', $defaults ); return $filtered; } /** * Stored settings merged over defaults, so every known key is always present. * * @return array */ public static function all(): array { $stored = get_option( self::OPTION, [] ); if ( ! is_array( $stored ) ) { $stored = []; } return array_replace_recursive( self::defaults(), $stored ); } public static function get( string $key, mixed $fallback = null ): mixed { $all = self::all(); return $all[ $key ] ?? $fallback; } /** * Seed defaults on activation if the option does not exist yet. */ public static function seed(): void { if ( get_option( self::OPTION, null ) === null ) { add_option( self::OPTION, self::defaults() ); } } /** * Partial update: sanitize input, then merge over the stored values. * * @param array $input * @return array The persisted settings. */ public static function update( array $input ): array { $old = self::all(); $new = array_replace_recursive( $old, self::sanitize( $input ) ); update_option( self::OPTION, $new ); do_action( 'fxlc_settings_updated', $new, $old ); return $new; } /** * Drop unknown keys and sanitize each known value by type. * * @param array $input * @return array */ public static function sanitize( array $input ): array { $allowed = self::defaults(); $clean = []; foreach ( $input as $key => $value ) { if ( ! array_key_exists( $key, $allowed ) ) { continue; } $clean[ $key ] = self::sanitize_value( (string) $key, $value ); } return $clean; } private static function sanitize_value( string $key, mixed $value ): mixed { switch ( $key ) { case 'email': return sanitize_email( (string) $value ); case 'social': $social = is_array( $value ) ? $value : []; return array_map( static fn( $url ): string => esc_url_raw( (string) $url ), $social ); case 'colors': $colors = is_array( $value ) ? $value : []; return array_map( static fn( $hex ): string => (string) sanitize_hex_color( (string) $hex ), $colors ); case 'notifications': $notifications = is_array( $value ) ? $value : []; return [ 'enabled' => ! empty( $notifications['enabled'] ), 'recipient' => self::sanitize_recipients( (string) ( $notifications['recipient'] ?? '' ) ), 'subject' => sanitize_text_field( (string) ( $notifications['subject'] ?? '' ) ), ]; case 'integrations': return self::sanitize_recursive( is_array( $value ) ? $value : [] ); default: return sanitize_text_field( (string) $value ); } } /** * A comma-separated recipient list, keeping only the addresses that survive * sanitize_email(). Typos are dropped rather than silently mailed into the * void, and an empty result falls back to the site admin at send time. */ private static function sanitize_recipients( string $value ): string { $clean = []; foreach ( explode( ',', $value ) as $candidate ) { $email = sanitize_email( trim( $candidate ) ); if ( '' !== $email && is_email( $email ) ) { $clean[] = $email; } } return implode( ', ', $clean ); } /** * @param array $value * @return array */ private static function sanitize_recursive( array $value ): array { $clean = []; foreach ( $value as $k => $v ) { $key = sanitize_key( (string) $k ); $clean[ $key ] = is_array( $v ) ? self::sanitize_recursive( $v ) : sanitize_text_field( (string) $v ); } return $clean; } }