diff --git a/.changeset/cold-doors-give.md b/.changeset/cold-doors-give.md new file mode 100644 index 00000000..043601dc --- /dev/null +++ b/.changeset/cold-doors-give.md @@ -0,0 +1,68 @@ +--- +"@wpengine/wp-graphql-content-blocks": major +--- + +Replaced core/block with core/synced-pattern for reusable blocks, aligning with WP 6.3's synced patterns. + +**🚨Breaking change🚨** This update does not break functionality for WP < 6.3, but it alters the GraphQL response structure. + +Query: +``` +{ + posts { + nodes { + editorBlocks { + name + clientId + parentClientId + ... on CoreSyncedPattern { + attributes { + slug + } + name + innerBlocks { + name + clientId + parentClientId + } + } + } + } + } +} +``` +Response: +``` +{ + "data": { + "posts": { + "nodes": [ + { + "editorBlocks": [ + { + "name": "core/synced-pattern", + "clientId": "67b317909b801", + "parentClientId": null, + "attributes": { + "slug": "my-synced-pattern" + }, + "innerBlocks": [ + { + "name": "core/group", + "clientId": "67b317909b89a", + "parentClientId": null + } + ] + }, + { + "name": "core/group", + "clientId": "67b317909b89a", + "parentClientId": "67b317909b801" + } + ] + } + ] + } + } +} +``` diff --git a/includes/Data/ContentBlocksResolver.php b/includes/Data/ContentBlocksResolver.php index 0188f6c8..c9d1a14e 100644 --- a/includes/Data/ContentBlocksResolver.php +++ b/includes/Data/ContentBlocksResolver.php @@ -87,7 +87,6 @@ public static function resolve_content_blocks( $node, $args, $allowed_block_name * @param array $allowed_block_names The list of allowed block names to filter. */ $parsed_blocks = apply_filters( 'wpgraphql_content_blocks_resolve_blocks', $parsed_blocks, $node, $args, $allowed_block_names ); - return is_array( $parsed_blocks ) ? $parsed_blocks : []; } @@ -100,7 +99,6 @@ public static function resolve_content_blocks( $node, $args, $allowed_block_name */ private static function parse_blocks( $content ): array { $blocks = parse_blocks( $content ); - return self::handle_do_blocks( $blocks ); } @@ -153,7 +151,6 @@ private static function handle_do_block( array $block ): ?array { $block = self::populate_reusable_blocks( $block ); $block = self::populate_pattern_inner_blocks( $block ); $block = self::populate_navigation_blocks( $block ); - // Prepare innerBlocks. if ( ! empty( $block['innerBlocks'] ) ) { $block['innerBlocks'] = self::handle_do_blocks( $block['innerBlocks'] ); @@ -173,6 +170,11 @@ private static function is_block_empty( array $block ): bool { return false; } + if ( isset( $block['attrs']['ref'] ) ) { + // If 'ref' exists, it's likely a synced pattern, so we don't consider it empty. + return false; + } + // If there is no innerHTML and no innerContent, we can consider it empty. if ( empty( $block['innerHTML'] ) && empty( $block['innerContent'] ) ) { return true; @@ -294,8 +296,11 @@ private static function populate_reusable_blocks( array $block ): array { if ( empty( $parsed_blocks ) ) { return $block; } - - return array_merge( ...$parsed_blocks ); + // Wrap it in a core/synced-pattern block instead of flattening + $block['blockName'] = 'core/synced-pattern'; + $block['attrs']['slug'] = $reusable_block->post_name; + $block['innerBlocks'] = $parsed_blocks; + return $block; } /** @@ -380,6 +385,12 @@ private static function filter_allowed_blocks( array $blocks, array $allowed_blo return array_filter( $blocks, static function ( $block ) use ( $allowed_block_names ) { + // Allow 'core/synced-pattern' to pass through without filtering. + // We need to ensure this block is included regardless of the allowed block names + // because it's handled separately (e.g., it should be rendered but not manually inserted by the user). + if ( 'core/synced-pattern' === $block['blockName'] ) { + return true; + } return in_array( $block['blockName'], $allowed_block_names, true ); } ); diff --git a/includes/Registry/Registry.php b/includes/Registry/Registry.php index dacc69e7..ab8cd61c 100644 --- a/includes/Registry/Registry.php +++ b/includes/Registry/Registry.php @@ -68,6 +68,7 @@ public function init(): void { $this->register_interface_types(); $this->register_scalar_types(); $this->register_support_block_types(); + $this->register_custom_block_types(); $this->register_block_types(); } @@ -257,6 +258,41 @@ static function ( $post_type ) { }//end foreach } + /** + * Registers custom block types. + * + * The 'core/synced-pattern' block is a reusable synced pattern block and + * is not meant to appear in the block editor selection but is used + * internally for resolving reusable content. + */ + protected function register_custom_block_types(): void { + $registry = \WP_Block_Type_Registry::get_instance(); + $block_name = 'core/synced-pattern'; + + if ( ! $registry->is_registered( $block_name ) ) { + $registry->register( + $block_name, + [ + 'name' => $block_name, + 'title' => __( 'Synced Pattern', 'wp-graphql-content-blocks' ), + 'icon' => null, + 'category' => 'theme', + 'attributes' => [ + 'ref' => [ + 'type' => 'number', + ], + 'slug' => [ + 'type' => 'string', + ], + ], + 'render_callback' => static function ( $attributes, $content ) { + return $content; + }, + ] + ); + } + } + /** * Register Scalar types to the GraphQL Schema * diff --git a/tests/unit/ContentBlocksResolverTest.php b/tests/unit/ContentBlocksResolverTest.php index 0bf575c4..d837f436 100644 --- a/tests/unit/ContentBlocksResolverTest.php +++ b/tests/unit/ContentBlocksResolverTest.php @@ -101,10 +101,16 @@ public function tearDown(): void { public function test_resolve_content_blocks_resolves_reusable_blocks() { $post_model = new Post( get_post( $this->reusable_post_id ) ); $actual = $this->instance->resolve_content_blocks( $post_model, [ 'flat' => true ] ); - - // There should return only the non-empty blocks - $this->assertEquals( 3, count( $actual ) ); - $this->assertEquals( 'core/columns', $actual[0]['blockName'] ); + + $this->assertNotEmpty( $actual ); + $this->assertEquals( 'core/synced-pattern', $actual[0]['blockName'] ); + + $this->assertArrayHasKey( 'attrs', $actual[0] ); + $this->assertArrayHasKey( 'ref', $actual[0]['attrs'] ); + $this->assertArrayHasKey( 'slug', $actual[0]['attrs'] ); + + $this->assertEquals( 'core/columns', $actual[1]['blockName'] ); + $this->assertCount( 4, $actual ); } public function test_resolve_content_blocks_filters_empty_blocks() {