From c0ae6307adb1112f6d553c5633c11df16253c21e Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Sat, 13 Jun 2026 20:38:28 +0900 Subject: [PATCH 1/3] Excerpt: Honor the visibility block support when generating excerpts. Blocks hidden via the `blockVisibility` metadata are stripped from rendered content because `do_blocks()` runs them through the `render_block` filter, where `wp_render_block_visibility_support()` returns an empty string. Excerpt generation bypasses that filter: `wp_trim_excerpt()` unhooks `do_blocks` and instead walks the parsed blocks via `excerpt_remove_blocks()`. Wrapper blocks (group, columns, column) are handed to `_excerpt_render_inner_blocks()`, which renders their inner blocks directly and never evaluates the wrapper's own visibility, so a hidden wrapper's content leaked into the excerpt. Skip blocks whose `blockVisibility` metadata is `false` in both `excerpt_remove_blocks()` and `_excerpt_render_inner_blocks()` so the excerpt matches the rendered content. Co-Authored-By: Claude --- src/wp-includes/blocks.php | 10 +++ .../tests/formatting/excerptRemoveBlocks.php | 61 +++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index 6a6418d966457..9a402685450f6 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -2238,6 +2238,11 @@ function excerpt_remove_blocks( $content ) { $output = ''; foreach ( $blocks as $block ) { + // Skip blocks hidden via the visibility block support. + if ( false === ( $block['attrs']['metadata']['blockVisibility'] ?? null ) ) { + continue; + } + if ( in_array( $block['blockName'], $allowed_blocks, true ) ) { if ( ! empty( $block['innerBlocks'] ) ) { if ( in_array( $block['blockName'], $allowed_wrapper_blocks, true ) ) { @@ -2299,6 +2304,11 @@ function _excerpt_render_inner_blocks( $parsed_block, $allowed_blocks ) { $output = ''; foreach ( $parsed_block['innerBlocks'] as $inner_block ) { + // Skip blocks hidden via the visibility block support. + if ( false === ( $inner_block['attrs']['metadata']['blockVisibility'] ?? null ) ) { + continue; + } + if ( ! in_array( $inner_block['blockName'], $allowed_blocks, true ) ) { continue; } diff --git a/tests/phpunit/tests/formatting/excerptRemoveBlocks.php b/tests/phpunit/tests/formatting/excerptRemoveBlocks.php index 2097c35bbf5b8..0f64386523687 100644 --- a/tests/phpunit/tests/formatting/excerptRemoveBlocks.php +++ b/tests/phpunit/tests/formatting/excerptRemoveBlocks.php @@ -129,4 +129,65 @@ public function test_excerpt_infinite_loop() { $query->the_post(); $this->assertEmpty( do_blocks( '' ) ); } + + /** + * Tests that a top-level block hidden via the visibility block support + * is removed from the excerpt. + * + * @ticket 65456 + */ + public function test_excerpt_remove_blocks_skips_hidden_block() { + $content = ' +

hidden

+ +

visible

'; + + $output = excerpt_remove_blocks( $content ); + + $this->assertStringNotContainsString( 'hidden', $output ); + $this->assertStringContainsString( 'visible', $output ); + } + + /** + * Tests that a hidden wrapper block (group/columns/column) is removed + * from the excerpt, including its inner blocks. + * + * @ticket 65456 + * + * @covers ::_excerpt_render_inner_blocks + */ + public function test_excerpt_remove_blocks_skips_hidden_wrapper_block() { + $content = ' +
+

hidden inside group

+
+ +

visible

'; + + $output = excerpt_remove_blocks( $content ); + + $this->assertStringNotContainsString( 'hidden inside group', $output ); + $this->assertStringContainsString( 'visible', $output ); + } + + /** + * Tests that a hidden block nested inside a visible wrapper is removed. + * + * @ticket 65456 + * + * @covers ::_excerpt_render_inner_blocks + */ + public function test_excerpt_remove_blocks_skips_hidden_inner_block() { + $content = ' +
+

hidden inner

+

visible inner

+
+'; + + $output = excerpt_remove_blocks( $content ); + + $this->assertStringNotContainsString( 'hidden inner', $output ); + $this->assertStringContainsString( 'visible inner', $output ); + } } From 98d9e0a94e95475304924dbb47e3523f7dd629fc Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Sat, 13 Jun 2026 21:37:01 +0900 Subject: [PATCH 2/3] Excerpt: Add test for viewport-only hidden blocks in excerpts. Viewport visibility (e.g. `blockVisibility.viewport.desktop = false`) only toggles the rendered display via CSS and does not fully hide the block. The excerpt strip logic intentionally matches only the strict `blockVisibility === false` case, so viewport-hidden blocks must remain in the excerpt. Add a test to lock in that behavior. Co-Authored-By: Claude --- .../tests/formatting/excerptRemoveBlocks.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/phpunit/tests/formatting/excerptRemoveBlocks.php b/tests/phpunit/tests/formatting/excerptRemoveBlocks.php index 0f64386523687..ae4ea76378f62 100644 --- a/tests/phpunit/tests/formatting/excerptRemoveBlocks.php +++ b/tests/phpunit/tests/formatting/excerptRemoveBlocks.php @@ -190,4 +190,21 @@ public function test_excerpt_remove_blocks_skips_hidden_inner_block() { $this->assertStringNotContainsString( 'hidden inner', $output ); $this->assertStringContainsString( 'visible inner', $output ); } + + /** + * Tests that a block hidden only on a specific viewport is kept in the + * excerpt. Viewport visibility only affects the rendered display via CSS, + * so it must not strip the block's text from the excerpt. + * + * @ticket 65456 + */ + public function test_excerpt_remove_blocks_keeps_viewport_hidden_block() { + $content = ' +

Hello World

+'; + + $output = excerpt_remove_blocks( $content ); + + $this->assertStringContainsString( 'Hello World', $output ); + } } From b24f7d8237642c025237e8d8f06d6642701a5297 Mon Sep 17 00:00:00 2001 From: Aki Hamano Date: Mon, 22 Jun 2026 16:00:40 +0900 Subject: [PATCH 3/3] Excerpt: Clarify why hidden blocks are skipped during excerpt generation. The previous comment implied the check inspected the block's visibility support, but the code only inspects the `blockVisibility` metadata. Reword it to explain that the metadata is honored regardless of the block's current visibility support, so blocks that previously had support enabled are not unintentionally exposed after their support is disabled. Co-Authored-By: Claude --- src/wp-includes/blocks.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index 9a402685450f6..eeb2a9ae5c3e5 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -2238,7 +2238,10 @@ function excerpt_remove_blocks( $content ) { $output = ''; foreach ( $blocks as $block ) { - // Skip blocks hidden via the visibility block support. + // Hide the block whenever the value is boolean false, regardless of the + // block's current visibility support. This prevents blocks that previously + // supported visibility from unintentionally appearing on the front end + // after their support was disabled. if ( false === ( $block['attrs']['metadata']['blockVisibility'] ?? null ) ) { continue; } @@ -2304,7 +2307,10 @@ function _excerpt_render_inner_blocks( $parsed_block, $allowed_blocks ) { $output = ''; foreach ( $parsed_block['innerBlocks'] as $inner_block ) { - // Skip blocks hidden via the visibility block support. + // Hide the block whenever the value is boolean false, regardless of the + // block's current visibility support. This prevents blocks that previously + // supported visibility from unintentionally appearing on the front end + // after their support was disabled. if ( false === ( $inner_block['attrs']['metadata']['blockVisibility'] ?? null ) ) { continue; }