Make WordPress Core


Ignore:
Timestamp:
08/15/2019 05:16:21 PM (7 years 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.