From 53c975b06792d323e638a5d53874c9744332f553 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Tue, 20 Nov 2018 01:53:51 -0500 Subject: [PATCH] Block API: Preserve unknown, respect `null` in server attributes preparation (#12003) * Framework: Preserve unknown attributes in block render * Framework: Respect null as a valid attribute type * REST API: Update block renderer test to omit undefined defaults * REST API: Assign default for block renderer attributes Previously we would pass `NULL` to `render`. While `render` provides a default `array()` value, since an explicit value is provided, it would not take effect. With this change, the endpoint assures a default value to be provided as a parameter to get the `get_item` callback that is compatible with the function signature of `WP_BlockType::render`. Alternatively, it could be considered to have separate invocations of `render`, depending on whether the request parameter is set. --- lib/class-wp-block-type.php | 39 ++++++++++++------- ...lass-wp-rest-block-renderer-controller.php | 1 + phpunit/class-block-type-test.php | 21 +++++++++- ...ss-rest-block-renderer-controller-test.php | 4 +- 4 files changed, 48 insertions(+), 17 deletions(-) diff --git a/lib/class-wp-block-type.php b/lib/class-wp-block-type.php index 0c18bb6efa4682..c186eec88a0a3b 100644 --- a/lib/class-wp-block-type.php +++ b/lib/class-wp-block-type.php @@ -120,36 +120,47 @@ public function is_dynamic() { /** * Validates attributes against the current block schema, populating - * defaulted and missing values, and omitting unknown attributes. + * defaulted and missing values. * * @param array $attributes Original block attributes. * @return array Prepared block attributes. */ public function prepare_attributes_for_render( $attributes ) { + // If there are no attribute definitions for the block type, skip + // processing and return vebatim. if ( ! isset( $this->attributes ) ) { return $attributes; } - $prepared_attributes = array(); + foreach ( $attributes as $attribute_name => $value ) { + // If the attribute is not defined by the block type, it cannot be + // validated. + if ( ! isset( $this->attributes[ $attribute_name ] ) ) { + continue; + } - foreach ( $this->attributes as $attribute_name => $schema ) { - $value = null; + $schema = $this->attributes[ $attribute_name ]; - if ( isset( $attributes[ $attribute_name ] ) ) { - $is_valid = rest_validate_value_from_schema( $attributes[ $attribute_name ], $schema ); - if ( ! is_wp_error( $is_valid ) ) { - $value = rest_sanitize_value_from_schema( $attributes[ $attribute_name ], $schema ); - } + // Validate value by JSON schema. An invalid value should revert to + // its default, if one exists. This occurs by virtue of the missing + // attributes loop immediately following. If there is not a default + // assigned, the attribute value should remain unset. + $is_valid = rest_validate_value_from_schema( $value, $schema ); + if ( is_wp_error( $is_valid ) ) { + unset( $attributes[ $attribute_name ] ); } + } - if ( is_null( $value ) && isset( $schema['default'] ) ) { - $value = $schema['default']; + // Populate values of any missing attributes for which the block type + // defines a default. + $missing_schema_attributes = array_diff_key( $this->attributes, $attributes ); + foreach ( $missing_schema_attributes as $attribute_name => $schema ) { + if ( isset( $schema['default'] ) ) { + $attributes[ $attribute_name ] = $schema['default']; } - - $prepared_attributes[ $attribute_name ] = $value; } - return $prepared_attributes; + return $attributes; } /** diff --git a/lib/class-wp-rest-block-renderer-controller.php b/lib/class-wp-rest-block-renderer-controller.php index 3ecf25de449301..b9839f080376a2 100644 --- a/lib/class-wp-rest-block-renderer-controller.php +++ b/lib/class-wp-rest-block-renderer-controller.php @@ -59,6 +59,7 @@ public function register_routes() { 'type' => 'object', 'additionalProperties' => false, 'properties' => $block_type->get_attributes(), + 'default' => array(), ), 'post_id' => array( 'description' => __( 'ID of the post context.', 'gutenberg' ), diff --git a/phpunit/class-block-type-test.php b/phpunit/class-block-type-test.php index cada34b83cc09a..648893082b1041 100644 --- a/phpunit/class-block-type-test.php +++ b/phpunit/class-block-type-test.php @@ -139,7 +139,8 @@ function test_prepare_attributes() { 'wrongType' => 5, 'wrongTypeDefaulted' => 5, /* missingDefaulted */ - 'undefined' => 'omit', + 'undefined' => 'include', + 'intendedNull' => null, ); $block_type = new WP_Block_Type( @@ -160,6 +161,10 @@ function test_prepare_attributes() { 'type' => 'string', 'default' => 'define', ), + 'intendedNull' => array( + 'type' => array( 'string', 'null' ), + 'default' => 'wrong', + ), ), ) ); @@ -169,14 +174,26 @@ function test_prepare_attributes() { $this->assertEquals( array( 'correct' => 'include', - 'wrongType' => null, + /* wrongType */ 'wrongTypeDefaulted' => 'defaulted', 'missingDefaulted' => 'define', + 'undefined' => 'include', + 'intendedNull' => null, ), $prepared_attributes ); } + function test_prepare_attributes_none_defined() { + $attributes = array( 'exists' => 'keep' ); + + $block_type = new WP_Block_Type( 'core/dummy', array() ); + + $prepared_attributes = $block_type->prepare_attributes_for_render( $attributes ); + + $this->assertEquals( $attributes, $prepared_attributes ); + } + function test_has_block_with_mixed_content() { $mixed_post_content = 'before' . '' . diff --git a/phpunit/class-rest-block-renderer-controller-test.php b/phpunit/class-rest-block-renderer-controller-test.php index 66191e122ededb..3691c4d162af4f 100644 --- a/phpunit/class-rest-block-renderer-controller-test.php +++ b/phpunit/class-rest-block-renderer-controller-test.php @@ -272,7 +272,9 @@ public function test_get_item_default_attributes() { $block_type = WP_Block_Type_Registry::get_instance()->get_registered( self::$block_name ); $defaults = array(); foreach ( $block_type->attributes as $key => $attribute ) { - $defaults[ $key ] = isset( $attribute['default'] ) ? $attribute['default'] : null; + if ( isset( $attribute['default'] ) ) { + $defaults[ $key ] = $attribute['default']; + } } $request = new WP_REST_Request( 'GET', self::$rest_api_route . self::$block_name );