Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 31 additions & 10 deletions inc/class-registration.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}
}

Expand Down Expand Up @@ -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<int, array<string, mixed>> $blocks Parsed blocks.
* @return array<int, int> 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.
*
Expand Down
34 changes: 34 additions & 0 deletions tests/test-registration.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '<!-- wp:group --><div class="wp-block-group"><!-- wp:block {"ref":42} /--></div><!-- /wp:group -->';

$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 = '<!-- wp:block {"ref":1} /-->'
. '<!-- wp:group --><div class="wp-block-group">'
. '<!-- wp:columns --><div class="wp-block-columns">'
. '<!-- wp:column --><div class="wp-block-column"><!-- wp:block {"ref":2} /--></div><!-- /wp:column -->'
. '</div><!-- /wp:columns -->'
. '<!-- wp:block {"ref":1} /-->'
. '</div><!-- /wp:group -->';

$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( '<!-- wp:paragraph --><p>Text</p><!-- /wp:paragraph -->' ) ) );
$this->assertEquals( array(), Registration::get_reusable_block_ids( parse_blocks( '<!-- wp:block /-->' ) ) );
}
}