1010defined ( 'ABSPATH ' ) || die ();
1111
1212use WP_Block ;
13- use WP_Block_Supports ;
14- use function add_action ;
13+ use WP_Post ;
14+ use WPCOMVIP \BlockDataApi \ContentParser ;
15+
1516use function add_filter ;
16- use function remove_filter ;
17+ use function get_post ;
18+ use function parse_blocks ;
1719
1820/**
1921 * Enhance core/block block by capturing its inner blocks.
@@ -26,59 +28,22 @@ class CoreBlock {
2628 */
2729 private static $ block_name = 'core/block ' ;
2830
29- /**
30- * A store of captured inner blocks. See `capture_inner_blocks`.
31- *
32- * @var array
33- *
34- * @access private
35- */
36- protected static $ captured_inner_blocks = [];
37-
3831 /**
3932 * Initialize the CoreBlock class.
4033 *
4134 * @access private
4235 */
4336 public static function init (): void {
44- add_action ( 'vip_block_data_api__before_block_render ' , [ __CLASS__ , 'setup_before_render ' ], 10 , 0 );
45- add_action ( 'vip_block_data_api__after_block_render ' , [ __CLASS__ , 'cleanup_after_render ' ], 10 , 0 );
46- add_filter ( 'vip_block_data_api__sourced_block_inner_blocks ' , [ __CLASS__ , 'get_inner_blocks ' ], 5 , 4 );
37+ add_filter ( 'vip_block_data_api__sourced_block_inner_blocks ' , [ __CLASS__ , 'get_inner_blocks ' ], 5 , 5 );
4738 add_filter ( 'vip_block_data_api__sourced_block_result ' , [ __CLASS__ , 'remove_content_array ' ], 5 , 2 );
4839 }
4940
5041 /**
51- * Setup before render.
52- */
53- public static function setup_before_render (): void {
54- /**
55- * Hook into the `render_block` filter, which is near the end of WP_Block#render().
56- * This allows us to capture the inner blocks of synced patterns ("core/block").
57- * See `capture_inner_blocks`.
58- */
59- add_filter ( 'render_block ' , [ __CLASS__ , 'capture_inner_blocks ' ], 10 , 3 );
60- }
61-
62- /**
63- * Cleanup after render.
64- */
65- public static function cleanup_after_render () {
66- self ::$ captured_inner_blocks = [];
67- remove_filter ( 'render_block ' , [ __CLASS__ , 'capture_inner_blocks ' ], 10 );
68- }
69-
70- /**
71- * Capture the inner blocks of synced patterns during block rendering. Intended
72- * for use with the `render_block` filter.
73- *
74- * We have no intention of filtering the rendered block content, but this hook
75- * is conveniently located near the end of WP_Block#render() after block
76- * processing is finished. We get access to the parent block via the global
77- * static class `WP_Block_Supports`.
42+ * Get the inner blocks of a synced pattern / reusable block. Intended for use
43+ * with the `vip_block_data_api__sourced_block_inner_blocks` filter.
7844 *
79- * This approach is necessary because synced patterns (core/block) are dynamic
80- * blocks, and core's method of rendering dynamic blocks severs the connection
81- * between the parent block and its inner blocks:
45+ * Synced patterns are dynamic blocks, and core's method of rendering dynamic
46+ * blocks severs the connection between the parent block and its inner blocks:
8247 *
8348 * https://github.com/WordPress/WordPress/blob/6.6.1/wp-includes/class-wp-block.php#L519
8449 *
@@ -88,105 +53,47 @@ public static function cleanup_after_render() {
8853 * missing from the Block Data API. Capturing synced pattern content as inner
8954 * blocks is extremely useful and avoids the need for additional API calls.
9055 *
91- * @param string $block_content Rendered block content.
92- * @param array $parsed_block Parsed block data.
93- * @param WP_Block $block Block instance.
94- * @return string
95- */
96- public static function capture_inner_blocks ( string $ block_content , array $ parsed_block , WP_Block $ block ): string {
97- // If this block is a synced pattern, that means it is finished rendering.
98- // Lock its inner blocks to prevent further captures in case it is rendered
99- // elsewhere in the tree.
100- if ( self ::$ block_name === $ block ->name ) {
101- $ store_key = self ::get_store_key ( $ parsed_block );
102- if ( isset ( self ::$ captured_inner_blocks [ $ store_key ] ) ) {
103- self ::$ captured_inner_blocks [ $ store_key ]['locked ' ] = true ;
104- }
105- }
106-
107- // Get the parent block that is currently being rendered. This is fragile,
108- // but is currently the only way we can get access to the parent block from
109- // inside a dynamic block's render callback function.
110- //
111- // https://github.com/WordPress/WordPress/blob/6.6.1/wp-includes/class-wp-block.php#L517
112- $ parent_block = isset ( WP_Block_Supports::$ block_to_render ) ? WP_Block_Supports::$ block_to_render : [];
113-
114- // If the parent block is not a synced pattern, do nothing.
115- if ( ! isset ( $ parent_block ['attrs ' ]['ref ' ] ) || self ::$ block_name !== $ parent_block ['blockName ' ] ) {
116- return $ block_content ;
117- }
118-
119- // Capture the inner block for this synced pattern.
120- self ::capture_inner_block ( $ parent_block , $ block );
121-
122- return $ block_content ;
123- }
124-
125- /**
126- * Get captured inner blocks for synced patterns. Intended for use with
127- * the `vip_block_data_api__sourced_block_inner_blocks` filter.
56+ * This requires us to reimplement some logic from `render_block_core_block()`:
57+ *
58+ * https://github.com/WordPress/WordPress/blob/6.6.1/wp-includes/blocks/block.php#L19
12859 *
12960 * @param array $inner_blocks Inner blocks.
13061 * @param string $block_name Block name.
131- * @param int|null $_post_id Post ID (unused) .
62+ * @param int|null $post_id Post ID.
13263 * @param array $parsed_block Parsed block data.
13364 * @return array
13465 */
135- public static function get_inner_blocks ( array $ inner_blocks , string $ block_name , int |null $ _post_id , array $ parsed_block ): array {
66+ public static function get_inner_blocks ( array $ inner_blocks , string $ block_name , int |null $ post_id , array $ parsed_block ): array {
67+ // Not a synced pattern? Return the inner blocks unchanged.
13668 if ( self ::$ block_name !== $ block_name || ! isset ( $ parsed_block ['attrs ' ]['ref ' ] ) ) {
13769 return $ inner_blocks ;
13870 }
13971
140- $ store_key = self :: get_store_key ( $ parsed_block ) ;
72+ $ context = [] ;
14173
142- if ( ! isset ( self ::$ captured_inner_blocks [ $ store_key ] ) ) {
143- return $ inner_blocks ;
74+ // Support synced pattern overrides. Copied and adapted from core:
75+ // https://github.com/WordPress/WordPress/blob/6.6.1/wp-includes/blocks/block.php#L81
76+ //
77+ // In our case, we don't need to filter the context since we can pass it in.
78+ if ( isset ( $ parsed_block ['attrs ' ]['content ' ] ) ) {
79+ $ context ['pattern/overrides ' ] = $ parsed_block ['attrs ' ]['content ' ];
14480 }
14581
146- return self ::$ captured_inner_blocks [ $ store_key ]['inner_blocks ' ];
147- }
82+ // Load, parse, and render the inner blocks of the synced pattern, passing
83+ // along its block context. We intentionally do not recursively call
84+ // ContentParser->parse() to avoid calling telemetry and filters again.
85+ $ parser = new ContentParser ();
86+ $ post = get_post ( $ parsed_block ['attrs ' ]['ref ' ] );
14887
149- /**
150- * Create a unique key that can be used to identify a synced pattern. This
151- * allows us to store and retrieve inner blocks for synced patterns and avoid
152- * duplication when they are used multiple times within the same tree.
153- *
154- * Using a hash of attributes is important because they may contain synced
155- * pattern overrides, which can change the inner block content. The attributes
156- * contain the synced pattern post ID, so uniqueness is built-in.
157- *
158- * @param array $parsed_block Parsed block data.
159- * @return string
160- */
161- protected static function get_store_key ( array $ parsed_block ): string {
162- // Include the synced pattern ID in the key just for legibility.
163- $ synced_pattern_id = $ parsed_block ['attrs ' ]['ref ' ] ?? null ;
164- $ attribute_json = wp_json_encode ( $ parsed_block ['attrs ' ] );
165-
166- return sprintf ( '%s_%s ' , strval ( $ synced_pattern_id ), sha1 ( $ attribute_json ) );
167- }
168-
169- /**
170- * Capture inner block for a synced pattern.
171- *
172- * @param array $synced_pattern Synced pattern block (parsed block).
173- * @param WP_Block $block Inner block.
174- */
175- protected static function capture_inner_block ( array $ synced_pattern , WP_Block $ block ): void {
176- $ store_key = self ::get_store_key ( $ synced_pattern );
177- if ( ! isset ( self ::$ captured_inner_blocks [ $ store_key ] ) ) {
178- self ::$ captured_inner_blocks [ $ store_key ] = [
179- 'inner_blocks ' => [],
180- 'locked ' => false ,
181- ];
88+ if ( ! $ post instanceof WP_Post ) {
89+ return [];
18290 }
18391
184- // This pattern has already been rendered somewhere in the tree and is now locked.
185- if ( self ::$ captured_inner_blocks [ $ store_key ]['locked ' ] ) {
186- return ;
187- }
92+ $ blocks = parse_blocks ( $ post ->post_content );
18893
189- self ::$ captured_inner_blocks [ $ store_key ]['inner_blocks ' ][] = $ block ;
94+ return array_map ( function ( array $ block ) use ( $ parser , $ context , $ post_id ): WP_Block {
95+ return $ parser ->render_parsed_block ( $ block , $ post_id , $ context );
96+ }, $ blocks );
19097 }
19198
19299 /**
0 commit comments