From 6e3e49a9dce7957a3ebd9e6ae22c9328e2346fae Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 28 Jun 2023 18:41:40 +0200 Subject: [PATCH 01/18] Experiment: Auto-inserting blocks in the editor (via REST API) --- lib/compat/wordpress-6.3/rest-api.php | 17 +- lib/experimental/auto-inserting-blocks.php | 183 ++++++++++++++++++ ...tenberg-rest-block-patterns-controller.php | 40 ++++ lib/experimental/rest-api.php | 11 ++ lib/experiments-page.php | 12 ++ lib/load.php | 6 + packages/block-library/src/avatar/block.json | 5 +- packages/block-library/src/pattern/index.php | 11 +- .../block-library/src/social-links/block.json | 5 +- .../blocks/render-comment-template-test.php | 5 + schemas/json/block.json | 11 ++ 11 files changed, 295 insertions(+), 11 deletions(-) create mode 100644 lib/experimental/auto-inserting-blocks.php create mode 100644 lib/experimental/class-gutenberg-rest-block-patterns-controller.php diff --git a/lib/compat/wordpress-6.3/rest-api.php b/lib/compat/wordpress-6.3/rest-api.php index ecb8f52392fef..e6dc5f48f9e3c 100644 --- a/lib/compat/wordpress-6.3/rest-api.php +++ b/lib/compat/wordpress-6.3/rest-api.php @@ -85,15 +85,16 @@ function add_modified_wp_template_schema() { } add_filter( 'rest_api_init', 'add_modified_wp_template_schema' ); -/** - * Registers the block patterns REST API routes. - */ -function gutenberg_register_rest_block_patterns() { - $block_patterns = new Gutenberg_REST_Block_Patterns_Controller_6_3(); - $block_patterns->register_routes(); +if ( ! gutenberg_is_experiment_enabled( 'gutenberg-auto-inserting-blocks' ) ) { + /** + * Registers the block patterns REST API routes. + */ + function gutenberg_register_rest_block_patterns() { + $block_patterns = new Gutenberg_REST_Block_Patterns_Controller_6_3(); + $block_patterns->register_routes(); + } + add_action( 'rest_api_init', 'gutenberg_register_rest_block_patterns' ); } -add_action( 'rest_api_init', 'gutenberg_register_rest_block_patterns' ); - /** * Registers the Navigation Fallbacks REST API routes. diff --git a/lib/experimental/auto-inserting-blocks.php b/lib/experimental/auto-inserting-blocks.php new file mode 100644 index 0000000000000..b1f342b7b8ee1 --- /dev/null +++ b/lib/experimental/auto-inserting-blocks.php @@ -0,0 +1,183 @@ + 0 ) { + if ( ! is_string( $block['innerContent'][ $chunk_index ] ) ) { + $anchor_block_index--; + } + $chunk_index++; + } + // Since WP_Block::render() iterates over `inner_content` (rather than `inner_blocks`) + // when rendering blocks, we also need to insert a value (`null`, to mark a block + // location) into that array. + array_splice( $block['innerContent'], $chunk_index, 0, array( null ) ); + } + return $block; + }; +} + +function gutenberg_register_auto_inserted_blocks( $settings, $metadata ) { + if ( ! isset( $metadata['__experimentalAutoInsert'] ) ) { + return $settings; + } + $auto_insert = $metadata['__experimentalAutoInsert']; + + $property_mappings = array( + 'before' => 'before', + 'after' => 'after', + 'firstChild' => 'first_child', + 'lastChild' => 'last_child', + ); + + $inserted_block_name = $metadata['name']; + foreach ( $auto_insert as $anchor_block_name => $position ) { + // Avoid infinite recursion (auto-inserting into self). + if ( $inserted_block_name === $anchor_block_name ) { + _doing_it_wrong( + __METHOD__, + __( 'Cannot auto-insert block next to itself.', 'gutenberg' ), + '6.4.0' + ); + continue; + } + + if ( ! isset( $property_mappings[ $position ] ) ) { + continue; + } + + $mapped_position = $property_mappings[ $position ]; + + $inserted_block = array( + 'blockName' => $inserted_block_name, + 'attrs' => array(), + 'innerHTML' => '', + 'innerContent' => array(), + 'innerBlocks' => array(), + ); + + // TODO: In the long run, we'd likely want some sort of registry for auto-inserted blocks. + + // Auto-insert sibling and child blocks into the editor (via the templates and patterns + // REST API endpoints), and auto-insert sibling blocks on the frontend. + $inserter = gutenberg_auto_insert_block( $anchor_block_name, $mapped_position, $inserted_block ); + add_filter( 'gutenberg_serialize_block', $inserter, 10, 1 ); + + $settings['auto_insert'][ $anchor_block_name ] = $mapped_position; + } + + return $settings; +} +add_filter( 'block_type_metadata_settings', 'gutenberg_register_auto_inserted_blocks', 10, 2 ); + +function gutenberg_parse_and_serialize_block_templates( $query_result ) { + foreach ( $query_result as $block_template ) { + if ( 'theme' !== $block_template->source ) { + continue; + } + $blocks = parse_blocks( $block_template->content ); + $block_template->content = gutenberg_serialize_blocks( $blocks ); + } + + return $query_result; +} +add_filter( 'get_block_templates', 'gutenberg_parse_and_serialize_block_templates', 10, 1 ); + +/** + * Filters the block template object after it has been (potentially) fetched from the theme file. + * + * @param WP_Block_Template|null $block_template The found block template, or null if there is none. + */ +function gutenberg_parse_and_serialize_blocks( $block_template ) { + + $blocks = parse_blocks( $block_template->content ); + $block_template->content = gutenberg_serialize_blocks( $blocks ); + + return $block_template; +} +add_filter( 'get_block_file_template', 'gutenberg_parse_and_serialize_blocks', 10, 1 ); + +/** + * Filterable version of `serialize_blocks()`. + * + * This function is identical to `serialize_blocks()`, except that it applies + * the `gutenberg_serialize_block` filter to each block before it is serialized. + * + * @param array $block The block to be serialized. + * @return string The serialized block. + * + * @see serialize_blocks() + */ +function gutenberg_serialize_block( $block ) { + $block_content = ''; + + /** + * Filters a block before it is serialized. + * + * @param array $block The block to be serialized. + */ + $block = apply_filters( 'gutenberg_serialize_block', $block ); + + $index = 0; + foreach ( $block['innerContent'] as $chunk ) { + if ( is_string( $chunk ) ) { + $block_content .= $chunk; + } else { // Compare to WP_Block::render(). + $inner_block = $block['innerBlocks'][ $index++ ]; + $block_content .= gutenberg_serialize_block( $inner_block ); + } + } + + if ( ! is_array( $block['attrs'] ) ) { + $block['attrs'] = array(); + } + + return get_comment_delimited_block_content( + $block['blockName'], + $block['attrs'], + $block_content + ); +} + +function gutenberg_serialize_blocks( $blocks ) { + return implode( '', array_map( 'gutenberg_serialize_block', $blocks ) ); +} diff --git a/lib/experimental/class-gutenberg-rest-block-patterns-controller.php b/lib/experimental/class-gutenberg-rest-block-patterns-controller.php new file mode 100644 index 0000000000000..d56465a2ea0c6 --- /dev/null +++ b/lib/experimental/class-gutenberg-rest-block-patterns-controller.php @@ -0,0 +1,40 @@ +get_data(); + + $blocks = parse_blocks( $data['content'] ); + $data['content'] = gutenberg_serialize_blocks( $blocks ); // Serialize or render? + + return rest_ensure_response( $data ); + } +} diff --git a/lib/experimental/rest-api.php b/lib/experimental/rest-api.php index 7c6a9bf74d739..8e548600b3875 100644 --- a/lib/experimental/rest-api.php +++ b/lib/experimental/rest-api.php @@ -10,6 +10,17 @@ die( 'Silence is golden.' ); } +if ( gutenberg_is_experiment_enabled( 'gutenberg-auto-inserting-blocks' ) ) { + /** + * Registers the block patterns REST API routes. + */ + function gutenberg_register_rest_block_patterns() { + $block_patterns = new Gutenberg_REST_Block_Patterns_Controller(); + $block_patterns->register_routes(); + } + add_action( 'rest_api_init', 'gutenberg_register_rest_block_patterns' ); +} + /** * Registers the customizer nonces REST API routes. */ diff --git a/lib/experiments-page.php b/lib/experiments-page.php index ce30242d20f81..3f468d0cbd12d 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -91,6 +91,18 @@ function gutenberg_initialize_experiments_settings() { ) ); + add_settings_field( + 'gutenberg-auto-inserting-blocks', + __( 'Auto-inserting blocks', 'gutenberg' ), + 'gutenberg_display_experiment_field', + 'gutenberg-experiments', + 'gutenberg_experiments_section', + array( + 'label' => __( 'Test Auto-inserting blocks', 'gutenberg' ), + 'id' => 'gutenberg-auto-inserting-blocks', + ) + ); + register_setting( 'gutenberg-experiments', 'gutenberg-experiments' diff --git a/lib/load.php b/lib/load.php index 71be1068b7061..85e9f9575e6e6 100644 --- a/lib/load.php +++ b/lib/load.php @@ -69,6 +69,9 @@ function gutenberg_is_experiment_enabled( $name ) { require_once __DIR__ . '/experimental/class-wp-rest-customizer-nonces.php'; } require_once __DIR__ . '/experimental/class-gutenberg-rest-template-revision-count.php'; + if ( gutenberg_is_experiment_enabled( 'gutenberg-auto-inserting-blocks' ) ) { + require_once __DIR__ . '/experimental/class-gutenberg-rest-block-patterns-controller.php'; + } require_once __DIR__ . '/experimental/rest-api.php'; } @@ -117,6 +120,9 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/experimental/disable-tinymce.php'; } +if ( gutenberg_is_experiment_enabled( 'gutenberg-auto-inserting-blocks' ) ) { + require __DIR__ . '/experimental/auto-inserting-blocks.php'; +} require __DIR__ . '/experimental/interactivity-api/class-wp-interactivity-store.php'; require __DIR__ . '/experimental/interactivity-api/store.php'; require __DIR__ . '/experimental/interactivity-api/scripts.php'; diff --git a/packages/block-library/src/avatar/block.json b/packages/block-library/src/avatar/block.json index 12e81d68aa767..62fe211aa36fd 100644 --- a/packages/block-library/src/avatar/block.json +++ b/packages/block-library/src/avatar/block.json @@ -49,5 +49,8 @@ } }, "editorStyle": "wp-block-avatar-editor", - "style": "wp-block-avatar" + "style": "wp-block-avatar", + "__experimentalAutoInsert": { + "core/comment-template": "lastChild" + } } diff --git a/packages/block-library/src/pattern/index.php b/packages/block-library/src/pattern/index.php index 6368bdb7b7487..9e243b2c15a02 100644 --- a/packages/block-library/src/pattern/index.php +++ b/packages/block-library/src/pattern/index.php @@ -41,7 +41,16 @@ function render_block_core_pattern( $attributes ) { } $pattern = $registry->get_registered( $slug ); - return do_blocks( $pattern['content'] ); + $content = $pattern['content']; + + if ( gutenberg_is_experiment_enabled( 'gutenberg-auto-inserting-blocks' ) ) { + // TODO: In the long run, we'd likely want to have a filter in the `WP_Block_Patterns_Registry` class + // instead to allow us plugging in code like this. + $blocks = parse_blocks( $content ); + $content = gutenberg_serialize_blocks( $blocks ); + } + + return do_blocks( $content ); } add_action( 'init', 'register_block_core_pattern' ); diff --git a/packages/block-library/src/social-links/block.json b/packages/block-library/src/social-links/block.json index 20206511a4c96..d9d8f31a9c509 100644 --- a/packages/block-library/src/social-links/block.json +++ b/packages/block-library/src/social-links/block.json @@ -85,5 +85,8 @@ { "name": "pill-shape", "label": "Pill Shape" } ], "editorStyle": "wp-block-social-links-editor", - "style": "wp-block-social-links" + "style": "wp-block-social-links", + "__experimentalAutoInsert": { + "core/post-content": "after" + } } diff --git a/phpunit/blocks/render-comment-template-test.php b/phpunit/blocks/render-comment-template-test.php index c297d1729d0dc..19f453fc18594 100644 --- a/phpunit/blocks/render-comment-template-test.php +++ b/phpunit/blocks/render-comment-template-test.php @@ -142,6 +142,9 @@ public function test_inner_block_inserted_by_render_block_data_is_retained() { return $parsed_block; }; + // Remove auto-insertion filter so it won't collide. + remove_filter( 'render_block_data', 'gutenberg_auto_insert_child_block' ); + add_filter( 'render_block_data', $render_block_data_callback, 10, 1 ); $parsed_blocks = parse_blocks( '' @@ -154,6 +157,8 @@ public function test_inner_block_inserted_by_render_block_data_is_retained() { ); $block->render(); remove_filter( 'render_block_data', $render_block_data_callback ); + // Add back auto-insertion filter. + add_filter( 'render_block_data', 'gutenberg_auto_insert_child_block', 10, 1 ); $this->assertSame( 5, $render_block_callback->get_call_count() ); diff --git a/schemas/json/block.json b/schemas/json/block.json index 41434f58e4727..d0c570e9e7098 100644 --- a/schemas/json/block.json +++ b/schemas/json/block.json @@ -859,6 +859,17 @@ "render": { "type": "string", "description": "Template file loaded on the server when rendering a block." + }, + "__experimentalAutoInsert": { + "type": "object", + "description": "Blocks to auto-insert this block next to.", + "patternProperties": { + "[a-zA-Z]": { + "type": "string", + "description": "Position relative to the block to auto-insert this block next to.", + "enum": [ "before", "after", "firstChild", "lastChild" ] + } + } } }, "required": [ "name", "title" ], From c73a1907f9a0d39129f16605b0872360baf42323 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 4 Jul 2023 16:48:13 +0200 Subject: [PATCH 02/18] Don't auto-insert Social Links block anymore --- packages/block-library/src/social-links/block.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/block-library/src/social-links/block.json b/packages/block-library/src/social-links/block.json index d9d8f31a9c509..20206511a4c96 100644 --- a/packages/block-library/src/social-links/block.json +++ b/packages/block-library/src/social-links/block.json @@ -85,8 +85,5 @@ { "name": "pill-shape", "label": "Pill Shape" } ], "editorStyle": "wp-block-social-links-editor", - "style": "wp-block-social-links", - "__experimentalAutoInsert": { - "core/post-content": "after" - } + "style": "wp-block-social-links" } From 919a2fe185c94600817167531c7c0a0596edc1fe Mon Sep 17 00:00:00 2001 From: Bernie Reiter <96308+ockham@users.noreply.github.com> Date: Thu, 6 Jul 2023 15:48:25 +0200 Subject: [PATCH 03/18] Update lib/experimental/auto-inserting-blocks.php --- lib/experimental/auto-inserting-blocks.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/experimental/auto-inserting-blocks.php b/lib/experimental/auto-inserting-blocks.php index b1f342b7b8ee1..786e0dd49302e 100644 --- a/lib/experimental/auto-inserting-blocks.php +++ b/lib/experimental/auto-inserting-blocks.php @@ -111,7 +111,7 @@ function gutenberg_register_auto_inserted_blocks( $settings, $metadata ) { function gutenberg_parse_and_serialize_block_templates( $query_result ) { foreach ( $query_result as $block_template ) { - if ( 'theme' !== $block_template->source ) { + if ( 'custom' === $block_template->source ) { continue; } $blocks = parse_blocks( $block_template->content ); From fbb1c6ddfe0e84f595fd0b53d941793b1e7dd220 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Thu, 6 Jul 2023 15:49:29 +0200 Subject: [PATCH 04/18] Stop auto-inserting Avatar block --- packages/block-library/src/avatar/block.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/block-library/src/avatar/block.json b/packages/block-library/src/avatar/block.json index 62fe211aa36fd..12e81d68aa767 100644 --- a/packages/block-library/src/avatar/block.json +++ b/packages/block-library/src/avatar/block.json @@ -49,8 +49,5 @@ } }, "editorStyle": "wp-block-avatar-editor", - "style": "wp-block-avatar", - "__experimentalAutoInsert": { - "core/comment-template": "lastChild" - } + "style": "wp-block-avatar" } From da643e83b04dd97d33541a3a917a0041363a2ca2 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Fri, 7 Jul 2023 09:46:12 +0200 Subject: [PATCH 05/18] Add missing PHPDoc --- lib/experimental/auto-inserting-blocks.php | 32 +++++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/experimental/auto-inserting-blocks.php b/lib/experimental/auto-inserting-blocks.php index 786e0dd49302e..f38e172d83f7e 100644 --- a/lib/experimental/auto-inserting-blocks.php +++ b/lib/experimental/auto-inserting-blocks.php @@ -56,6 +56,13 @@ function gutenberg_auto_insert_block( $anchor_block, $relative_position, $insert }; } +/** + * Register blocks for auto-insertion. + * + * @param array $settings Array of determined settings for registering a block type. + * @param array $metadata Metadata provided for registering a block type. + * @return array Updated settings array. + */ function gutenberg_register_auto_inserted_blocks( $settings, $metadata ) { if ( ! isset( $metadata['__experimentalAutoInsert'] ) ) { return $settings; @@ -109,6 +116,12 @@ function gutenberg_register_auto_inserted_blocks( $settings, $metadata ) { } add_filter( 'block_type_metadata_settings', 'gutenberg_register_auto_inserted_blocks', 10, 2 ); +/** + * Parse and serialize block templates to allow running filters. + * + * @param WP_Block_Template[] $query_result Array of found block templates. + * @return void + */ function gutenberg_parse_and_serialize_block_templates( $query_result ) { foreach ( $query_result as $block_template ) { if ( 'custom' === $block_template->source ) { @@ -137,21 +150,21 @@ function gutenberg_parse_and_serialize_blocks( $block_template ) { add_filter( 'get_block_file_template', 'gutenberg_parse_and_serialize_blocks', 10, 1 ); /** - * Filterable version of `serialize_blocks()`. + * Filterable version of `serialize_block()`. * - * This function is identical to `serialize_blocks()`, except that it applies + * This function is identical to `serialize_block()`, except that it applies * the `gutenberg_serialize_block` filter to each block before it is serialized. * * @param array $block The block to be serialized. * @return string The serialized block. * - * @see serialize_blocks() + * @see serialize_block() */ function gutenberg_serialize_block( $block ) { $block_content = ''; /** - * Filters a block before it is serialized. + * Filters a parsed block before it is serialized. * * @param array $block The block to be serialized. */ @@ -178,6 +191,17 @@ function gutenberg_serialize_block( $block ) { ); } +/** + * Filterable version of `serialize_blocks()`. + * + * This function is identical to `serialize_blocks()`, except that it applies + * the `gutenberg_serialize_block` filter to each block before it is serialized. + * + * @param array $block The block to be serialized. + * @return string The serialized block. + * + * @see serialize_blocks() + */ function gutenberg_serialize_blocks( $blocks ) { return implode( '', array_map( 'gutenberg_serialize_block', $blocks ) ); } From 2f78d81ea55f83dc1681481aeef8bfcd8d1a9cdf Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Fri, 7 Jul 2023 09:46:48 +0200 Subject: [PATCH 06/18] Whitespace --- .../class-gutenberg-rest-block-patterns-controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/experimental/class-gutenberg-rest-block-patterns-controller.php b/lib/experimental/class-gutenberg-rest-block-patterns-controller.php index d56465a2ea0c6..aee926efd5e07 100644 --- a/lib/experimental/class-gutenberg-rest-block-patterns-controller.php +++ b/lib/experimental/class-gutenberg-rest-block-patterns-controller.php @@ -30,7 +30,7 @@ public function prepare_item_for_response( $item, $request ) { return $response; } - $data = $response->get_data(); + $data = $response->get_data(); $blocks = parse_blocks( $data['content'] ); $data['content'] = gutenberg_serialize_blocks( $blocks ); // Serialize or render? From 7f7faee37b4ed81e10f5d89ec2927f80c7e84f2a Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 10 Jul 2023 11:23:33 +0200 Subject: [PATCH 07/18] Introduce gutenberg_register_auto_inserted_block function --- lib/experimental/auto-inserting-blocks.php | 29 ++++++++++++++++------ 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/lib/experimental/auto-inserting-blocks.php b/lib/experimental/auto-inserting-blocks.php index f38e172d83f7e..a9c5740ef6228 100644 --- a/lib/experimental/auto-inserting-blocks.php +++ b/lib/experimental/auto-inserting-blocks.php @@ -57,7 +57,7 @@ function gutenberg_auto_insert_block( $anchor_block, $relative_position, $insert } /** - * Register blocks for auto-insertion. + * Register blocks for auto-insertion, based on their block.json metadata. * * @param array $settings Array of determined settings for registering a block type. * @param array $metadata Metadata provided for registering a block type. @@ -78,7 +78,7 @@ function gutenberg_register_auto_inserted_blocks( $settings, $metadata ) { $inserted_block_name = $metadata['name']; foreach ( $auto_insert as $anchor_block_name => $position ) { - // Avoid infinite recursion (auto-inserting into self). + // Avoid infinite recursion (auto-inserting next to or into self). if ( $inserted_block_name === $anchor_block_name ) { _doing_it_wrong( __METHOD__, @@ -102,12 +102,7 @@ function gutenberg_register_auto_inserted_blocks( $settings, $metadata ) { 'innerBlocks' => array(), ); - // TODO: In the long run, we'd likely want some sort of registry for auto-inserted blocks. - - // Auto-insert sibling and child blocks into the editor (via the templates and patterns - // REST API endpoints), and auto-insert sibling blocks on the frontend. - $inserter = gutenberg_auto_insert_block( $anchor_block_name, $mapped_position, $inserted_block ); - add_filter( 'gutenberg_serialize_block', $inserter, 10, 1 ); + gutenberg_register_auto_inserted_block( $inserted_block, $mapped_position, $anchor_block_name ); $settings['auto_insert'][ $anchor_block_name ] = $mapped_position; } @@ -116,6 +111,24 @@ function gutenberg_register_auto_inserted_blocks( $settings, $metadata ) { } add_filter( 'block_type_metadata_settings', 'gutenberg_register_auto_inserted_blocks', 10, 2 ); +/** + * Register block for auto-insertion into the frontend and REST API. + * + * Register a block for auto-insertion into the frontend and into the markup + * returned by the templates and patterns REST API endpoints. + * + * @todo In the long run, we'd likely want some sort of registry for auto-inserted blocks. + * + * @param string $inserted_block The name of the block to insert. + * @param string $position The desired position of the auto-inserted block, relative to its anchor block. + * @param string $anchor_block The name of the block to insert the auto-inserted block next to. + * @return void + */ +function gutenberg_register_auto_inserted_block( $inserted_block, $position, $anchor_block ) { + $inserter = gutenberg_auto_insert_block( $anchor_block, $position, $inserted_block ); + add_filter( 'gutenberg_serialize_block', $inserter, 10, 1 ); +} + /** * Parse and serialize block templates to allow running filters. * From 4d21f5c9871cbb30305db3d563ab190965100ebc Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 10 Jul 2023 11:26:10 +0200 Subject: [PATCH 08/18] Harmonize argument order --- lib/experimental/auto-inserting-blocks.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/experimental/auto-inserting-blocks.php b/lib/experimental/auto-inserting-blocks.php index a9c5740ef6228..4c89175926736 100644 --- a/lib/experimental/auto-inserting-blocks.php +++ b/lib/experimental/auto-inserting-blocks.php @@ -8,13 +8,13 @@ /** * Return a function that auto-inserts blocks relative to a given block. * - * @param string $anchor_block The block to insert relative to. - * @param string $relative_position The position relative to the given block. * @param array $inserted_block The block to insert. + * @param string $relative_position The position relative to the given block. + * @param string $anchor_block The block to insert relative to. * @return callable A function that accepts a block's content and returns the content with the inserted block. */ -function gutenberg_auto_insert_block( $anchor_block, $relative_position, $inserted_block ) { - return function( $block ) use ( $anchor_block, $relative_position, $inserted_block ) { +function gutenberg_auto_insert_block( $inserted_block, $relative_position, $anchor_block ) { + return function( $block ) use ( $inserted_block, $relative_position, $anchor_block ) { if ( $anchor_block === $block['blockName'] ) { if ( 'first_child' === $relative_position ) { array_unshift( $block['innerBlocks'], $inserted_block ); @@ -125,7 +125,7 @@ function gutenberg_register_auto_inserted_blocks( $settings, $metadata ) { * @return void */ function gutenberg_register_auto_inserted_block( $inserted_block, $position, $anchor_block ) { - $inserter = gutenberg_auto_insert_block( $anchor_block, $position, $inserted_block ); + $inserter = gutenberg_auto_insert_block( $inserted_block, $position, $anchor_block ); add_filter( 'gutenberg_serialize_block', $inserter, 10, 1 ); } From 5e3bfd0da8a80baf04c6b429665fcfd82294fae7 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 10 Jul 2023 11:32:30 +0200 Subject: [PATCH 09/18] Placate linter --- lib/experimental/auto-inserting-blocks.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/experimental/auto-inserting-blocks.php b/lib/experimental/auto-inserting-blocks.php index 4c89175926736..e48a036df8ba5 100644 --- a/lib/experimental/auto-inserting-blocks.php +++ b/lib/experimental/auto-inserting-blocks.php @@ -125,7 +125,7 @@ function gutenberg_register_auto_inserted_blocks( $settings, $metadata ) { * @return void */ function gutenberg_register_auto_inserted_block( $inserted_block, $position, $anchor_block ) { - $inserter = gutenberg_auto_insert_block( $inserted_block, $position, $anchor_block ); + $inserter = gutenberg_auto_insert_block( $inserted_block, $position, $anchor_block ); add_filter( 'gutenberg_serialize_block', $inserter, 10, 1 ); } @@ -133,7 +133,7 @@ function gutenberg_register_auto_inserted_block( $inserted_block, $position, $an * Parse and serialize block templates to allow running filters. * * @param WP_Block_Template[] $query_result Array of found block templates. - * @return void + * @return WP_Block_Template[] Updated array of found block templates. */ function gutenberg_parse_and_serialize_block_templates( $query_result ) { foreach ( $query_result as $block_template ) { @@ -210,8 +210,8 @@ function gutenberg_serialize_block( $block ) { * This function is identical to `serialize_blocks()`, except that it applies * the `gutenberg_serialize_block` filter to each block before it is serialized. * - * @param array $block The block to be serialized. - * @return string The serialized block. + * @param array $blocks The blocks to be serialized. + * @return string[] The serialized blocks. * * @see serialize_blocks() */ From 954eeedfc6c7dcd8ae5292ca7c65e0138c07ef9f Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 12 Jul 2023 09:57:32 +0200 Subject: [PATCH 10/18] Remove __experimentalAutoInsert from block.json schema Turns out we don't need it for 3rd party blocks to work. --- schemas/json/block.json | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/schemas/json/block.json b/schemas/json/block.json index d0c570e9e7098..41434f58e4727 100644 --- a/schemas/json/block.json +++ b/schemas/json/block.json @@ -859,17 +859,6 @@ "render": { "type": "string", "description": "Template file loaded on the server when rendering a block." - }, - "__experimentalAutoInsert": { - "type": "object", - "description": "Blocks to auto-insert this block next to.", - "patternProperties": { - "[a-zA-Z]": { - "type": "string", - "description": "Position relative to the block to auto-insert this block next to.", - "enum": [ "before", "after", "firstChild", "lastChild" ] - } - } } }, "required": [ "name", "title" ], From 52efdf092f7e9a96a5bd48d55c9a518e79b431b1 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Thu, 13 Jul 2023 14:37:15 +0200 Subject: [PATCH 11/18] Fix gutenberg_register_auto_inserted_block --- lib/experimental/auto-inserting-blocks.php | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/experimental/auto-inserting-blocks.php b/lib/experimental/auto-inserting-blocks.php index e48a036df8ba5..66dda768ceba8 100644 --- a/lib/experimental/auto-inserting-blocks.php +++ b/lib/experimental/auto-inserting-blocks.php @@ -94,15 +94,7 @@ function gutenberg_register_auto_inserted_blocks( $settings, $metadata ) { $mapped_position = $property_mappings[ $position ]; - $inserted_block = array( - 'blockName' => $inserted_block_name, - 'attrs' => array(), - 'innerHTML' => '', - 'innerContent' => array(), - 'innerBlocks' => array(), - ); - - gutenberg_register_auto_inserted_block( $inserted_block, $mapped_position, $anchor_block_name ); + gutenberg_register_auto_inserted_block( $inserted_block_name, $mapped_position, $anchor_block_name ); $settings['auto_insert'][ $anchor_block_name ] = $mapped_position; } @@ -125,6 +117,13 @@ function gutenberg_register_auto_inserted_blocks( $settings, $metadata ) { * @return void */ function gutenberg_register_auto_inserted_block( $inserted_block, $position, $anchor_block ) { + $inserted_block = array( + 'blockName' => $inserted_block, + 'attrs' => array(), + 'innerHTML' => '', + 'innerContent' => array(), + 'innerBlocks' => array(), + ); $inserter = gutenberg_auto_insert_block( $inserted_block, $position, $anchor_block ); add_filter( 'gutenberg_serialize_block', $inserter, 10, 1 ); } From 7d7f70a1ae61d7a08dcac8e1d8f183d1b492b3d4 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 17 Jul 2023 21:13:56 +0200 Subject: [PATCH 12/18] Remove now-obsolete gutenberg_auto_insert_child_block filter handling --- phpunit/blocks/render-comment-template-test.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/phpunit/blocks/render-comment-template-test.php b/phpunit/blocks/render-comment-template-test.php index 19f453fc18594..c297d1729d0dc 100644 --- a/phpunit/blocks/render-comment-template-test.php +++ b/phpunit/blocks/render-comment-template-test.php @@ -142,9 +142,6 @@ public function test_inner_block_inserted_by_render_block_data_is_retained() { return $parsed_block; }; - // Remove auto-insertion filter so it won't collide. - remove_filter( 'render_block_data', 'gutenberg_auto_insert_child_block' ); - add_filter( 'render_block_data', $render_block_data_callback, 10, 1 ); $parsed_blocks = parse_blocks( '' @@ -157,8 +154,6 @@ public function test_inner_block_inserted_by_render_block_data_is_retained() { ); $block->render(); remove_filter( 'render_block_data', $render_block_data_callback ); - // Add back auto-insertion filter. - add_filter( 'render_block_data', 'gutenberg_auto_insert_child_block', 10, 1 ); $this->assertSame( 5, $render_block_callback->get_call_count() ); From 0f564f5345abff5e3e40ccd907c8f79b7e8ea498 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 17 Jul 2023 21:16:26 +0200 Subject: [PATCH 13/18] Coding Standards --- lib/experimental/auto-inserting-blocks.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/experimental/auto-inserting-blocks.php b/lib/experimental/auto-inserting-blocks.php index 66dda768ceba8..5522543295152 100644 --- a/lib/experimental/auto-inserting-blocks.php +++ b/lib/experimental/auto-inserting-blocks.php @@ -124,6 +124,7 @@ function gutenberg_register_auto_inserted_block( $inserted_block, $position, $an 'innerContent' => array(), 'innerBlocks' => array(), ); + $inserter = gutenberg_auto_insert_block( $inserted_block, $position, $anchor_block ); add_filter( 'gutenberg_serialize_block', $inserter, 10, 1 ); } From f3e566ba4d785157fe0976114fc505361d284f76 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 25 Jul 2023 16:55:03 +0200 Subject: [PATCH 14/18] Better document position argument and mapping --- lib/experimental/auto-inserting-blocks.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/experimental/auto-inserting-blocks.php b/lib/experimental/auto-inserting-blocks.php index 5522543295152..371bf8e3f6fed 100644 --- a/lib/experimental/auto-inserting-blocks.php +++ b/lib/experimental/auto-inserting-blocks.php @@ -69,6 +69,12 @@ function gutenberg_register_auto_inserted_blocks( $settings, $metadata ) { } $auto_insert = $metadata['__experimentalAutoInsert']; + /** + * Map the camelCased position string from block.json to the snake_cased block type position + * used in the auto-inserting block registration function. + * + * @var array + */ $property_mappings = array( 'before' => 'before', 'after' => 'after', @@ -113,6 +119,7 @@ function gutenberg_register_auto_inserted_blocks( $settings, $metadata ) { * * @param string $inserted_block The name of the block to insert. * @param string $position The desired position of the auto-inserted block, relative to its anchor block. + * Can be 'before', 'after', 'first_child', or 'last_child'. * @param string $anchor_block The name of the block to insert the auto-inserted block next to. * @return void */ From aa65ac3864a001e81fe33a9771fb16ba097f04fa Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 25 Jul 2023 15:24:36 +0200 Subject: [PATCH 15/18] Add more PHPDoc to better explain purpose of functions --- lib/experimental/auto-inserting-blocks.php | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/experimental/auto-inserting-blocks.php b/lib/experimental/auto-inserting-blocks.php index 371bf8e3f6fed..9df94fcbfcd8f 100644 --- a/lib/experimental/auto-inserting-blocks.php +++ b/lib/experimental/auto-inserting-blocks.php @@ -115,6 +115,9 @@ function gutenberg_register_auto_inserted_blocks( $settings, $metadata ) { * Register a block for auto-insertion into the frontend and into the markup * returned by the templates and patterns REST API endpoints. * + * This is currently done by filtering parsed blocks as obtained from a block template + * template part, or pattern and injecting the auto-inserted block where applicable. + * * @todo In the long run, we'd likely want some sort of registry for auto-inserted blocks. * * @param string $inserted_block The name of the block to insert. @@ -137,7 +140,11 @@ function gutenberg_register_auto_inserted_block( $inserted_block, $position, $an } /** - * Parse and serialize block templates to allow running filters. + * Parse and reserialize block templates to allow running filters. + * + * By parsing a block template's content and then reserializing it + * via `gutenberg_serialize_blocks()`, we are able to run filters + * on the parsed blocks. * * @param WP_Block_Template[] $query_result Array of found block templates. * @return WP_Block_Template[] Updated array of found block templates. @@ -158,6 +165,10 @@ function gutenberg_parse_and_serialize_block_templates( $query_result ) { /** * Filters the block template object after it has been (potentially) fetched from the theme file. * + * By parsing a block template's content and then reserializing it + * via `gutenberg_serialize_blocks()`, we are able to run filters + * on the parsed blocks. + * * @param WP_Block_Template|null $block_template The found block template, or null if there is none. */ function gutenberg_parse_and_serialize_blocks( $block_template ) { @@ -169,6 +180,14 @@ function gutenberg_parse_and_serialize_blocks( $block_template ) { } add_filter( 'get_block_file_template', 'gutenberg_parse_and_serialize_blocks', 10, 1 ); +// Helper functions. +// ----------------- +// The sole purpose of the following two functions (`gutenberg_serialize_block` +// and `gutenberg_serialize_blocks`), which are otherwise copies of their unprefixed +// counterparts (`serialize_block` and `serialize_blocks`) is to apply a filter +// (also called `gutenberg_serialize_block`) as an entry point for modifications +// to the parsed blocks. + /** * Filterable version of `serialize_block()`. * From bfe97c26c3e7a77047162f38b51769b42bfebd6b Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 26 Jul 2023 09:40:14 +0200 Subject: [PATCH 16/18] Derive experimental Patterns controller from 6.3 polyfill --- .../class-gutenberg-rest-block-patterns-controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/experimental/class-gutenberg-rest-block-patterns-controller.php b/lib/experimental/class-gutenberg-rest-block-patterns-controller.php index aee926efd5e07..1ac567959b146 100644 --- a/lib/experimental/class-gutenberg-rest-block-patterns-controller.php +++ b/lib/experimental/class-gutenberg-rest-block-patterns-controller.php @@ -13,7 +13,7 @@ * * @see WP_REST_Controller */ -class Gutenberg_REST_Block_Patterns_Controller extends Gutenberg_REST_Block_Patterns_Controller_6_2 { +class Gutenberg_REST_Block_Patterns_Controller extends Gutenberg_REST_Block_Patterns_Controller_6_3 { /** * Prepare a raw block pattern before it gets output in a REST API response. * From edffdc1136aafa23fbf882140dbcd6286dbf4cb3 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 26 Jul 2023 09:42:56 +0200 Subject: [PATCH 17/18] Add explanatory comment to conditionally loaded 6.3 block patterns controller --- lib/compat/wordpress-6.3/rest-api.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/compat/wordpress-6.3/rest-api.php b/lib/compat/wordpress-6.3/rest-api.php index e6dc5f48f9e3c..d7fa31cd33fe6 100644 --- a/lib/compat/wordpress-6.3/rest-api.php +++ b/lib/compat/wordpress-6.3/rest-api.php @@ -85,6 +85,8 @@ function add_modified_wp_template_schema() { } add_filter( 'rest_api_init', 'add_modified_wp_template_schema' ); +// If the Auto-inserting Blocks experiment is enabled, we load the block patterns +// controller in lib/experimental/rest-api.php instead. if ( ! gutenberg_is_experiment_enabled( 'gutenberg-auto-inserting-blocks' ) ) { /** * Registers the block patterns REST API routes. From b610c373edfb54e89b2eceb601dcaecd8c0f3f5c Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 26 Jul 2023 10:08:31 +0200 Subject: [PATCH 18/18] Avoid gutenberg_ prefixed function in block library --- packages/block-library/src/pattern/index.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/pattern/index.php b/packages/block-library/src/pattern/index.php index 9e243b2c15a02..bc42e891d9f1f 100644 --- a/packages/block-library/src/pattern/index.php +++ b/packages/block-library/src/pattern/index.php @@ -43,7 +43,8 @@ function render_block_core_pattern( $attributes ) { $pattern = $registry->get_registered( $slug ); $content = $pattern['content']; - if ( gutenberg_is_experiment_enabled( 'gutenberg-auto-inserting-blocks' ) ) { + $gutenberg_experiments = get_option( 'gutenberg-experiments' ); + if ( $gutenberg_experiments && ! empty( $gutenberg_experiments['gutenberg-auto-inserting-blocks'] ) ) { // TODO: In the long run, we'd likely want to have a filter in the `WP_Block_Patterns_Registry` class // instead to allow us plugging in code like this. $blocks = parse_blocks( $content );