From ca15c02db9c3c2fd044ad3c2e6e06905991a2c9c Mon Sep 17 00:00:00 2001 From: Alec Geatches Date: Wed, 27 Mar 2024 13:24:59 +0100 Subject: [PATCH 1/9] Add vip_block_data_api__before_parse_post_content filter --- src/parser/content-parser.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/parser/content-parser.php b/src/parser/content-parser.php index 599bfc2..1e4e424 100644 --- a/src/parser/content-parser.php +++ b/src/parser/content-parser.php @@ -128,6 +128,14 @@ public function parse( $post_content, $post_id = null, $filter_options = [] ) { $parsing_error = false; try { + /** + * Filters content before parsing blocks in a post. + * + * @param string $post_content The content of the post being parsed. + * @param int $post_id Post ID associated with the content. + */ + $post_content = apply_filters( 'vip_block_data_api__before_parse_post_content', $post_content, $post_id ); + $blocks = parse_blocks( $post_content ); $blocks = array_values( array_filter( $blocks, function ( $block ) { $is_whitespace_block = ( null === $block['blockName'] && empty( trim( $block['innerHTML'] ) ) ); From aada721cb36511d9dba962c65049b5dfb68ca081 Mon Sep 17 00:00:00 2001 From: Alec Geatches Date: Wed, 27 Mar 2024 14:58:32 +0100 Subject: [PATCH 2/9] Add post-parse vip_block_data_api__after_parse_blocks filter too --- src/parser/content-parser.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/parser/content-parser.php b/src/parser/content-parser.php index 1e4e424..18392ed 100644 --- a/src/parser/content-parser.php +++ b/src/parser/content-parser.php @@ -176,6 +176,15 @@ public function parse( $post_content, $post_id = null, $filter_options = [] ) { 'details' => $parsing_error->__toString(), ] ); } else { + /** + * Filters the API result before returning parsed blocks in a post. + * + * @param string $result The successful API result, contains 'blocks' + * key with an array of block data, and optionally 'warnings' and 'debug' keys. + * @param int $post_id Post ID associated with the content. + */ + $result = apply_filters( 'vip_block_data_api__after_parse_blocks', $result, $post_id ); + return $result; } } From b238b2c021495ebab5fcc0915b839bcce515ad89 Mon Sep 17 00:00:00 2001 From: Alec Geatches Date: Mon, 8 Apr 2024 10:35:21 -0600 Subject: [PATCH 3/9] Add documentation for new filters --- README.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/README.md b/README.md index 4b5cf4c..b565698 100644 --- a/README.md +++ b/README.md @@ -1218,6 +1218,64 @@ Direct block HTML can be accessed through `$block['innerHTML']`. This may be use For another example of how this filter can be used to extend block data, we have implemented a default image block filter in [`src/parser/block-additions/core-image.php`][repo-core-image-block-addition]. This filter is automatically called on `core/image` blocks to add `width` and `height` to image attributes. +--- + +### `vip_block_data_api__before_parse_post_content` + +Modify raw post content before it's parsed by the Block Data API. + +```php +/** + * Filters content before parsing blocks in a post. + * + * @param string $post_content The content of the post being parsed. + * @param int $post_id Post ID associated with the content. + */ +$post_content = apply_filters( 'vip_block_data_api__before_parse_post_content', $post_content, $post_id ); +``` + +--- + +### `vip_block_data_api__after_parse_blocks` + +Modify the Block Data API REST endpoint response. + +```php +/** + * Filters the API result before returning parsed blocks in a post. + * + * @param string $result The successful API result, contains 'blocks' key with an array + * of block data, and optionally 'warnings' and 'debug' keys. + * @param int $post_id Post ID associated with the content. + */ +$result = apply_filters( 'vip_block_data_api__after_parse_blocks', $result, $post_id ); +``` + +This filter is called directly before returning a result in the REST API. Use this filter to add additional metadata or debug information to the API output. + +```php +add_action( 'vip_block_data_api__after_parse_blocks', 'add_block_data_debug_info', 10, 2 ); + +function add_block_data_debug_info( $result, $post_id ) { + $result['debug']['my-value'] = 123; + + return $result; +} +``` + +This would add `debug.my-value` to all Block Data API REST results: + +```bash +> curl https://my.site/wp-json/vip-block-data-api/v1/posts/1/blocks + +{ + "debug": { + "my-value": 123 + }, + "blocks": [ /* ... */ ] +} +``` + ## Analytics **Please note that, this is for VIP sites only. Analytics are disabled if this plugin is not being run on VIP sites.** From 8b06ef16f1d8a038ffb77e5e229c4a17d0edc3c1 Mon Sep 17 00:00:00 2001 From: Alec Geatches Date: Mon, 8 Apr 2024 11:02:36 -0600 Subject: [PATCH 4/9] Add test for before_parse filter --- tests/parser/test-content-parser.php | 56 -------------- tests/parser/test-parser-filters.php | 110 +++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 56 deletions(-) create mode 100644 tests/parser/test-parser-filters.php diff --git a/tests/parser/test-content-parser.php b/tests/parser/test-content-parser.php index f76c00f..a5b90d0 100644 --- a/tests/parser/test-content-parser.php +++ b/tests/parser/test-content-parser.php @@ -13,62 +13,6 @@ class ContentParserTest extends RegistryTestCase { /* Multiple attributes */ - public function test_block_filter_via_code() { - $this->register_block_with_attributes( 'test/block1', [ - 'content' => [ - 'type' => 'string', - 'source' => 'html', - 'selector' => 'div.a', - ], - ] ); - - $this->register_block_with_attributes( 'test/block2', [ - 'url' => [ - 'type' => 'string', - 'source' => 'attribute', - 'selector' => 'img', - 'attribute' => 'src', - ], - ] ); - - $html = ' - -
Block 1
- - - - - - '; - - $expected_blocks = [ - [ - 'name' => 'test/block1', - 'attributes' => [ - 'content' => 'Block 1', - ], - ], - ]; - - $block_filter_function = function ( $is_block_included, $block_name ) { - if ( 'test/block2' === $block_name ) { - return false; - } else { - return true; - } - }; - - add_filter( 'vip_block_data_api__allow_block', $block_filter_function, 10, 2 ); - $content_parser = new ContentParser( $this->registry ); - $blocks = $content_parser->parse( $html ); - remove_filter( 'vip_block_data_api__allow_block', $block_filter_function, 10, 2 ); - - $this->assertArrayNotHasKey( 'errors', $blocks ); - $this->assertNotEmpty( $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); - $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); - $this->assertEquals( $expected_blocks, $blocks['blocks'] ); - } - public function test_parse_multiple_attributes_from_block() { $this->register_block_with_attributes( 'test/captioned-image', [ 'caption' => [ diff --git a/tests/parser/test-parser-filters.php b/tests/parser/test-parser-filters.php new file mode 100644 index 0000000..d5a01b8 --- /dev/null +++ b/tests/parser/test-parser-filters.php @@ -0,0 +1,110 @@ +register_block_with_attributes( 'test/block1', [ + 'content' => [ + 'type' => 'string', + 'source' => 'html', + 'selector' => 'div.a', + ], + ] ); + + $this->register_block_with_attributes( 'test/block2', [ + 'url' => [ + 'type' => 'string', + 'source' => 'attribute', + 'selector' => 'img', + 'attribute' => 'src', + ], + ] ); + + $html = ' + +
Block 1
+ + + + + + '; + + $expected_blocks = [ + [ + 'name' => 'test/block1', + 'attributes' => [ + 'content' => 'Block 1', + ], + ], + ]; + + $block_filter_function = function ( $is_block_included, $block_name ) { + if ( 'test/block2' === $block_name ) { + return false; + } else { + return true; + } + }; + + add_filter( 'vip_block_data_api__allow_block', $block_filter_function, 10, 2 ); + $content_parser = new ContentParser( $this->registry ); + $blocks = $content_parser->parse( $html ); + remove_filter( 'vip_block_data_api__allow_block', $block_filter_function, 10, 2 ); + + $this->assertArrayNotHasKey( 'errors', $blocks ); + $this->assertNotEmpty( $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); + $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); + $this->assertEquals( $expected_blocks, $blocks['blocks'] ); + } + + public function test_before_parse_post_content_filter() { + $this->register_block_with_attributes( 'test/valid-block', [ + 'content' => [ + 'type' => 'rich-text', + 'source' => 'rich-text', + 'selector' => 'code', + ], + ] ); + + $html = ' + + Block content! + + '; + + $expected_blocks = [ + [ + 'name' => 'test/valid-block', + 'attributes' => [ + 'content' => 'Block content!', + ], + ], + ]; + + $replace_post_content_filter = function ( $post_content ) { + return str_replace( 'test/invalid-block', 'test/valid-block', $post_content ); + }; + + add_filter( 'vip_block_data_api__before_parse_post_content', $replace_post_content_filter ); + $content_parser = new ContentParser( $this->registry ); + $blocks = $content_parser->parse( $html ); + remove_filter( 'vip_block_data_api__before_parse_post_content', $replace_post_content_filter ); + + $this->assertArrayNotHasKey( 'errors', $blocks ); + $this->assertNotEmpty( $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); + $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); + $this->assertEquals( $expected_blocks, $blocks['blocks'] ); + } +} From 988a1ed8f01956e46500993b7a3afea0f7a3037e Mon Sep 17 00:00:00 2001 From: Alec Geatches Date: Mon, 8 Apr 2024 11:03:08 -0600 Subject: [PATCH 5/9] Fix GraphQL tests by removing no-longer-default "cite" attribute --- tests/graphql/test-graphql-api.php | 124 ++++++++++++----------------- 1 file changed, 50 insertions(+), 74 deletions(-) diff --git a/tests/graphql/test-graphql-api.php b/tests/graphql/test-graphql-api.php index 0683837..777cd39 100644 --- a/tests/graphql/test-graphql-api.php +++ b/tests/graphql/test-graphql-api.php @@ -30,12 +30,12 @@ public function test_get_blocks_data() {

Welcome to WordPress. This is your first post. Edit or delete it, then start writing!

- +

This is a heading inside a quote

- +

This is a heading

@@ -49,68 +49,60 @@ public function test_get_blocks_data() { [ 'name' => 'core/paragraph', 'attributes' => [ - array( + [ 'name' => 'content', 'value' => 'Welcome to WordPress. This is your first post. Edit or delete it, then start writing!', - ), - array( + ], + [ 'name' => 'dropCap', 'value' => '', - ), + ], ], 'id' => '1', ], [ 'name' => 'core/quote', 'attributes' => [ - array( + [ 'name' => 'value', 'value' => '', - ), - array( - 'name' => 'citation', - 'value' => '', - ), + ], ], 'innerBlocks' => [ [ 'name' => 'core/paragraph', 'attributes' => [ - array( + [ 'name' => 'content', 'value' => 'This is a heading inside a quote', - ), - array( + ], + [ 'name' => 'dropCap', 'value' => '', - ), + ], ], 'id' => '3', ], [ 'name' => 'core/quote', 'attributes' => [ - array( + [ 'name' => 'value', 'value' => '', - ), - array( - 'name' => 'citation', - 'value' => '', - ), + ], ], 'innerBlocks' => [ [ 'name' => 'core/heading', 'attributes' => [ - array( + [ 'name' => 'content', 'value' => 'This is a heading', - ), - array( + ], + [ 'name' => 'level', 'value' => '2', - ), + ], ], 'id' => '5', ], @@ -137,68 +129,60 @@ public function test_flatten_inner_blocks() { [ 'name' => 'core/paragraph', 'attributes' => [ - array( + [ 'name' => 'content', 'value' => 'Welcome to WordPress. This is your first post. Edit or delete it, then start writing!', - ), - array( + ], + [ 'name' => 'dropCap', 'value' => '', - ), + ], ], 'id' => '2', ], [ 'name' => 'core/quote', 'attributes' => [ - array( + [ 'name' => 'value', 'value' => '', - ), - array( - 'name' => 'citation', - 'value' => '', - ), + ], ], 'innerBlocks' => [ [ 'name' => 'core/paragraph', 'attributes' => [ - array( + [ 'name' => 'content', 'value' => 'This is a heading inside a quote', - ), - array( + ], + [ 'name' => 'dropCap', 'value' => '', - ), + ], ], 'id' => '4', ], [ 'name' => 'core/quote', 'attributes' => [ - array( + [ 'name' => 'value', 'value' => '', - ), - array( - 'name' => 'citation', - 'value' => '', - ), + ], ], 'innerBlocks' => [ [ 'name' => 'core/heading', 'attributes' => [ - array( + [ 'name' => 'content', 'value' => 'This is a heading', - ), - array( + ], + [ 'name' => 'level', 'value' => '2', - ), + ], ], 'id' => '6', ], @@ -214,14 +198,14 @@ public function test_flatten_inner_blocks() { [ 'name' => 'core/paragraph', 'attributes' => [ - array( + [ 'name' => 'content', 'value' => 'Welcome to WordPress. This is your first post. Edit or delete it, then start writing!', - ), - array( + ], + [ 'name' => 'dropCap', 'value' => '', - ), + ], ], 'parentId' => '1', 'id' => '2', @@ -229,14 +213,10 @@ public function test_flatten_inner_blocks() { [ 'name' => 'core/quote', 'attributes' => [ - array( + [ 'name' => 'value', 'value' => '', - ), - array( - 'name' => 'citation', - 'value' => '', - ), + ], ], 'id' => '3', 'parentId' => '1', @@ -244,14 +224,14 @@ public function test_flatten_inner_blocks() { [ 'name' => 'core/paragraph', 'attributes' => [ - array( + [ 'name' => 'content', 'value' => 'This is a heading inside a quote', - ), - array( + ], + [ 'name' => 'dropCap', 'value' => '', - ), + ], ], 'id' => '4', 'parentId' => '3', @@ -259,14 +239,10 @@ public function test_flatten_inner_blocks() { [ 'name' => 'core/quote', 'attributes' => [ - array( + [ 'name' => 'value', 'value' => '', - ), - array( - 'name' => 'citation', - 'value' => '', - ), + ], ], 'id' => '5', 'parentId' => '3', @@ -274,14 +250,14 @@ public function test_flatten_inner_blocks() { [ 'name' => 'core/heading', 'attributes' => [ - array( + [ 'name' => 'content', 'value' => 'This is a heading', - ), - array( + ], + [ 'name' => 'level', 'value' => '2', - ), + ], ], 'id' => '6', 'parentId' => '5', From fc73fc5b85c37901e6270ee92a88f1f847307d28 Mon Sep 17 00:00:00 2001 From: Alec Geatches Date: Mon, 8 Apr 2024 11:03:24 -0600 Subject: [PATCH 6/9] Convert some array() usage to [] --- tests/rest/test-rest-api.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/rest/test-rest-api.php b/tests/rest/test-rest-api.php index 2524297..0995c1a 100644 --- a/tests/rest/test-rest-api.php +++ b/tests/rest/test-rest-api.php @@ -209,7 +209,7 @@ public function test_rest_api_does_not_return_excluded_blocks_for_post() { $request = new WP_REST_Request( 'GET', sprintf( '/vip-block-data-api/v1/posts/%d/blocks', $post_id ) ); // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude - $request->set_query_params( array( 'exclude' => 'core/paragraph,core/separator' ) ); + $request->set_query_params( [ 'exclude' => 'core/paragraph,core/separator' ] ); $response = $this->server->dispatch( $request ); @@ -270,7 +270,7 @@ public function test_rest_api_only_returns_included_blocks_for_post() { ]; $request = new WP_REST_Request( 'GET', sprintf( '/vip-block-data-api/v1/posts/%d/blocks', $post_id ) ); - $request->set_query_params( array( 'include' => 'core/heading' ) ); + $request->set_query_params( [ 'include' => 'core/heading' ] ); $response = $this->server->dispatch( $request ); @@ -430,11 +430,11 @@ public function test_rest_api_returns_error_for_include_and_exclude_filter() { $this->expectExceptionMessage( 'vip-block-data-api-invalid-params' ); $request = new WP_REST_Request( 'GET', sprintf( '/vip-block-data-api/v1/posts/%d/blocks', $post_id ) ); - $request->set_query_params( array( + $request->set_query_params( [ // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude 'exclude' => 'core/paragraph,core/separator', 'include' => 'core/heading,core/quote,core/media-text', - ) ); + ] ); $response = $this->server->dispatch( $request ); From 8d970c64974e93c0909044f9295ceaae34cc140a Mon Sep 17 00:00:00 2001 From: Alec Geatches Date: Mon, 8 Apr 2024 11:11:19 -0600 Subject: [PATCH 7/9] Add after_parse_blocks filter test --- tests/parser/test-parser-filters.php | 48 +++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/tests/parser/test-parser-filters.php b/tests/parser/test-parser-filters.php index d5a01b8..de99543 100644 --- a/tests/parser/test-parser-filters.php +++ b/tests/parser/test-parser-filters.php @@ -11,7 +11,7 @@ * Content parser filter testing. */ class ParserFiltersTest extends RegistryTestCase { - /* vip_block_data_api__allow_block filter */ + /* vip_block_data_api__allow_block */ public function test_allow_block_filter_via_code() { $this->register_block_with_attributes( 'test/block1', [ @@ -69,6 +69,8 @@ public function test_allow_block_filter_via_code() { $this->assertEquals( $expected_blocks, $blocks['blocks'] ); } + /* vip_block_data_api__before_parse_post_content */ + public function test_before_parse_post_content_filter() { $this->register_block_with_attributes( 'test/valid-block', [ 'content' => [ @@ -107,4 +109,48 @@ public function test_before_parse_post_content_filter() { $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); $this->assertEquals( $expected_blocks, $blocks['blocks'] ); } + + /* vip_block_data_api__after_parse_blocks */ + + public function test_after_parse_filter() { + $this->register_block_with_attributes( 'test/paragraph', [ + 'content' => [ + 'type' => 'rich-text', + 'source' => 'rich-text', + 'selector' => 'p', + ], + ] ); + + $html = ' + +

Paragaph text

+ + '; + + $expected_blocks = [ + [ + 'name' => 'test/paragraph', + 'attributes' => [ + 'content' => 'Paragaph text', + ], + ], + ]; + + $add_extra_data_filter = function ( $result ) { + $result['my-key'] = 'my-value'; + + return $result; + }; + + add_filter( 'vip_block_data_api__after_parse_blocks', $add_extra_data_filter ); + $content_parser = new ContentParser( $this->registry ); + $result = $content_parser->parse( $html ); + remove_filter( 'vip_block_data_api__after_parse_blocks', $add_extra_data_filter ); + + $this->assertArrayNotHasKey( 'errors', $result ); + $this->assertNotEmpty( $result, sprintf( 'Unexpected parser output: %s', wp_json_encode( $result ) ) ); + $this->assertArrayHasKey( 'blocks', $result, sprintf( 'Unexpected parser output: %s', wp_json_encode( $result ) ) ); + $this->assertEquals( $expected_blocks, $result['blocks'] ); + $this->assertEquals( 'my-value', $result['my-key'] ); + } } From f7534215612b0592570b0c68075bfef3a207191e Mon Sep 17 00:00:00 2001 From: Alec Geatches Date: Mon, 8 Apr 2024 15:32:50 -0600 Subject: [PATCH 8/9] Add before_parse filter example --- README.md | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b565698..dc7bfc8 100644 --- a/README.md +++ b/README.md @@ -1222,7 +1222,7 @@ For another example of how this filter can be used to extend block data, we have ### `vip_block_data_api__before_parse_post_content` -Modify raw post content before it's parsed by the Block Data API. +Modify raw post content before it's parsed by the Block Data API. The `$post_content` provided by this filter is directly what is stored in the post database before any processing occurs. ```php /** @@ -1234,6 +1234,41 @@ Modify raw post content before it's parsed by the Block Data API. $post_content = apply_filters( 'vip_block_data_api__before_parse_post_content', $post_content, $post_id ); ``` +For example, this could be used to modify a block's type before parsing. The code below replaces instances of `test/invalid-block` blocks with `core/paragraph`: + +```php +add_filter( 'vip_block_data_api__before_parse_post_content', 'replace_invalid_blocks' ); + +function replace_invalid_blocks( $post_content, $post_id ) { + return str_replace( 'wp:test/invalid-block', 'wp:paragraph', $post_content ); +} + +$html = ' + +

Block content!

+ +'; + +$content_parser = new ContentParser(); +$result = $content_parser->parse( $html ); + +// Evaluates to true +assertEquals( [ + [ + 'name' => 'core/paragraph', + 'attributes' => [ + 'content' => 'Block content!', + ], + ], +], $result['blocks'] ); +``` + +**Warning** + +Be careful with content modification before parsing. In the example above, if a block contained the text "wp:test/invalid-block" outside of a block header, this would also be changed to "core/paragraph". This is likely not the intent of the code. + +All block markup is sensitive to changes, even changes in whitespace. We've added this filter to make the plugin flexible, but any transforms to `post_content` should be done with extreme care. Strongly consider adding tests to any usage of this filter. + --- ### `vip_block_data_api__after_parse_blocks` From c547220bb0d578ca9108bab5037bfeb8a851a5fa Mon Sep 17 00:00:00 2001 From: Alec Geatches Date: Mon, 8 Apr 2024 16:58:18 -0600 Subject: [PATCH 9/9] Fix mistake in warning text --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dc7bfc8..71a4744 100644 --- a/README.md +++ b/README.md @@ -1265,7 +1265,7 @@ assertEquals( [ **Warning** -Be careful with content modification before parsing. In the example above, if a block contained the text "wp:test/invalid-block" outside of a block header, this would also be changed to "core/paragraph". This is likely not the intent of the code. +Be careful with content modification before parsing. In the example above, if a block contained the text "wp:test/invalid-block" outside of a block header, this would also be changed to "wp:paragraph". This is likely not the intent of the code. All block markup is sensitive to changes, even changes in whitespace. We've added this filter to make the plugin flexible, but any transforms to `post_content` should be done with extreme care. Strongly consider adding tests to any usage of this filter.