> $fields Field definitions. * @param array $defaults Optional defaults for "_enabled" and "_priority". * @return array */ public static function get( $key, array $fields, array $defaults = array() ) { $stored = self::stored( $key ); $values = array( '_enabled' => array_key_exists( '_enabled', $stored ) ? ! empty( $stored['_enabled'] ) : ! empty( $defaults['_enabled'] ), '_priority' => isset( $stored['_priority'] ) ? (int) $stored['_priority'] : ( isset( $defaults['_priority'] ) ? (int) $defaults['_priority'] : 50 ), ); foreach ( $fields as $name => $field ) { $raw = isset( $stored[ $name ] ) ? $stored[ $name ] : ( isset( $field['default'] ) ? $field['default'] : '' ); if ( 'password' === self::type_of( $field ) && Crypto::is_encrypted( (string) $raw ) ) { $plain = Crypto::decrypt( (string) $raw ); $raw = null === $plain ? '' : $plain; } $values[ $name ] = $raw; } return $values; } /** * Tells whether an encrypted secret exists but can no longer be read. * * @param string $key Component key. * @param string $field Field name. * @return bool */ public static function is_unreadable( $key, $field ) { $stored = self::stored( $key ); return isset( $stored[ $field ] ) && Crypto::is_encrypted( (string) $stored[ $field ] ) && null === Crypto::decrypt( (string) $stored[ $field ] ); } /** * Tells whether a secret field currently holds a value. * * @param string $key Component key. * @param string $field Field name. * @return bool */ public static function has_secret( $key, $field ) { $stored = self::stored( $key ); return ! empty( $stored[ $field ] ); } /** * Saves submitted values for a component. * * @param string $key Component key. * @param array> $fields Field definitions. * @param array $input Raw submitted values (already unslashed). * @return void */ public static function save( $key, array $fields, array $input ) { $all = self::all_stored(); $stored = isset( $all[ $key ] ) ? $all[ $key ] : array(); $next = $stored; $next['_enabled'] = ! empty( $input['_enabled'] ) && '0' !== (string) $input['_enabled']; $next['_priority'] = isset( $input['_priority'] ) ? max( 1, min( 999, (int) $input['_priority'] ) ) : 50; foreach ( $fields as $name => $field ) { if ( ! array_key_exists( $name, $input ) || is_array( $input[ $name ] ) ) { continue; } $clean = self::sanitize_field( $field, (string) $input[ $name ] ); if ( 'password' === self::type_of( $field ) ) { // An empty password box means "keep the current secret". if ( '' === $clean ) { continue; } $clean = Crypto::encrypt( $clean ); } $next[ $name ] = $clean; } $all[ $key ] = $next; update_option( self::OPTION_NAME, $all, false ); } /** * Writes one value directly, encrypting it when the field is a secret. * * Used by code that creates values itself, such as a webhook secret. * * @param string $key Component key. * @param string $name Field name. * @param string $value Plain value. * @param bool $encrypted Whether to encrypt the value. * @return void */ public static function set_value( $key, $name, $value, $encrypted = false ) { $all = self::all_stored(); $all[ $key ][ $name ] = $encrypted ? Crypto::encrypt( $value ) : $value; update_option( self::OPTION_NAME, $all, false ); } /** * Sanitizes one submitted value for its field type. * * @param array $field Field definition. * @param string $value Raw value. * @return string */ public static function sanitize_field( array $field, $value ) { switch ( self::type_of( $field ) ) { case 'password': return trim( preg_replace( '/[\x00-\x1F\x7F]/', '', $value ) ); case 'select': $choices = isset( $field['choices'] ) ? array_keys( $field['choices'] ) : array(); return in_array( $value, array_map( 'strval', $choices ), true ) ? $value : (string) ( isset( $field['default'] ) ? $field['default'] : '' ); case 'textarea': return sanitize_textarea_field( $value ); case 'url': return esc_url_raw( trim( $value ) ); case 'email': return sanitize_email( trim( $value ) ); default: return sanitize_text_field( $value ); } } /** * Returns a field's type, defaulting to text. * * @param array $field Field definition. * @return string */ public static function type_of( array $field ) { return isset( $field['type'] ) ? (string) $field['type'] : 'text'; } /** * Returns the raw stored array of one component. * * @param string $key Component key. * @return array */ private static function stored( $key ) { $all = self::all_stored(); return isset( $all[ $key ] ) && is_array( $all[ $key ] ) ? $all[ $key ] : array(); } /** * Returns every stored component array. * * @return array> */ private static function all_stored() { $all = get_option( self::OPTION_NAME, array() ); return is_array( $all ) ? $all : array(); } }