WordPress.org

Make WordPress Core


Ignore:
Timestamp:
08/15/2019 05:16:21 PM (5 weeks ago)
Author:
kadamwhite
Message:

REST API: Support 'object' and 'array' types in register_meta() schemas.

Extends meta registration to support complex schema values, mirroring the functionality in the settings controller.
Error when trying to modify a meta key containing schema-nonconformant data.

Props @TimothyBlynJacobs, @birgire, @mnelson4, @flixos90.
Fixes #43392.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php

    r45681 r45807  
    144144             */
    145145            if ( is_null( $meta[ $name ] ) ) {
     146                $args = $this->get_registered_fields()[ $meta_key ];
     147
     148                if ( $args['single'] ) {
     149                    $current = get_metadata( $this->get_meta_type(), $object_id, $meta_key, true );
     150
     151                    if ( is_wp_error( rest_validate_value_from_schema( $current, $args['schema'] ) ) ) {
     152                        return new WP_Error(
     153                            'rest_invalid_stored_value',
     154                            /* translators: %s: custom field key */
     155                            sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ),
     156                            array( 'status' => 500 )
     157                        );
     158                    }
     159                }
     160
    146161                $result = $this->delete_meta_value( $object_id, $meta_key, $name );
    147162                if ( is_wp_error( $result ) ) {
     
    151166            }
    152167
    153             $is_valid = rest_validate_value_from_schema( $meta[ $name ], $args['schema'], 'meta.' . $name );
     168            $value = $meta[ $name ];
     169
     170            if ( ! $args['single'] && is_array( $value ) && count( array_filter( $value, 'is_null' ) ) ) {
     171                return new WP_Error(
     172                    'rest_invalid_stored_value',
     173                    /* translators: %s: custom field key */
     174                    sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ),
     175                    array( 'status' => 500 )
     176                );
     177            }
     178
     179            $is_valid = rest_validate_value_from_schema( $value, $args['schema'], 'meta.' . $name );
    154180            if ( is_wp_error( $is_valid ) ) {
    155181                $is_valid->add_data( array( 'status' => 400 ) );
     
    157183            }
    158184
    159             $value = rest_sanitize_value_from_schema( $meta[ $name ], $args['schema'] );
     185            $value = rest_sanitize_value_from_schema( $value, $args['schema'] );
    160186
    161187            if ( $args['single'] ) {
     
    261287        }
    262288
    263         // `delete_metadata` removes _all_ instances of the value, so only call once.
    264         $to_remove = array_unique( $to_remove );
     289        // `delete_metadata` removes _all_ instances of the value, so only call once. Otherwise,
     290        // `delete_metadata` will return false for subsequent calls of the same value.
     291        // Use serialization to produce a predictable string that can be used by array_unique.
     292        $to_remove = array_map( 'maybe_unserialize', array_unique( array_map( 'maybe_serialize', $to_remove ) ) );
    265293
    266294        foreach ( $to_remove as $value ) {
     
    394422            $type = ! empty( $rest_args['schema']['type'] ) ? $rest_args['schema']['type'] : $type;
    395423
    396             if ( ! in_array( $type, array( 'string', 'boolean', 'integer', 'number' ) ) ) {
     424            if ( null === $rest_args['schema']['default'] ) {
     425                $rest_args['schema']['default'] = $this->get_default_for_type( $type );
     426            }
     427
     428            $rest_args['schema'] = $this->default_additional_properties_to_false( $rest_args['schema'] );
     429
     430            if ( ! in_array( $type, array( 'string', 'boolean', 'integer', 'number', 'array', 'object' ) ) ) {
    397431                continue;
    398432            }
    399433
    400434            if ( empty( $rest_args['single'] ) ) {
    401                 $rest_args['schema']['items'] = array(
    402                     'type' => $rest_args['type'],
     435                $rest_args['schema'] = array(
     436                    'type'  => 'array',
     437                    'items' => $rest_args['schema'],
    403438                );
    404                 $rest_args['schema']['type']  = 'array';
    405439            }
    406440
     
    453487     */
    454488    public static function prepare_value( $value, $request, $args ) {
    455         $type = $args['schema']['type'];
    456 
    457         // For multi-value fields, check the item type instead.
    458         if ( 'array' === $type && ! empty( $args['schema']['items']['type'] ) ) {
    459             $type = $args['schema']['items']['type'];
    460         }
    461 
    462         switch ( $type ) {
    463             case 'string':
    464                 $value = (string) $value;
    465                 break;
    466             case 'integer':
    467                 $value = (int) $value;
    468                 break;
    469             case 'number':
    470                 $value = (float) $value;
    471                 break;
    472             case 'boolean':
    473                 $value = (bool) $value;
    474                 break;
    475         }
    476 
    477         // Don't allow objects to be output.
    478         if ( is_object( $value ) && ! ( $value instanceof JsonSerializable ) ) {
     489
     490        if ( $args['single'] ) {
     491            $schema = $args['schema'];
     492        } else {
     493            $schema = $args['schema']['items'];
     494        }
     495
     496        if ( is_wp_error( rest_validate_value_from_schema( $value, $schema ) ) ) {
    479497            return null;
    480498        }
    481499
    482         return $value;
     500        return rest_sanitize_value_from_schema( $value, $schema );
    483501    }
    484502
     
    500518        return $value;
    501519    }
     520
     521    /**
     522     * Recursively add additionalProperties = false to all objects in a schema if no additionalProperties setting
     523     * is specified.
     524     *
     525     * This is needed to restrict properties of objects in meta values to only
     526     * registered items, as the REST API will allow additional properties by
     527     * default.
     528     *
     529     * @since 5.3.0
     530     *
     531     * @param array $schema The schema array.
     532     * @return array
     533     */
     534    protected function default_additional_properties_to_false( $schema ) {
     535        switch ( $schema['type'] ) {
     536            case 'object':
     537                foreach ( $schema['properties'] as $key => $child_schema ) {
     538                    $schema['properties'][ $key ] = $this->default_additional_properties_to_false( $child_schema );
     539                }
     540
     541                if ( ! isset( $schema['additionalProperties'] ) ) {
     542                    $schema['additionalProperties'] = false;
     543                }
     544                break;
     545            case 'array':
     546                $schema['items'] = $this->default_additional_properties_to_false( $schema['items'] );
     547                break;
     548        }
     549
     550        return $schema;
     551    }
     552
     553    /**
     554     * Gets the default value for a schema type.
     555     *
     556     * @since 5.3.0
     557     *
     558     * @param string $type
     559     * @return mixed
     560     */
     561    protected function get_default_for_type( $type ) {
     562        switch ( $type ) {
     563            case 'string':
     564                return '';
     565            case 'boolean':
     566                return false;
     567            case 'integer':
     568                return 0;
     569            case 'number':
     570                return 0.0;
     571            case 'array':
     572                return array();
     573            case 'object':
     574                return array();
     575            default:
     576                return null;
     577        }
     578    }
    502579}
Note: See TracChangeset for help on using the changeset viewer.