diff --git a/bin/get-server-block-attributes.php b/bin/get-server-block-attributes.php new file mode 100755 index 00000000000000..0570ab9ba8add5 --- /dev/null +++ b/bin/get-server-block-attributes.php @@ -0,0 +1,38 @@ +#!/usr/bin/env php + { } ); setUnknownTypeHandlerName( undefined ); setDefaultBlockName( undefined ); + window._wpBlocksAttributes = {}; console.error = error; } ); @@ -109,6 +110,23 @@ describe( 'blocks', () => { expect( block ).toBeUndefined(); } ); + it( 'should default to browser-initialized global attributes', () => { + const attributes = { ok: { type: 'boolean' } }; + window._wpBlocksAttributes = { + 'core/test-block-with-attributes': attributes, + }; + + const blockType = { settingName: 'settingValue', save: noop, category: 'common' }; + registerBlockType( 'core/test-block-with-attributes', blockType ); + expect( getBlockType( 'core/test-block-with-attributes' ) ).toEqual( { + name: 'core/test-block-with-attributes', + settingName: 'settingValue', + save: noop, + category: 'common', + attributes, + } ); + } ); + it( 'should store a copy of block type', () => { const blockType = { settingName: 'settingValue', save: noop, category: 'common' }; registerBlockType( 'core/test-block-with-settings', blockType ); diff --git a/blocks/library/latest-posts/index.js b/blocks/library/latest-posts/index.js index 6e4e865ea5b27c..7ccdc7a2ee2aaf 100644 --- a/blocks/library/latest-posts/index.js +++ b/blocks/library/latest-posts/index.js @@ -36,28 +36,6 @@ registerBlockType( 'core/latest-posts', { keywords: [ __( 'recent posts' ) ], - attributes: { - postsToShow: { - type: 'number', - default: 5, - }, - displayPostDate: { - type: 'boolean', - default: false, - }, - layout: { - type: 'string', - default: 'list', - }, - columns: { - type: 'number', - default: 3, - }, - align: { - type: 'string', - }, - }, - getEditWrapperProps( attributes ) { const { align } = attributes; if ( 'left' === align || 'right' === align || 'wide' === align || 'full' === align ) { diff --git a/blocks/library/latest-posts/index.php b/blocks/library/latest-posts/index.php index 978f1374da40eb..9335cdebdae7d3 100644 --- a/blocks/library/latest-posts/index.php +++ b/blocks/library/latest-posts/index.php @@ -13,27 +13,8 @@ * @return string Returns the post content with latest posts added. */ function gutenberg_render_block_core_latest_posts( $attributes ) { - $posts_to_show = 5; - - if ( array_key_exists( 'postsToShow', $attributes ) ) { - - // Basic attribute validation. - if ( - is_numeric( $attributes['postsToShow'] ) && - $attributes['postsToShow'] > 0 && - $attributes['postsToShow'] < 100 - ) { - $posts_to_show = intval( $attributes['postsToShow'] ); - } - } - - $align = 'center'; - if ( isset( $attributes['align'] ) && in_array( $attributes['align'], array( 'left', 'right', 'wide', 'full' ), true ) ) { - $align = $attributes['align']; - } - $recent_posts = wp_get_recent_posts( array( - 'numberposts' => $posts_to_show, + 'numberposts' => $attributes['postsToShow'], 'post_status' => 'publish', ) ); @@ -63,7 +44,7 @@ function gutenberg_render_block_core_latest_posts( $attributes ) { $list_items_markup .= "\n"; } - $class = "wp-block-latest-posts align{$align}"; + $class = "wp-block-latest-posts align{$attributes['align']}"; if ( isset( $attributes['layout'] ) && 'grid' === $attributes['layout'] ) { $class .= ' is-grid'; } @@ -82,5 +63,28 @@ function gutenberg_render_block_core_latest_posts( $attributes ) { } register_block_type( 'core/latest-posts', array( + 'attributes' => array( + 'postsToShow' => array( + 'type' => 'number', + 'default' => 5, + ), + 'displayPostDate' => array( + 'type' => 'boolean', + 'default' => false, + ), + 'layout' => array( + 'type' => 'string', + 'default' => 'list', + ), + 'columns' => array( + 'type' => 'number', + 'default' => 3, + ), + 'align' => array( + 'type' => 'string', + 'default' => 'center', + ), + ), + 'render_callback' => 'gutenberg_render_block_core_latest_posts', ) ); diff --git a/blocks/test/fixtures/core__latest-posts.json b/blocks/test/fixtures/core__latest-posts.json index c1e9e6f8d44334..8f17313f15f966 100644 --- a/blocks/test/fixtures/core__latest-posts.json +++ b/blocks/test/fixtures/core__latest-posts.json @@ -7,7 +7,8 @@ "postsToShow": 5, "displayPostDate": false, "layout": "list", - "columns": 3 + "columns": 3, + "align": "center" } } ] diff --git a/blocks/test/fixtures/core__latest-posts__displayPostDate.json b/blocks/test/fixtures/core__latest-posts__displayPostDate.json index 238718c80ea868..58ed17cb9dc059 100644 --- a/blocks/test/fixtures/core__latest-posts__displayPostDate.json +++ b/blocks/test/fixtures/core__latest-posts__displayPostDate.json @@ -7,7 +7,8 @@ "postsToShow": 5, "displayPostDate": true, "layout": "list", - "columns": 3 + "columns": 3, + "align": "center" } } ] diff --git a/blocks/test/full-content.js b/blocks/test/full-content.js index daf002e8c9ccc1..4601439d9b3608 100644 --- a/blocks/test/full-content.js +++ b/blocks/test/full-content.js @@ -89,6 +89,8 @@ function normalizeParsedBlocks( blocks ) { describe( 'full post content fixture', () => { beforeAll( () => { + window._wpBlocksAttributes = require( './server-attributes.json' ); + // Register all blocks. require( 'blocks' ); } ); diff --git a/blocks/test/server-attributes.json b/blocks/test/server-attributes.json new file mode 100644 index 00000000000000..ffbc5ddd6aad97 --- /dev/null +++ b/blocks/test/server-attributes.json @@ -0,0 +1,24 @@ +{ + "core\/latest-posts": { + "postsToShow": { + "type": "number", + "default": 5 + }, + "displayPostDate": { + "type": "boolean", + "default": false + }, + "layout": { + "type": "string", + "default": "list" + }, + "columns": { + "type": "number", + "default": 3 + }, + "align": { + "type": "string", + "default": "center" + } + } +} \ No newline at end of file diff --git a/lib/class-wp-block-type-registry.php b/lib/class-wp-block-type-registry.php index a715f109cdda79..3c0c5221dba910 100644 --- a/lib/class-wp-block-type-registry.php +++ b/lib/class-wp-block-type-registry.php @@ -45,6 +45,7 @@ final class WP_Block_Type_Registry { * ones described below are supported by default. Default empty array. * * @type callable $render_callback Callback used to render blocks of this block type. + * @type array $attributes Block attributes mapping, property name to schema. * } * @return WP_Block_Type|false The registered block type on success, or false on failure. */ diff --git a/lib/class-wp-block-type.php b/lib/class-wp-block-type.php index e66f1083e4e03b..930e711aed661a 100644 --- a/lib/class-wp-block-type.php +++ b/lib/class-wp-block-type.php @@ -32,6 +32,15 @@ class WP_Block_Type { */ public $render_callback; + /** + * Block type attributes property schemas. + * + * @since 0.10.0 + * @access public + * @var array + */ + public $attributes; + /** * Constructor. * @@ -71,9 +80,45 @@ public function render( $attributes = array(), $content = null ) { return $content; } + $attributes = $this->prepare_attributes_for_render( $attributes ); + return call_user_func( $this->render_callback, $attributes, $content ); } + /** + * Validates attributes against the current block schema, populating + * defaulted and missing values, and omitting unknown attributes. + * + * @param array $attributes Original block attributes. + * @return array Prepared block attributes. + */ + public function prepare_attributes_for_render( $attributes ) { + if ( ! isset( $this->attributes ) ) { + return $attributes; + } + + $prepared_attributes = array(); + + foreach ( $this->attributes as $attribute_name => $schema ) { + $value = null; + + if ( isset( $attributes[ $attribute_name ] ) ) { + $is_valid = rest_validate_value_from_schema( $attributes[ $attribute_name ], $schema ); + if ( ! is_wp_error( $is_valid ) ) { + $value = $attributes[ $attribute_name ]; + } + } + + if ( is_null( $value ) && isset( $schema['default'] ) ) { + $value = $schema['default']; + } + + $prepared_attributes[ $attribute_name ] = $value; + } + + return $prepared_attributes; + } + /** * Sets block type properties. * diff --git a/lib/client-assets.php b/lib/client-assets.php index 77ce1758361ad2..71684f7c628a77 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -769,6 +769,16 @@ function gutenberg_editor_scripts_and_styles( $hook ) { 'before' ); + // Preload server-registered block schemas. + $block_registry = WP_Block_Type_Registry::get_instance(); + $schemas = array(); + foreach ( $block_registry->get_all_registered() as $block_name => $block_type ) { + if ( isset( $block_type->attributes ) ) { + $schemas[ $block_name ] = $block_type->attributes; + } + } + wp_localize_script( 'wp-blocks', '_wpBlocksAttributes', $schemas ); + // Initialize the editor. $gutenberg_theme_support = get_theme_support( 'gutenberg' ); $color_palette = gutenberg_color_palette(); diff --git a/package.json b/package.json index 86189f7d33403a..e8a764e588b04f 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,8 @@ "test": "npm run lint && npm run test-unit", "ci": "concurrently \"npm run lint && npm run build\" \"npm run test-unit:coverage-ci\"", "fixtures:clean": "rimraf \"blocks/test/fixtures/*.+(json|serialized.html)\"", - "fixtures:generate": "cross-env GENERATE_MISSING_FIXTURES=y npm run test-unit", + "fixtures:server-attributes": "./bin/get-server-block-attributes.php > blocks/test/server-attributes.json", + "fixtures:generate": "npm run fixtures:server-attributes && cross-env GENERATE_MISSING_FIXTURES=y npm run test-unit", "fixtures:regenerate": "npm run fixtures:clean && npm run fixtures:generate", "package-plugin": "./bin/build-plugin-zip.sh", "docs-start": "./docutron/bin/cli.js start", diff --git a/phpunit/class-block-type-test.php b/phpunit/class-block-type-test.php index 213f182c572652..dfa776cca10379 100644 --- a/phpunit/class-block-type-test.php +++ b/phpunit/class-block-type-test.php @@ -63,6 +63,44 @@ function test_render_without_callback() { $this->assertSame( $content, $output ); } + function test_prepare_attributes() { + $attributes = array( + 'correct' => 'include', + 'wrongType' => 5, + 'wrongTypeDefaulted' => 5, + /* missingDefaulted */ + 'undefined' => 'omit', + ); + + $block_type = new WP_Block_Type( 'core/dummy', array( + 'attributes' => array( + 'correct' => array( + 'type' => 'string', + ), + 'wrongType' => array( + 'type' => 'string', + ), + 'wrongTypeDefaulted' => array( + 'type' => 'string', + 'default' => 'defaulted', + ), + 'missingDefaulted' => array( + 'type' => 'string', + 'default' => 'define', + ), + ), + ) ); + + $prepared_attributes = $block_type->prepare_attributes_for_render( $attributes ); + + $this->assertEquals( array( + 'correct' => 'include', + 'wrongType' => null, + 'wrongTypeDefaulted' => 'defaulted', + 'missingDefaulted' => 'define', + ), $prepared_attributes ); + } + function render_dummy_block( $attributes ) { return json_encode( $attributes ); }