$fix ) { $fixes[ $id ] = (bool) $fix['default']; } return array( 'fixes' => $fixes, 'background_scan' => true, 'delete_data' => true, 'ai_provider' => 'none', 'ai_hosted_consent' => false, 'ai_auto_apply' => false, 'share_stats' => false, ); } /** * All settings merged with defaults. * * @return array */ public static function all() { $stored = get_option( self::OPTION, array() ); $stored = is_array( $stored ) ? $stored : array(); $defaults = self::defaults(); $settings = array_merge( $defaults, $stored ); // New fixes added in an update keep their default until the owner changes them. $settings['fixes'] = array_merge( $defaults['fixes'], isset( $stored['fixes'] ) && is_array( $stored['fixes'] ) ? $stored['fixes'] : array() ); return $settings; } /** * One setting. * * @param string $key Setting key. * @return mixed */ public static function get( $key ) { $settings = self::all(); return isset( $settings[ $key ] ) ? $settings[ $key ] : null; } /** * Enabled state of every fix. * * @return array */ public static function fixes() { return array_map( 'boolval', self::get( 'fixes' ) ); } /** * Saves settings pushed by an agency through Fixlight Pro: which fixes are * on, and the background check. Other settings stay as they are. * * @since 0.1.0 * * @param array $fixes Fix ID => on. * @param bool $background_scan Whether the background check runs. * @return bool Whether anything changed. */ public static function save_managed( array $fixes, $background_scan ) { $current = self::all(); $clean = $current; foreach ( array_keys( Catalog::fixes() ) as $id ) { if ( isset( $fixes[ $id ] ) ) { $clean['fixes'][ $id ] = (bool) $fixes[ $id ]; } } $clean['background_scan'] = (bool) $background_scan; if ( $clean === $current ) { return false; } // Saved as built here: the form sanitizer only reads the settings screens' forms. $filter = 'sanitize_option_' . self::OPTION; $had = remove_filter( $filter, array( self::class, 'sanitize' ) ); update_option( self::OPTION, $clean ); if ( $had ) { add_filter( $filter, array( self::class, 'sanitize' ) ); } if ( $clean['fixes'] !== $current['fixes'] ) { Cache::purge(); delete_transient( 'fixlight_summary' ); } return true; } /** * Sanitizes the settings form. * * @param mixed $input Submitted value. * @return array */ public static function sanitize( $input ) { $input = is_array( $input ) ? $input : array(); $current = self::all(); $clean = $current; if ( ! isset( $input['form'] ) ) { // A whole settings array, not a form: WordPress passes the saved value through // here again when the option is first created, and code may save it directly. return self::normalize( array_merge( $current, $input ) ); } if ( 'fixes' === $input['form'] ) { foreach ( array_keys( Catalog::fixes() ) as $id ) { $clean['fixes'][ $id ] = ! empty( $input['fixes'][ $id ] ); } } if ( 'settings' === $input['form'] ) { $clean['background_scan'] = ! empty( $input['background_scan'] ); $clean['delete_data'] = ! empty( $input['delete_data'] ); $provider = isset( $input['ai_provider'] ) ? sanitize_key( $input['ai_provider'] ) : 'none'; $clean['ai_provider'] = ( 'none' === $provider || isset( Ai::providers()[ $provider ] ) ) ? $provider : 'none'; $clean['ai_hosted_consent'] = ! empty( $input['ai_hosted_consent'] ); $clean['ai_auto_apply'] = ! empty( $input['ai_auto_apply'] ); $clean['share_stats'] = ! empty( $input['share_stats'] ); if ( 'hosted' === $clean['ai_provider'] && ! $clean['ai_hosted_consent'] ) { add_settings_error( self::OPTION, 'fixlight_ai_consent', __( 'Settings saved. AI alt text stays off until you tick the box to agree to send images to the Fixlight service.', 'fixlight' ), 'warning' ); } } return self::normalize( $clean ); } /** * Keeps only known settings, with the right types. * * @param array $settings Settings. * @return array */ private static function normalize( array $settings ) { $defaults = self::defaults(); $clean = array(); foreach ( $defaults as $key => $default ) { $value = array_key_exists( $key, $settings ) ? $settings[ $key ] : $default; if ( 'fixes' === $key ) { $value = is_array( $value ) ? $value : array(); $fixes = array(); foreach ( $default as $id => $on ) { $fixes[ $id ] = array_key_exists( $id, $value ) ? (bool) $value[ $id ] : $on; } $value = $fixes; } elseif ( 'ai_provider' === $key ) { // Before 0.1.0 shipped, the Connectors choice was saved under the lowercase product name. $value = 'wordpress' === strtolower( (string) $value ) ? 'connectors' : (string) $value; // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText $value = in_array( $value, array( 'none', 'hosted', 'connectors' ), true ) ? $value : 'none'; } else { $value = (bool) $value; } $clean[ $key ] = $value; } return $clean; } }