diff --git a/inc/class-registration.php b/inc/class-registration.php index fff5da32a..14adcfb45 100644 --- a/inc/class-registration.php +++ b/inc/class-registration.php @@ -529,16 +529,8 @@ public function enqueue_dependencies( $post = null ) { $this->enqueue_block_styles( $post ); if ( has_block( 'core/block', $post ) ) { - $blocks = parse_blocks( $content ); - $blocks = array_filter( - $blocks, - function ( $block ) { - return 'core/block' === $block['blockName'] && isset( $block['attrs']['ref'] ); - } - ); - - foreach ( $blocks as $block ) { - $this->enqueue_dependencies( $block['attrs']['ref'] ); + foreach ( self::get_reusable_block_ids( parse_blocks( $content ) ) as $ref ) { + $this->enqueue_dependencies( $ref ); } } @@ -1230,6 +1222,35 @@ public static function get_active_widgets_content() { return $content; } + /** + * Collect the IDs of every reusable block (synced pattern) in a block tree. + * + * Synced patterns can be nested inside other blocks, so the whole tree is + * walked instead of only its top level. Without this, a pattern placed inside + * a Group block is never found and the blocks it holds get none of their + * assets registered. + * + * @param array> $blocks Parsed blocks. + * @return array List of reusable block IDs. + * @since 3.2.3 + * @access public + */ + public static function get_reusable_block_ids( $blocks ) { + $ids = array(); + + foreach ( $blocks as $block ) { + if ( 'core/block' === $block['blockName'] && isset( $block['attrs']['ref'] ) ) { + $ids[] = (int) $block['attrs']['ref']; + } + + if ( ! empty( $block['innerBlocks'] ) ) { + $ids = array_merge( $ids, self::get_reusable_block_ids( $block['innerBlocks'] ) ); + } + } + + return array_values( array_unique( $ids ) ); + } + /** * Watch and save the used widgets. * diff --git a/tests/test-registration.php b/tests/test-registration.php index d12b2983c..12d2b920b 100644 --- a/tests/test-registration.php +++ b/tests/test-registration.php @@ -244,4 +244,38 @@ public function test_register_blocks_survives_dynamic_renderer_class_whose_file_ $this->assertFalse( class_exists( $class, false ), 'The renderer class must not have been defined.' ); $this->assertCaptchaRegisteredWithoutRenderer(); } + + /** + * A synced pattern nested inside another block must still be found: the blocks + * it holds get none of their assets registered otherwise. + */ + public function test_reusable_block_ids_finds_a_nested_pattern() { + $content = '
'; + + $this->assertEquals( array( 42 ), Registration::get_reusable_block_ids( parse_blocks( $content ) ) ); + } + + /** + * Patterns are collected from every level, each ID only once. + */ + public function test_reusable_block_ids_collects_every_level_without_duplicates() { + $content = '' + . '
' + . '
' + . '
' + . '
' + . '' + . '
'; + + $this->assertEquals( array( 1, 2 ), Registration::get_reusable_block_ids( parse_blocks( $content ) ) ); + } + + /** + * Content without patterns yields nothing, and a pattern block without a ref + * attribute is skipped rather than producing a bogus ID. + */ + public function test_reusable_block_ids_is_empty_without_usable_patterns() { + $this->assertEquals( array(), Registration::get_reusable_block_ids( parse_blocks( '

Text

' ) ) ); + $this->assertEquals( array(), Registration::get_reusable_block_ids( parse_blocks( '' ) ) ); + } }