From 8e7719a334747f54e5ebd6cedaebb3d499a33f2a Mon Sep 17 00:00:00 2001 From: Rutvik Savsani Date: Thu, 1 Jan 2026 14:47:42 +0530 Subject: [PATCH 01/34] Insert block template skip link via HTML API, minify CSS, remove JS. --- src/wp-includes/block-template.php | 116 +++++++++++- src/wp-includes/theme-templates.php | 84 +-------- tests/phpunit/tests/block-template-utils.php | 184 +++++++++++++++++++ 3 files changed, 301 insertions(+), 83 deletions(-) diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index eecbe2d61dd61..9898d72e5d5ed 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -301,7 +301,121 @@ function get_the_block_template_html() { // Wrap block template in .wp-site-blocks to allow for specific descendant styles // (e.g. `.wp-site-blocks > *`). - return '
' . $content . '
'; + $template_html = '
' . $content . '
'; + + return _block_template_skip_link_markup( $template_html ); +} + +/** + * Inserts the block template skip link into the template HTML. + * + * Uses the HTML API to ensure that the main content element has an ID and to + * inject the skip-link anchor before the block template wrapper. + * + * @access private + * @since 7.0.0 + * + * @global string $_wp_current_template_content + * + * @param string $template_html Block template markup. + * @return string Modified markup with skip link when applicable. + */ +function _block_template_skip_link_markup( $template_html ) { + global $_wp_current_template_content; + + // Back-compat for plugins that disable functionality by unhooking this action. + if ( ! has_action( 'wp_footer', 'the_block_template_skip_link' ) ) { + return $template_html; + } + + // Early exit if not a block theme. + if ( ! current_theme_supports( 'block-templates' ) ) { + return $template_html; + } + + // Early exit if not a block template. + if ( ! $_wp_current_template_content ) { + return $template_html; + } + + // Ensure a skip-link target exists and has an ID. + $processor = new WP_HTML_Tag_Processor( $template_html ); + $skip_link_target_id = null; + + // Get the first
element. + while ( $processor->next_tag() ) { + if ( 'MAIN' !== $processor->get_tag() || $processor->is_tag_closer() ) { + continue; + } + + $skip_link_target_id = $processor->get_attribute( 'id' ); + if ( ! $skip_link_target_id ) { + $skip_link_target_id = 'wp--skip-link--target'; + $processor->set_attribute( 'id', $skip_link_target_id ); + } + + // Only consider the first
element. + break; + } + + // Early exit if a skip-link target can't be located. + if ( ! $skip_link_target_id ) { + return $template_html; + } + + // Apply any updates from setting the main ID. + $template_html = $processor->get_updated_html(); + + // If a skip link already exists, don't insert another one. + $existing = new WP_HTML_Tag_Processor( $template_html ); + while ( $existing->next_tag() ) { + if ( 'A' === $existing->get_tag() && 'wp-skip-link' === $existing->get_attribute( 'id' ) ) { + return $template_html; + } + } + + // Anonymous subclass of WP_HTML_Tag_Processor which exposes underlying bookmark spans + // so that text can be inserted before the current token. + $inserter = new class( $template_html ) extends WP_HTML_Tag_Processor { + /** + * Gets the span for the current token. + * + * @return WP_HTML_Span Current token span. + */ + private function get_span(): WP_HTML_Span { + // Note: This call will never fail according to the usage of this class, given it is always called after ::next_tag() is true. + $this->set_bookmark( 'here' ); + return $this->bookmarks['here']; + } + + /** + * Inserts text before the current token. + * + * @param string $text Text to insert. + */ + public function insert_before( string $text ) { + $this->lexical_updates[] = new WP_HTML_Text_Replacement( $this->get_span()->start, 0, $text ); + } + }; + + while ( $inserter->next_tag() ) { + if ( $inserter->is_tag_closer() ) { + continue; + } + + if ( 'DIV' === $inserter->get_tag() && $inserter->has_class( 'wp-site-blocks' ) ) { + $skip_link = sprintf( + '', + esc_attr( $skip_link_target_id ), + /* translators: Hidden accessibility text. Do not use HTML entities ( , etc.). */ + esc_html__( 'Skip to content' ) + ); + $inserter->insert_before( $skip_link ); + break; + } + } + + return $inserter->get_updated_html(); } /** diff --git a/src/wp-includes/theme-templates.php b/src/wp-includes/theme-templates.php index eed0fb9b2b029..6ef45a9a0e7b0 100644 --- a/src/wp-includes/theme-templates.php +++ b/src/wp-includes/theme-templates.php @@ -99,7 +99,7 @@ function wp_filter_wp_template_unique_post_slug( $override_slug, $slug, $post_id } /** - * Enqueues the skip-link script & styles. + * Enqueues the skip-link styles. * * @access private * @since 6.4.0 @@ -125,34 +125,7 @@ function wp_enqueue_block_template_skip_link() { return; } - $skip_link_styles = ' - .skip-link.screen-reader-text { - border: 0; - clip-path: inset(50%); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute !important; - width: 1px; - word-wrap: normal !important; - } - - .skip-link.screen-reader-text:focus { - background-color: #eee; - clip-path: none; - color: #444; - display: block; - font-size: 1em; - height: auto; - left: 5px; - line-height: normal; - padding: 15px 23px 14px; - text-decoration: none; - top: 5px; - width: auto; - z-index: 100000; - }'; + $skip_link_styles = '.skip-link.screen-reader-text{border:0;clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute!important;width:1px;word-wrap:normal!important}.skip-link.screen-reader-text:focus{background-color:#eee;clip-path:none;color:#444;display:block;font-size:1em;height:auto;left:5px;line-height:normal;padding:15px 23px 14px;text-decoration:none;top:5px;width:auto;z-index:100000}'; $handle = 'wp-block-template-skip-link'; @@ -162,59 +135,6 @@ function wp_enqueue_block_template_skip_link() { wp_register_style( $handle, false ); wp_add_inline_style( $handle, $skip_link_styles ); wp_enqueue_style( $handle ); - - /** - * Enqueue the skip-link script. - */ - ob_start(); - ?> - - true ) ); - wp_add_inline_script( $script_handle, $skip_link_script ); - wp_enqueue_script( $script_handle ); } /** diff --git a/tests/phpunit/tests/block-template-utils.php b/tests/phpunit/tests/block-template-utils.php index e5255ba5ae011..94f7c2a3572cf 100644 --- a/tests/phpunit/tests/block-template-utils.php +++ b/tests/phpunit/tests/block-template-utils.php @@ -297,6 +297,190 @@ public function data_remove_theme_attribute_in_block_template_content() { ); } + /** + * Tests that the skip link is added and a missing main ID is created. + * + * @ticket 64361 + * + * @covers ::_block_template_skip_link_markup + */ + public function test_block_template_skip_link_inserts_link_and_adds_main_id_when_missing() { + global $_wp_current_template_content; + + $previous_template_content = null; + if ( isset( $_wp_current_template_content ) ) { + $previous_template_content = $_wp_current_template_content; + } + + $_wp_current_template_content = 'Template content.'; + + $has_existing_hook = has_action( 'wp_footer', 'the_block_template_skip_link' ); + $had_block_templates_support = current_theme_supports( 'block-templates' ); + if ( ! $has_existing_hook ) { + add_action( 'wp_footer', 'the_block_template_skip_link' ); + } + if ( ! $had_block_templates_support ) { + add_theme_support( 'block-templates' ); + } + + $template_html = '
Content
'; + $result = _block_template_skip_link_markup( $template_html ); + + if ( ! $has_existing_hook ) { + remove_action( 'wp_footer', 'the_block_template_skip_link' ); + } + if ( ! $had_block_templates_support ) { + remove_theme_support( 'block-templates' ); + } + + if ( null === $previous_template_content ) { + unset( $_wp_current_template_content ); + } else { + $_wp_current_template_content = $previous_template_content; + } + + $this->assertNotSame( $template_html, $result, 'Skip link markup was not added.' ); + $this->assertStringContainsString( 'id="wp--skip-link--target"', $result, 'Main element ID was not added.' ); + $this->assertStringContainsString( 'href="#wp--skip-link--target"', $result, 'Skip link does not point to the expected target.' ); + } + + /** + * Tests that an existing main ID is preserved and used by the skip link. + * + * @ticket 64361 + * + * @covers ::_block_template_skip_link_markup + */ + public function test_block_template_skip_link_uses_existing_main_id() { + global $_wp_current_template_content; + + $previous_template_content = null; + if ( isset( $_wp_current_template_content ) ) { + $previous_template_content = $_wp_current_template_content; + } + + $_wp_current_template_content = 'Template content.'; + + $has_existing_hook = has_action( 'wp_footer', 'the_block_template_skip_link' ); + $had_block_templates_support = current_theme_supports( 'block-templates' ); + if ( ! $has_existing_hook ) { + add_action( 'wp_footer', 'the_block_template_skip_link' ); + } + if ( ! $had_block_templates_support ) { + add_theme_support( 'block-templates' ); + } + + $template_html = '
Content
'; + $result = _block_template_skip_link_markup( $template_html ); + + if ( ! $has_existing_hook ) { + remove_action( 'wp_footer', 'the_block_template_skip_link' ); + } + if ( ! $had_block_templates_support ) { + remove_theme_support( 'block-templates' ); + } + + if ( null === $previous_template_content ) { + unset( $_wp_current_template_content ); + } else { + $_wp_current_template_content = $previous_template_content; + } + + $this->assertStringContainsString( 'id="custom-id"', $result, 'Existing main element ID was not preserved.' ); + $this->assertStringContainsString( 'href="#custom-id"', $result, 'Skip link does not point to the existing main element ID.' ); + $this->assertStringNotContainsString( 'wp--skip-link--target', $result, 'Unexpected default skip link target ID was added.' ); + } + + /** + * Tests that no skip link is added when there is no main element. + * + * @ticket 64361 + * + * @covers ::_block_template_skip_link_markup + */ + public function test_block_template_skip_link_not_inserted_when_main_missing() { + global $_wp_current_template_content; + + $previous_template_content = null; + if ( isset( $_wp_current_template_content ) ) { + $previous_template_content = $_wp_current_template_content; + } + + $_wp_current_template_content = 'Template content.'; + + $has_existing_hook = has_action( 'wp_footer', 'the_block_template_skip_link' ); + $had_block_templates_support = current_theme_supports( 'block-templates' ); + if ( ! $has_existing_hook ) { + add_action( 'wp_footer', 'the_block_template_skip_link' ); + } + if ( ! $had_block_templates_support ) { + add_theme_support( 'block-templates' ); + } + + $template_html = '
Content
'; + $result = _block_template_skip_link_markup( $template_html ); + + if ( ! $has_existing_hook ) { + remove_action( 'wp_footer', 'the_block_template_skip_link' ); + } + if ( ! $had_block_templates_support ) { + remove_theme_support( 'block-templates' ); + } + + if ( null === $previous_template_content ) { + unset( $_wp_current_template_content ); + } else { + $_wp_current_template_content = $previous_template_content; + } + + $this->assertSame( $template_html, $result, 'Skip link markup should not be added when there is no main element.' ); + } + + /** + * Tests that an existing skip link anchor is not duplicated. + * + * @ticket 64361 + * + * @covers ::_block_template_skip_link_markup + */ + public function test_block_template_skip_link_not_duplicated_when_existing_anchor_present() { + global $_wp_current_template_content; + + $previous_template_content = null; + if ( isset( $_wp_current_template_content ) ) { + $previous_template_content = $_wp_current_template_content; + } + + $_wp_current_template_content = 'Template content.'; + + $has_existing_hook = has_action( 'wp_footer', 'the_block_template_skip_link' ); + $had_block_templates_support = current_theme_supports( 'block-templates' ); + if ( ! $has_existing_hook ) { + add_action( 'wp_footer', 'the_block_template_skip_link' ); + } + if ( ! $had_block_templates_support ) { + add_theme_support( 'block-templates' ); + } + + $template_html = '
Content
'; + $result = _block_template_skip_link_markup( $template_html ); + + if ( ! $has_existing_hook ) { + remove_action( 'wp_footer', 'the_block_template_skip_link' ); + } + if ( ! $had_block_templates_support ) { + remove_theme_support( 'block-templates' ); + } + + if ( null === $previous_template_content ) { + unset( $_wp_current_template_content ); + } else { + $_wp_current_template_content = $previous_template_content; + } + + $this->assertSame( $template_html, $result, 'Existing skip link anchor should be preserved and not duplicated.' ); + } + /** * Should retrieve the template from the theme files. */ From 7e29956ca6214db50c643862cc4283416c47f0e6 Mon Sep 17 00:00:00 2001 From: Rutvik Savsani <53530700+rutviksavsani@users.noreply.github.com> Date: Sat, 3 Jan 2026 12:54:02 +0530 Subject: [PATCH 02/34] remove extra checks for block template. Co-authored-by: Weston Ruter --- src/wp-includes/block-template.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 9898d72e5d5ed..78c73d0924cb6 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -328,16 +328,6 @@ function _block_template_skip_link_markup( $template_html ) { return $template_html; } - // Early exit if not a block theme. - if ( ! current_theme_supports( 'block-templates' ) ) { - return $template_html; - } - - // Early exit if not a block template. - if ( ! $_wp_current_template_content ) { - return $template_html; - } - // Ensure a skip-link target exists and has an ID. $processor = new WP_HTML_Tag_Processor( $template_html ); $skip_link_target_id = null; From 908f6a9a7bffe853d7fb277b1725ec1c3fa8782c Mon Sep 17 00:00:00 2001 From: Rutvik Savsani <53530700+rutviksavsani@users.noreply.github.com> Date: Sat, 3 Jan 2026 12:55:48 +0530 Subject: [PATCH 03/34] Add type and return type to the function. Co-authored-by: Weston Ruter --- src/wp-includes/block-template.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 78c73d0924cb6..0026049064684 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -320,7 +320,7 @@ function get_the_block_template_html() { * @param string $template_html Block template markup. * @return string Modified markup with skip link when applicable. */ -function _block_template_skip_link_markup( $template_html ) { +function _block_template_skip_link_markup( string $template_html ): string { global $_wp_current_template_content; // Back-compat for plugins that disable functionality by unhooking this action. From d6278d3123cddfd8fc6a0b3fcbd867855393ad4d Mon Sep 17 00:00:00 2001 From: Rutvik Savsani <53530700+rutviksavsani@users.noreply.github.com> Date: Sat, 3 Jan 2026 12:56:12 +0530 Subject: [PATCH 04/34] Update translators comment. Co-authored-by: Weston Ruter --- src/wp-includes/block-template.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 0026049064684..d2f731196be91 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -397,7 +397,7 @@ public function insert_before( string $text ) { $skip_link = sprintf( '', esc_attr( $skip_link_target_id ), - /* translators: Hidden accessibility text. Do not use HTML entities ( , etc.). */ + /* translators: Hidden accessibility text. */ esc_html__( 'Skip to content' ) ); $inserter->insert_before( $skip_link ); From 9a73e67358990f52f49c8af257c23b7e1c24e3f5 Mon Sep 17 00:00:00 2001 From: Rutvik Savsani <53530700+rutviksavsani@users.noreply.github.com> Date: Sat, 3 Jan 2026 12:57:02 +0530 Subject: [PATCH 05/34] Add extra checks for get_attribute link target. Co-authored-by: Weston Ruter --- src/wp-includes/block-template.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index d2f731196be91..76a9dec243544 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -339,7 +339,7 @@ function _block_template_skip_link_markup( string $template_html ): string { } $skip_link_target_id = $processor->get_attribute( 'id' ); - if ( ! $skip_link_target_id ) { + if ( ! is_string( $skip_link_target_id ) || '' === trim( $skip_link_target_id ) ) { $skip_link_target_id = 'wp--skip-link--target'; $processor->set_attribute( 'id', $skip_link_target_id ); } From a62dffbb50463787c24c31fd1918d69ec17a7496 Mon Sep 17 00:00:00 2001 From: Rutvik Savsani <53530700+rutviksavsani@users.noreply.github.com> Date: Sat, 3 Jan 2026 12:57:25 +0530 Subject: [PATCH 06/34] update function doc comment. Co-authored-by: Weston Ruter --- src/wp-includes/block-template.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 76a9dec243544..2579ca3e0cd8a 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -315,13 +315,11 @@ function get_the_block_template_html() { * @access private * @since 7.0.0 * - * @global string $_wp_current_template_content * * @param string $template_html Block template markup. * @return string Modified markup with skip link when applicable. */ -function _block_template_skip_link_markup( string $template_html ): string { - global $_wp_current_template_content; +function _block_template_skip_link_markup( $template_html ) { // Back-compat for plugins that disable functionality by unhooking this action. if ( ! has_action( 'wp_footer', 'the_block_template_skip_link' ) ) { From 43c4b01f251d710c264664f7433e51811293617b Mon Sep 17 00:00:00 2001 From: Rutvik Savsani Date: Sat, 3 Jan 2026 13:47:31 +0530 Subject: [PATCH 07/34] Remove the duplication check and tag closers check. --- src/wp-includes/block-template.php | 17 +------- tests/phpunit/tests/block-template-utils.php | 45 -------------------- 2 files changed, 2 insertions(+), 60 deletions(-) diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 2579ca3e0cd8a..2150256f61f77 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -315,11 +315,10 @@ function get_the_block_template_html() { * @access private * @since 7.0.0 * - * * @param string $template_html Block template markup. * @return string Modified markup with skip link when applicable. */ -function _block_template_skip_link_markup( $template_html ) { +function _block_template_skip_link_markup( string $template_html ): string { // Back-compat for plugins that disable functionality by unhooking this action. if ( ! has_action( 'wp_footer', 'the_block_template_skip_link' ) ) { @@ -332,7 +331,7 @@ function _block_template_skip_link_markup( $template_html ) { // Get the first
element. while ( $processor->next_tag() ) { - if ( 'MAIN' !== $processor->get_tag() || $processor->is_tag_closer() ) { + if ( 'MAIN' !== $processor->get_tag() ) { continue; } @@ -354,14 +353,6 @@ function _block_template_skip_link_markup( $template_html ) { // Apply any updates from setting the main ID. $template_html = $processor->get_updated_html(); - // If a skip link already exists, don't insert another one. - $existing = new WP_HTML_Tag_Processor( $template_html ); - while ( $existing->next_tag() ) { - if ( 'A' === $existing->get_tag() && 'wp-skip-link' === $existing->get_attribute( 'id' ) ) { - return $template_html; - } - } - // Anonymous subclass of WP_HTML_Tag_Processor which exposes underlying bookmark spans // so that text can be inserted before the current token. $inserter = new class( $template_html ) extends WP_HTML_Tag_Processor { @@ -387,10 +378,6 @@ public function insert_before( string $text ) { }; while ( $inserter->next_tag() ) { - if ( $inserter->is_tag_closer() ) { - continue; - } - if ( 'DIV' === $inserter->get_tag() && $inserter->has_class( 'wp-site-blocks' ) ) { $skip_link = sprintf( '', diff --git a/tests/phpunit/tests/block-template-utils.php b/tests/phpunit/tests/block-template-utils.php index 94f7c2a3572cf..a6323edd90f16 100644 --- a/tests/phpunit/tests/block-template-utils.php +++ b/tests/phpunit/tests/block-template-utils.php @@ -436,51 +436,6 @@ public function test_block_template_skip_link_not_inserted_when_main_missing() { $this->assertSame( $template_html, $result, 'Skip link markup should not be added when there is no main element.' ); } - /** - * Tests that an existing skip link anchor is not duplicated. - * - * @ticket 64361 - * - * @covers ::_block_template_skip_link_markup - */ - public function test_block_template_skip_link_not_duplicated_when_existing_anchor_present() { - global $_wp_current_template_content; - - $previous_template_content = null; - if ( isset( $_wp_current_template_content ) ) { - $previous_template_content = $_wp_current_template_content; - } - - $_wp_current_template_content = 'Template content.'; - - $has_existing_hook = has_action( 'wp_footer', 'the_block_template_skip_link' ); - $had_block_templates_support = current_theme_supports( 'block-templates' ); - if ( ! $has_existing_hook ) { - add_action( 'wp_footer', 'the_block_template_skip_link' ); - } - if ( ! $had_block_templates_support ) { - add_theme_support( 'block-templates' ); - } - - $template_html = '
Content
'; - $result = _block_template_skip_link_markup( $template_html ); - - if ( ! $has_existing_hook ) { - remove_action( 'wp_footer', 'the_block_template_skip_link' ); - } - if ( ! $had_block_templates_support ) { - remove_theme_support( 'block-templates' ); - } - - if ( null === $previous_template_content ) { - unset( $_wp_current_template_content ); - } else { - $_wp_current_template_content = $previous_template_content; - } - - $this->assertSame( $template_html, $result, 'Existing skip link anchor should be preserved and not duplicated.' ); - } - /** * Should retrieve the template from the theme files. */ From ce8885ab68bb542226d740526073f124c15fe965 Mon Sep 17 00:00:00 2001 From: Rutvik Savsani Date: Sat, 3 Jan 2026 16:10:23 +0530 Subject: [PATCH 08/34] css concat but make it still readable. --- src/wp-includes/theme-templates.php | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/theme-templates.php b/src/wp-includes/theme-templates.php index 6ef45a9a0e7b0..4827a14275d59 100644 --- a/src/wp-includes/theme-templates.php +++ b/src/wp-includes/theme-templates.php @@ -125,7 +125,33 @@ function wp_enqueue_block_template_skip_link() { return; } - $skip_link_styles = '.skip-link.screen-reader-text{border:0;clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute!important;width:1px;word-wrap:normal!important}.skip-link.screen-reader-text:focus{background-color:#eee;clip-path:none;color:#444;display:block;font-size:1em;height:auto;left:5px;line-height:normal;padding:15px 23px 14px;text-decoration:none;top:5px;width:auto;z-index:100000}'; + $skip_link_styles = '.skip-link.screen-reader-text {' + . 'border:0;' + . 'clip-path:inset(50%);' + . 'height:1px;' + . 'margin:-1px;' + . 'overflow:hidden;' + . 'padding:0;' + . 'position:absolute!important;' + . 'width:1px;' + . 'word-wrap:normal!important;' + . '}'; + + $skip_link_styles .= '.skip-link.screen-reader-text:focus {' + . 'background-color:#eee;' + . 'clip-path:none;' + . 'color:#444;' + . 'display:block;' + . 'font-size:1em;' + . 'height:auto;' + . 'left:5px;' + . 'line-height:normal;' + . 'padding:15px 23px 14px;' + . 'text-decoration:none;' + . 'top:5px;' + . 'width:auto;' + . 'z-index:100000;' + . '}'; $handle = 'wp-block-template-skip-link'; From cd9e02921a095a3f38990f7adf76156cdb1b370c Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sun, 4 Jan 2026 22:23:49 -0800 Subject: [PATCH 09/34] Remove single-use private method --- src/wp-includes/block-template.php | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 2150256f61f77..0047165ca432d 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -356,24 +356,14 @@ function _block_template_skip_link_markup( string $template_html ): string { // Anonymous subclass of WP_HTML_Tag_Processor which exposes underlying bookmark spans // so that text can be inserted before the current token. $inserter = new class( $template_html ) extends WP_HTML_Tag_Processor { - /** - * Gets the span for the current token. - * - * @return WP_HTML_Span Current token span. - */ - private function get_span(): WP_HTML_Span { - // Note: This call will never fail according to the usage of this class, given it is always called after ::next_tag() is true. - $this->set_bookmark( 'here' ); - return $this->bookmarks['here']; - } - /** * Inserts text before the current token. * * @param string $text Text to insert. */ public function insert_before( string $text ) { - $this->lexical_updates[] = new WP_HTML_Text_Replacement( $this->get_span()->start, 0, $text ); + $this->set_bookmark( 'here' ); + $this->lexical_updates[] = new WP_HTML_Text_Replacement( $this->bookmarks['here']->start, 0, $text ); } }; From a194ca830c5ed621dcefdc9f25ce8ca7e6b86363 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sun, 4 Jan 2026 22:25:48 -0800 Subject: [PATCH 10/34] Add since tag to wp_enqueue_block_template_skip_link() --- src/wp-includes/theme-templates.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wp-includes/theme-templates.php b/src/wp-includes/theme-templates.php index 4827a14275d59..42c9b2daabd69 100644 --- a/src/wp-includes/theme-templates.php +++ b/src/wp-includes/theme-templates.php @@ -103,6 +103,7 @@ function wp_filter_wp_template_unique_post_slug( $override_slug, $slug, $post_id * * @access private * @since 6.4.0 + * @since 7.0.0 A script is no longer printed in favor of being added via {@see _block_template_skip_link_markup()}. * * @global string $_wp_current_template_content */ From 6b7841c4059a2ac8e0b262164d5459dfd0f3e04c Mon Sep 17 00:00:00 2001 From: Rutvik Savsani Date: Mon, 5 Jan 2026 14:05:43 +0530 Subject: [PATCH 11/34] address feedback. --- .../css/wp-block-template-skip-link.css | 27 ++++++ src/wp-includes/script-loader.php | 1 + src/wp-includes/theme-templates.php | 34 ------- tests/phpunit/tests/block-template-utils.php | 93 ------------------- 4 files changed, 28 insertions(+), 127 deletions(-) create mode 100644 src/wp-includes/css/wp-block-template-skip-link.css diff --git a/src/wp-includes/css/wp-block-template-skip-link.css b/src/wp-includes/css/wp-block-template-skip-link.css new file mode 100644 index 0000000000000..4176599ad0667 --- /dev/null +++ b/src/wp-includes/css/wp-block-template-skip-link.css @@ -0,0 +1,27 @@ +.skip-link.screen-reader-text { + border: 0; + clip-path: inset(50%); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute !important; + width: 1px; + word-wrap: normal !important; +} + +.skip-link.screen-reader-text:focus { + background-color: #eee; + clip-path: none; + color: #444; + display: block; + font-size: 1em; + height: auto; + left: 5px; + line-height: normal; + padding: 15px 23px 14px; + text-decoration: none; + top: 5px; + width: auto; + z-index: 100000; +} diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index c0108dc848276..62979f38d8200 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -1648,6 +1648,7 @@ function wp_default_styles( $styles ) { $styles->add( 'wp-pointer', "/wp-includes/css/wp-pointer$suffix.css", array( 'dashicons' ) ); $styles->add( 'customize-preview', "/wp-includes/css/customize-preview$suffix.css", array( 'dashicons' ) ); $styles->add( 'wp-empty-template-alert', "/wp-includes/css/wp-empty-template-alert$suffix.css" ); + $styles->add( 'wp-block-template-skip-link', "/wp-includes/css/wp-block-template-skip-link$suffix.css" ); // External libraries and friends. $styles->add( 'imgareaselect', '/wp-includes/js/imgareaselect/imgareaselect.css', array(), '0.9.8' ); diff --git a/src/wp-includes/theme-templates.php b/src/wp-includes/theme-templates.php index 42c9b2daabd69..f8b4c7c1bdbda 100644 --- a/src/wp-includes/theme-templates.php +++ b/src/wp-includes/theme-templates.php @@ -126,41 +126,7 @@ function wp_enqueue_block_template_skip_link() { return; } - $skip_link_styles = '.skip-link.screen-reader-text {' - . 'border:0;' - . 'clip-path:inset(50%);' - . 'height:1px;' - . 'margin:-1px;' - . 'overflow:hidden;' - . 'padding:0;' - . 'position:absolute!important;' - . 'width:1px;' - . 'word-wrap:normal!important;' - . '}'; - - $skip_link_styles .= '.skip-link.screen-reader-text:focus {' - . 'background-color:#eee;' - . 'clip-path:none;' - . 'color:#444;' - . 'display:block;' - . 'font-size:1em;' - . 'height:auto;' - . 'left:5px;' - . 'line-height:normal;' - . 'padding:15px 23px 14px;' - . 'text-decoration:none;' - . 'top:5px;' - . 'width:auto;' - . 'z-index:100000;' - . '}'; - $handle = 'wp-block-template-skip-link'; - - /** - * Print the skip-link styles. - */ - wp_register_style( $handle, false ); - wp_add_inline_style( $handle, $skip_link_styles ); wp_enqueue_style( $handle ); } diff --git a/tests/phpunit/tests/block-template-utils.php b/tests/phpunit/tests/block-template-utils.php index a6323edd90f16..3b63e8f24435c 100644 --- a/tests/phpunit/tests/block-template-utils.php +++ b/tests/phpunit/tests/block-template-utils.php @@ -305,40 +305,9 @@ public function data_remove_theme_attribute_in_block_template_content() { * @covers ::_block_template_skip_link_markup */ public function test_block_template_skip_link_inserts_link_and_adds_main_id_when_missing() { - global $_wp_current_template_content; - - $previous_template_content = null; - if ( isset( $_wp_current_template_content ) ) { - $previous_template_content = $_wp_current_template_content; - } - - $_wp_current_template_content = 'Template content.'; - - $has_existing_hook = has_action( 'wp_footer', 'the_block_template_skip_link' ); - $had_block_templates_support = current_theme_supports( 'block-templates' ); - if ( ! $has_existing_hook ) { - add_action( 'wp_footer', 'the_block_template_skip_link' ); - } - if ( ! $had_block_templates_support ) { - add_theme_support( 'block-templates' ); - } - $template_html = '
Content
'; $result = _block_template_skip_link_markup( $template_html ); - if ( ! $has_existing_hook ) { - remove_action( 'wp_footer', 'the_block_template_skip_link' ); - } - if ( ! $had_block_templates_support ) { - remove_theme_support( 'block-templates' ); - } - - if ( null === $previous_template_content ) { - unset( $_wp_current_template_content ); - } else { - $_wp_current_template_content = $previous_template_content; - } - $this->assertNotSame( $template_html, $result, 'Skip link markup was not added.' ); $this->assertStringContainsString( 'id="wp--skip-link--target"', $result, 'Main element ID was not added.' ); $this->assertStringContainsString( 'href="#wp--skip-link--target"', $result, 'Skip link does not point to the expected target.' ); @@ -352,40 +321,9 @@ public function test_block_template_skip_link_inserts_link_and_adds_main_id_when * @covers ::_block_template_skip_link_markup */ public function test_block_template_skip_link_uses_existing_main_id() { - global $_wp_current_template_content; - - $previous_template_content = null; - if ( isset( $_wp_current_template_content ) ) { - $previous_template_content = $_wp_current_template_content; - } - - $_wp_current_template_content = 'Template content.'; - - $has_existing_hook = has_action( 'wp_footer', 'the_block_template_skip_link' ); - $had_block_templates_support = current_theme_supports( 'block-templates' ); - if ( ! $has_existing_hook ) { - add_action( 'wp_footer', 'the_block_template_skip_link' ); - } - if ( ! $had_block_templates_support ) { - add_theme_support( 'block-templates' ); - } - $template_html = '
Content
'; $result = _block_template_skip_link_markup( $template_html ); - if ( ! $has_existing_hook ) { - remove_action( 'wp_footer', 'the_block_template_skip_link' ); - } - if ( ! $had_block_templates_support ) { - remove_theme_support( 'block-templates' ); - } - - if ( null === $previous_template_content ) { - unset( $_wp_current_template_content ); - } else { - $_wp_current_template_content = $previous_template_content; - } - $this->assertStringContainsString( 'id="custom-id"', $result, 'Existing main element ID was not preserved.' ); $this->assertStringContainsString( 'href="#custom-id"', $result, 'Skip link does not point to the existing main element ID.' ); $this->assertStringNotContainsString( 'wp--skip-link--target', $result, 'Unexpected default skip link target ID was added.' ); @@ -399,40 +337,9 @@ public function test_block_template_skip_link_uses_existing_main_id() { * @covers ::_block_template_skip_link_markup */ public function test_block_template_skip_link_not_inserted_when_main_missing() { - global $_wp_current_template_content; - - $previous_template_content = null; - if ( isset( $_wp_current_template_content ) ) { - $previous_template_content = $_wp_current_template_content; - } - - $_wp_current_template_content = 'Template content.'; - - $has_existing_hook = has_action( 'wp_footer', 'the_block_template_skip_link' ); - $had_block_templates_support = current_theme_supports( 'block-templates' ); - if ( ! $has_existing_hook ) { - add_action( 'wp_footer', 'the_block_template_skip_link' ); - } - if ( ! $had_block_templates_support ) { - add_theme_support( 'block-templates' ); - } - $template_html = '
Content
'; $result = _block_template_skip_link_markup( $template_html ); - if ( ! $has_existing_hook ) { - remove_action( 'wp_footer', 'the_block_template_skip_link' ); - } - if ( ! $had_block_templates_support ) { - remove_theme_support( 'block-templates' ); - } - - if ( null === $previous_template_content ) { - unset( $_wp_current_template_content ); - } else { - $_wp_current_template_content = $previous_template_content; - } - $this->assertSame( $template_html, $result, 'Skip link markup should not be added when there is no main element.' ); } From 0bb3ec4df85c6cdf52e44e198766a763c7497464 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 5 Jan 2026 09:36:59 -0800 Subject: [PATCH 12/34] Add missing path data for stylesheet --- src/wp-includes/script-loader.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 62979f38d8200..903d42fb9fabb 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -1648,7 +1648,9 @@ function wp_default_styles( $styles ) { $styles->add( 'wp-pointer', "/wp-includes/css/wp-pointer$suffix.css", array( 'dashicons' ) ); $styles->add( 'customize-preview', "/wp-includes/css/customize-preview$suffix.css", array( 'dashicons' ) ); $styles->add( 'wp-empty-template-alert', "/wp-includes/css/wp-empty-template-alert$suffix.css" ); - $styles->add( 'wp-block-template-skip-link', "/wp-includes/css/wp-block-template-skip-link$suffix.css" ); + $skip_link_style_path = WPINC . "/css/wp-block-template-skip-link$suffix.css"; + $styles->add( 'wp-block-template-skip-link', $skip_link_style_path ); + $styles->add_data( 'wp-block-template-skip-link', 'path', ABSPATH . $skip_link_style_path ); // External libraries and friends. $styles->add( 'imgareaselect', '/wp-includes/js/imgareaselect/imgareaselect.css', array(), '0.9.8' ); From c340b2cbd8e95963104d9537bfb636b745980fac Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 5 Jan 2026 10:00:50 -0800 Subject: [PATCH 13/34] Leverage assertEqualHTML --- tests/phpunit/tests/block-template-utils.php | 37 +++++++++++++------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/tests/phpunit/tests/block-template-utils.php b/tests/phpunit/tests/block-template-utils.php index 3b63e8f24435c..0ec9615e370f2 100644 --- a/tests/phpunit/tests/block-template-utils.php +++ b/tests/phpunit/tests/block-template-utils.php @@ -306,11 +306,15 @@ public function data_remove_theme_attribute_in_block_template_content() { */ public function test_block_template_skip_link_inserts_link_and_adds_main_id_when_missing() { $template_html = '
Content
'; - $result = _block_template_skip_link_markup( $template_html ); - - $this->assertNotSame( $template_html, $result, 'Skip link markup was not added.' ); - $this->assertStringContainsString( 'id="wp--skip-link--target"', $result, 'Main element ID was not added.' ); - $this->assertStringContainsString( 'href="#wp--skip-link--target"', $result, 'Skip link does not point to the expected target.' ); + $this->assertEqualHTML( + ' + +
Content
+ ', + _block_template_skip_link_markup( $template_html ), + '', + 'Expected skip link to be added with reusing the ID on
.' + ); } /** @@ -322,11 +326,15 @@ public function test_block_template_skip_link_inserts_link_and_adds_main_id_when */ public function test_block_template_skip_link_uses_existing_main_id() { $template_html = '
Content
'; - $result = _block_template_skip_link_markup( $template_html ); - - $this->assertStringContainsString( 'id="custom-id"', $result, 'Existing main element ID was not preserved.' ); - $this->assertStringContainsString( 'href="#custom-id"', $result, 'Skip link does not point to the existing main element ID.' ); - $this->assertStringNotContainsString( 'wp--skip-link--target', $result, 'Unexpected default skip link target ID was added.' ); + $this->assertEqualHTML( + ' + +
Content
+ ', + _block_template_skip_link_markup( $template_html ), + '', + 'Expected original ID on
to be re-used.' + ); } /** @@ -338,9 +346,12 @@ public function test_block_template_skip_link_uses_existing_main_id() { */ public function test_block_template_skip_link_not_inserted_when_main_missing() { $template_html = '
Content
'; - $result = _block_template_skip_link_markup( $template_html ); - - $this->assertSame( $template_html, $result, 'Skip link markup should not be added when there is no main element.' ); + $this->assertEqualHTML( + $template_html, + _block_template_skip_link_markup( $template_html ), + '', + 'Skip link markup should not be added when there is no main element.' + ); } /** From eff44893e5ed604ab33a5517c9c9fd2bccea8180 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 5 Jan 2026 10:03:12 -0800 Subject: [PATCH 14/34] Add test case for removal of the_block_template_skip_link from wp_footer --- tests/phpunit/tests/block-template-utils.php | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/phpunit/tests/block-template-utils.php b/tests/phpunit/tests/block-template-utils.php index 0ec9615e370f2..9d5524022b412 100644 --- a/tests/phpunit/tests/block-template-utils.php +++ b/tests/phpunit/tests/block-template-utils.php @@ -338,13 +338,31 @@ public function test_block_template_skip_link_uses_existing_main_id() { } /** - * Tests that no skip link is added when there is no main element. + * Tests that no skip link is added when the old action was removed. * * @ticket 64361 * * @covers ::_block_template_skip_link_markup */ public function test_block_template_skip_link_not_inserted_when_main_missing() { + $template_html = '
Content
'; + remove_action( 'wp_footer', 'the_block_template_skip_link' ); + $this->assertEqualHTML( + $template_html, + _block_template_skip_link_markup( $template_html ), + '', + 'Skip link markup should not be added when the_block_template_skip_link is removed from the wp_footer action.' + ); + } + + /** + * Tests that no skip link is added when there is no main element. + * + * @ticket 64361 + * + * @covers ::_block_template_skip_link_markup + */ + public function test_block_template_skip_link_when_action_removed() { $template_html = '
Content
'; $this->assertEqualHTML( $template_html, From b00b244b5cb5670a4c24a755c4e082e51637f98e Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 5 Jan 2026 10:06:44 -0800 Subject: [PATCH 15/34] Tweak comment --- src/wp-includes/block-template.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 0047165ca432d..9825b40d346ea 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -353,8 +353,7 @@ function _block_template_skip_link_markup( string $template_html ): string { // Apply any updates from setting the main ID. $template_html = $processor->get_updated_html(); - // Anonymous subclass of WP_HTML_Tag_Processor which exposes underlying bookmark spans - // so that text can be inserted before the current token. + // Anonymous subclass of WP_HTML_Tag_Processor to access protected bookmark spans. $inserter = new class( $template_html ) extends WP_HTML_Tag_Processor { /** * Inserts text before the current token. From d179f23ea96b778ab9f27e3f80de8f0dc17cdd18 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 5 Jan 2026 10:09:45 -0800 Subject: [PATCH 16/34] Fix swapped test names --- tests/phpunit/tests/block-template-utils.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/tests/block-template-utils.php b/tests/phpunit/tests/block-template-utils.php index 9d5524022b412..afd8c055e520c 100644 --- a/tests/phpunit/tests/block-template-utils.php +++ b/tests/phpunit/tests/block-template-utils.php @@ -344,7 +344,7 @@ public function test_block_template_skip_link_uses_existing_main_id() { * * @covers ::_block_template_skip_link_markup */ - public function test_block_template_skip_link_not_inserted_when_main_missing() { + public function test_block_template_skip_link_when_action_removed() { $template_html = '
Content
'; remove_action( 'wp_footer', 'the_block_template_skip_link' ); $this->assertEqualHTML( @@ -362,7 +362,7 @@ public function test_block_template_skip_link_not_inserted_when_main_missing() { * * @covers ::_block_template_skip_link_markup */ - public function test_block_template_skip_link_when_action_removed() { + public function test_block_template_skip_link_not_inserted_when_main_missing() { $template_html = '
Content
'; $this->assertEqualHTML( $template_html, From 540b526a982567060f5fb908043e331934053326 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 5 Jan 2026 10:16:37 -0800 Subject: [PATCH 17/34] Replace while loop with if statement --- src/wp-includes/block-template.php | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 928959a5cb260..986c8b7eb6efd 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -330,23 +330,16 @@ function _block_template_skip_link_markup( string $template_html ): string { $skip_link_target_id = null; // Get the first
element. - while ( $processor->next_tag() ) { - if ( 'MAIN' !== $processor->get_tag() ) { - continue; - } - + if ( $processor->next_tag( 'MAIN' ) ) { $skip_link_target_id = $processor->get_attribute( 'id' ); if ( ! is_string( $skip_link_target_id ) || '' === trim( $skip_link_target_id ) ) { $skip_link_target_id = 'wp--skip-link--target'; $processor->set_attribute( 'id', $skip_link_target_id ); } - - // Only consider the first
element. - break; } // Early exit if a skip-link target can't be located. - if ( ! $skip_link_target_id ) { + if ( null === $skip_link_target_id ) { return $template_html; } From 746e686e39005584060ef42b7fadbf7a3ffb5982 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 5 Jan 2026 10:25:16 -0800 Subject: [PATCH 18/34] Use data provider --- tests/phpunit/tests/block-template-utils.php | 99 ++++++++------------ 1 file changed, 39 insertions(+), 60 deletions(-) diff --git a/tests/phpunit/tests/block-template-utils.php b/tests/phpunit/tests/block-template-utils.php index afd8c055e520c..7773a6949b8eb 100644 --- a/tests/phpunit/tests/block-template-utils.php +++ b/tests/phpunit/tests/block-template-utils.php @@ -298,77 +298,56 @@ public function data_remove_theme_attribute_in_block_template_content() { } /** - * Tests that the skip link is added and a missing main ID is created. + * Tests adding the skip link (or not). * * @ticket 64361 * * @covers ::_block_template_skip_link_markup - */ - public function test_block_template_skip_link_inserts_link_and_adds_main_id_when_missing() { - $template_html = '
Content
'; - $this->assertEqualHTML( - ' - -
Content
- ', - _block_template_skip_link_markup( $template_html ), - '', - 'Expected skip link to be added with reusing the ID on
.' - ); - } - - /** - * Tests that an existing main ID is preserved and used by the skip link. - * - * @ticket 64361 * - * @covers ::_block_template_skip_link_markup + * @dataProvider data_provider_to_test_block_template_skip_link_markup */ - public function test_block_template_skip_link_uses_existing_main_id() { - $template_html = '
Content
'; - $this->assertEqualHTML( - ' - -
Content
- ', - _block_template_skip_link_markup( $template_html ), - '', - 'Expected original ID on
to be re-used.' - ); - } - - /** - * Tests that no skip link is added when the old action was removed. - * - * @ticket 64361 - * - * @covers ::_block_template_skip_link_markup - */ - public function test_block_template_skip_link_when_action_removed() { - $template_html = '
Content
'; - remove_action( 'wp_footer', 'the_block_template_skip_link' ); - $this->assertEqualHTML( - $template_html, - _block_template_skip_link_markup( $template_html ), - '', - 'Skip link markup should not be added when the_block_template_skip_link is removed from the wp_footer action.' - ); + public function test_block_template_skip_link_markup( ?Closure $set_up, string $template_html, string $expected ) { + if ( $set_up instanceof Closure ) { + $set_up(); + } + $this->assertEqualHTML( $expected, _block_template_skip_link_markup( $template_html ) ); } /** - * Tests that no skip link is added when there is no main element. - * - * @ticket 64361 + * Data provider for test_block_template_skip_link_markup. * - * @covers ::_block_template_skip_link_markup + * @return array> */ - public function test_block_template_skip_link_not_inserted_when_main_missing() { - $template_html = '
Content
'; - $this->assertEqualHTML( - $template_html, - _block_template_skip_link_markup( $template_html ), - '', - 'Skip link markup should not be added when there is no main element.' + public function data_provider_to_test_block_template_skip_link_markup(): array { + return array( + 'inserts_link_and_adds_main_id_when_missing' => array( + 'set_up' => null, + 'template_html' => '
Content
', + 'expected' => ' + +
Content
+ ', + ), + 'uses_existing_main_id' => array( + 'set_up' => null, + 'template_html' => '
Content
', + 'expected' => ' + +
Content
+ ', + ), + 'action_removed' => array( + 'set_up' => static function () { + remove_action( 'wp_footer', 'the_block_template_skip_link' ); + }, + 'template_html' => '
Content
', + 'expected' => '
Content
', + ), + 'main_missing' => array( + 'set_up' => null, + 'template_html' => '
Content
', + 'expected' => '
Content
', + ), ); } From 45c26b8f60b45c3ed4850969200ba415f7b77f87 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 5 Jan 2026 10:27:48 -0800 Subject: [PATCH 19/34] Add test cases for malformed IDs --- tests/phpunit/tests/block-template-utils.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/phpunit/tests/block-template-utils.php b/tests/phpunit/tests/block-template-utils.php index 7773a6949b8eb..f46f044a04327 100644 --- a/tests/phpunit/tests/block-template-utils.php +++ b/tests/phpunit/tests/block-template-utils.php @@ -336,6 +336,22 @@ public function data_provider_to_test_block_template_skip_link_markup(): array {
Content
', ), + 'main_has_boolean_id' => array( + 'set_up' => null, + 'template_html' => '
Content
', + 'expected' => ' + +
Content
+ ', + ), + 'main_has_whitespace_id' => array( + 'set_up' => null, + 'template_html' => '
Content
', + 'expected' => ' + +
Content
+ ', + ), 'action_removed' => array( 'set_up' => static function () { remove_action( 'wp_footer', 'the_block_template_skip_link' ); From c565ff9dd1ec64c4c629f17b8e9bffd6bb637a49 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 5 Jan 2026 10:29:44 -0800 Subject: [PATCH 20/34] Remove unnecessary variable --- src/wp-includes/theme-templates.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/wp-includes/theme-templates.php b/src/wp-includes/theme-templates.php index f8b4c7c1bdbda..cc3af0d53d8f5 100644 --- a/src/wp-includes/theme-templates.php +++ b/src/wp-includes/theme-templates.php @@ -126,8 +126,7 @@ function wp_enqueue_block_template_skip_link() { return; } - $handle = 'wp-block-template-skip-link'; - wp_enqueue_style( $handle ); + wp_enqueue_style( 'wp-block-template-skip-link' ); } /** From ce5b88e78c00227866e004e7c8e4345419a57f0a Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 7 Jan 2026 12:57:44 -0800 Subject: [PATCH 21/34] Use esc_url() instead of esc_attr() --- src/wp-includes/block-template.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 986c8b7eb6efd..7b09c3fe77d10 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -362,8 +362,8 @@ public function insert_before( string $text ) { while ( $inserter->next_tag() ) { if ( 'DIV' === $inserter->get_tag() && $inserter->has_class( 'wp-site-blocks' ) ) { $skip_link = sprintf( - '', - esc_attr( $skip_link_target_id ), + '', + esc_url( '#' . $skip_link_target_id ), /* translators: Hidden accessibility text. */ esc_html__( 'Skip to content' ) ); From 8b83d4ed31111960ad64444c31f0a5f979a8019e Mon Sep 17 00:00:00 2001 From: Rutvik Savsani Date: Thu, 8 Jan 2026 19:20:48 +0530 Subject: [PATCH 22/34] Block template skip link handling --- src/wp-includes/block-template.php | 60 ++++++++++++++++++----------- src/wp-includes/theme-templates.php | 2 +- 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 7b09c3fe77d10..521119078005b 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -303,14 +303,38 @@ function get_the_block_template_html() { // (e.g. `.wp-site-blocks > *`). $template_html = '
' . $content . '
'; - return _block_template_skip_link_markup( $template_html ); + // Back-compat for plugins that disable functionality by unhooking this action. + if ( ! has_action( 'wp_footer', 'the_block_template_skip_link' ) ) { + return $template_html; + } + + return _block_template_add_skip_link( $template_html ); } /** - * Inserts the block template skip link into the template HTML. + * Inserts the block template skip-link into the template HTML. + * + * When a `MAIN` element exists in the template, this function ensures the + * element has an `id` attribute and inserts a link to that element at the + * top of the first `DIV.wp-site-blocks` match. + * + * Example: + * + * // Input. + *
+ * + *
+ *

... + * + * // Output. + *
+ * + *
+ *

+
Content
+ '; + + $this->assertEqualHTML( $expected, _block_template_add_skip_link( $template_html ) ); + } + + /** + * Tests that an existing MAIN ID is reused for the skip link. * - * @dataProvider data_provider_to_test_block_template_skip_link_markup + * @ticket 64361 + * + * @covers ::_block_template_add_skip_link */ - public function test_block_template_skip_link_markup( ?Closure $set_up, string $template_html, string $expected ) { - if ( $set_up instanceof Closure ) { - $set_up(); - } - $this->assertEqualHTML( $expected, _block_template_skip_link_markup( $template_html ) ); + public function test_block_template_add_skip_link_uses_existing_main_id() { + $template_html = '
Content
'; + $expected = ' + +
Content
+ '; + + $this->assertEqualHTML( $expected, _block_template_add_skip_link( $template_html ) ); } /** - * Data provider for test_block_template_skip_link_markup. + * Tests that a boolean MAIN ID is treated as missing and replaced with the default. + * + * @ticket 64361 * - * @return array> + * @covers ::_block_template_add_skip_link */ - public function data_provider_to_test_block_template_skip_link_markup(): array { - return array( - 'inserts_link_and_adds_main_id_when_missing' => array( - 'set_up' => null, - 'template_html' => '
Content
', - 'expected' => ' - -
Content
- ', - ), - 'uses_existing_main_id' => array( - 'set_up' => null, - 'template_html' => '
Content
', - 'expected' => ' - -
Content
- ', - ), - 'main_has_boolean_id' => array( - 'set_up' => null, - 'template_html' => '
Content
', - 'expected' => ' - -
Content
- ', - ), - 'main_has_whitespace_id' => array( - 'set_up' => null, - 'template_html' => '
Content
', - 'expected' => ' - -
Content
- ', - ), - 'action_removed' => array( - 'set_up' => static function () { - remove_action( 'wp_footer', 'the_block_template_skip_link' ); - }, - 'template_html' => '
Content
', - 'expected' => '
Content
', - ), - 'main_missing' => array( - 'set_up' => null, - 'template_html' => '
Content
', - 'expected' => '
Content
', - ), - ); + public function test_block_template_add_skip_link_handles_boolean_main_id() { + $template_html = '
Content
'; + $expected = ' + +
Content
+ '; + + $this->assertEqualHTML( $expected, _block_template_add_skip_link( $template_html ) ); + } + + /** + * Tests that a MAIN ID containing whitespace is preserved and used for the skip link. + * + * @ticket 64361 + * + * @covers ::_block_template_add_skip_link + */ + public function test_block_template_add_skip_link_preserves_whitespace_main_id() { + $template_html = '
Content
'; + $expected = ' + +
Content
+ '; + + $this->assertEqualHTML( $expected, _block_template_add_skip_link( $template_html ) ); + } + + /** + * Tests that no changes are made when there is no MAIN element. + * + * @ticket 64361 + * + * @covers ::_block_template_add_skip_link + */ + public function test_block_template_add_skip_link_does_not_modify_when_main_missing() { + $template_html = '
Content
'; + + $this->assertSame( $template_html, _block_template_add_skip_link( $template_html ) ); } /** diff --git a/tests/phpunit/tests/block-template.php b/tests/phpunit/tests/block-template.php index bb153dfea6c9a..c9f41b96082d1 100644 --- a/tests/phpunit/tests/block-template.php +++ b/tests/phpunit/tests/block-template.php @@ -311,6 +311,57 @@ public function test_get_the_block_template_html_skips_singular_query_loop_when_ $this->assertSame( array( false ), $in_the_loop_logs, 'Main query loop was triggered despite a custom block template outside the current theme being used' ); } + /** + * Tests that `get_the_block_template_html()` adds a skip link when a MAIN element is present. + * + * @ticket 64361 + * @covers ::get_the_block_template_html + */ + public function test_get_the_block_template_html_adds_skip_link_when_main_present() { + global $_wp_current_template_id, $_wp_current_template_content; + + $_wp_current_template_id = get_stylesheet() . '//index'; + $_wp_current_template_content = '
Content
'; + + $output = get_the_block_template_html(); + + $this->assertStringContainsString( + '
*

... * * When the `MAIN` element already contains a non-empty `id` value it will be - * used instead of the default skip-link ID. + * used instead of the default skip-link id. * * @access private * @since 7.0.0 @@ -343,25 +343,8 @@ function get_the_block_template_html() { * @return string Modified markup with skip link when applicable. */ function _block_template_add_skip_link( string $template_html ): string { - // Ensure a skip-link target exists and has an ID. - $processor = new WP_HTML_Tag_Processor( $template_html ); - - // Only add skip-links to templates with a MAIN element. - if ( ! $processor->next_tag( 'MAIN' ) ) { - return $template_html; - } - - $skip_link_target_id = $processor->get_attribute( 'id' ); - if ( ! is_string( $skip_link_target_id ) || '' === $skip_link_target_id ) { - $skip_link_target_id = 'wp--skip-link--target'; - $processor->set_attribute( 'id', $skip_link_target_id ); - } - - // Apply any updates from setting the main ID. - $template_html = $processor->get_updated_html(); - // Anonymous subclass of WP_HTML_Tag_Processor to access protected bookmark spans. - $inserter = new class( $template_html ) extends WP_HTML_Tag_Processor { + $processor = new class( $template_html ) extends WP_HTML_Tag_Processor { /** * Inserts text before the current token. * @@ -373,20 +356,42 @@ public function insert_before( string $text ) { } }; - while ( $inserter->next_tag() ) { - if ( 'DIV' === $inserter->get_tag() && $inserter->has_class( 'wp-site-blocks' ) ) { - $skip_link = sprintf( - '', - esc_url( '#' . $skip_link_target_id ), - /* translators: Hidden accessibility text. */ - esc_html__( 'Skip to content' ) - ); - $inserter->insert_before( $skip_link ); - break; - } + // Find and bookmark the first DIV.wp-site-blocks. + if ( + ! $processor->next_tag( + array( + 'tag_name' => 'DIV', + 'class_name' => 'wp-site-blocks', + ) + ) + ) { + return $template_html; + } + $processor->set_bookmark( 'skip_link_insertion_point' ); + + // Ensure the MAIN element has an ID. + if ( ! $processor->next_tag( 'MAIN' ) ) { + return $template_html; + } + + $skip_link_target_id = $processor->get_attribute( 'id' ); + if ( ! is_string( $skip_link_target_id ) || '' === $skip_link_target_id ) { + $skip_link_target_id = 'wp--skip-link--target'; + $processor->set_attribute( 'id', $skip_link_target_id ); } - return $inserter->get_updated_html(); + // Seek back to the bookmarked insertion point. + $processor->seek( 'skip_link_insertion_point' ); + + $skip_link = sprintf( + '', + esc_url( '#' . $skip_link_target_id ), + /* translators: Hidden accessibility text. */ + esc_html__( 'Skip to content' ) + ); + $processor->insert_before( $skip_link ); + + return $processor->get_updated_html(); } /** From 350b57c7d5d9e5e77fc76c523c3fa2cc3a3094e1 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 9 Jan 2026 00:25:07 -0800 Subject: [PATCH 25/34] Fix URL for wp-block-template-skip-link --- src/wp-includes/script-loader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 36004fb512674..47da27d947455 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -1606,7 +1606,7 @@ function wp_default_styles( $styles ) { $styles->add( 'customize-preview', "/wp-includes/css/customize-preview$suffix.css", array( 'dashicons' ) ); $styles->add( 'wp-empty-template-alert', "/wp-includes/css/wp-empty-template-alert$suffix.css" ); $skip_link_style_path = WPINC . "/css/wp-block-template-skip-link$suffix.css"; - $styles->add( 'wp-block-template-skip-link', $skip_link_style_path ); + $styles->add( 'wp-block-template-skip-link', "/$skip_link_style_path" ); $styles->add_data( 'wp-block-template-skip-link', 'path', ABSPATH . $skip_link_style_path ); // External libraries and friends. From b1afb70f43530e165a9a2a224679750fa3ce41fa Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 9 Jan 2026 10:51:41 -0800 Subject: [PATCH 26/34] Debug: Reduce phpunit-tests for debugging --- .github/workflows/phpunit-tests.yml | 10 +++++----- .github/workflows/reusable-phpunit-tests-v3.yml | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/phpunit-tests.yml b/.github/workflows/phpunit-tests.yml index 63615dfad19f8..23d507feb3d61 100644 --- a/.github/workflows/phpunit-tests.yml +++ b/.github/workflows/phpunit-tests.yml @@ -69,7 +69,7 @@ jobs: secrets: inherit if: ${{ startsWith( github.repository, 'WordPress/' ) && ( github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' ) }} strategy: - fail-fast: false + fail-fast: true matrix: os: [ ubuntu-24.04 ] php: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5' ] @@ -146,7 +146,7 @@ jobs: secrets: inherit if: ${{ startsWith( github.repository, 'WordPress/' ) && ( github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' ) }} strategy: - fail-fast: false + fail-fast: true matrix: os: [ ubuntu-24.04 ] php: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5' ] @@ -198,7 +198,7 @@ jobs: secrets: inherit if: ${{ startsWith( github.repository, 'WordPress/' ) && ( github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' ) }} strategy: - fail-fast: false + fail-fast: true matrix: os: [ ubuntu-24.04 ] php: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5' ] @@ -246,7 +246,7 @@ jobs: secrets: inherit if: ${{ startsWith( github.repository, 'WordPress/' ) && ( github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' ) }} strategy: - fail-fast: false + fail-fast: true matrix: php: [ '7.2', '7.4', '8.0', '8.4' ] db-type: [ 'mysql' ] @@ -275,7 +275,7 @@ jobs: secrets: inherit if: ${{ ! startsWith( github.repository, 'WordPress/' ) && github.event_name == 'pull_request' }} strategy: - fail-fast: false + fail-fast: true matrix: php: [ '7.2', '8.4' ] db-version: [ '8.4', '11.8' ] diff --git a/.github/workflows/reusable-phpunit-tests-v3.yml b/.github/workflows/reusable-phpunit-tests-v3.yml index 34fffdfad4f99..e52c926118af6 100644 --- a/.github/workflows/reusable-phpunit-tests-v3.yml +++ b/.github/workflows/reusable-phpunit-tests-v3.yml @@ -205,7 +205,7 @@ jobs: continue-on-error: ${{ inputs.allow-errors }} run: | node ./tools/local-env/scripts/docker.js run \ - php ./vendor/bin/phpunit \ + php ./vendor/bin/phpunit --filter=test_wp_hoist_late_printed_style \ --verbose \ -c "${PHPUNIT_CONFIG}" \ ${{ inputs.phpunit-test-groups && '--group "${TEST_GROUPS}"' || '' }} \ @@ -217,23 +217,23 @@ jobs: - name: Run AJAX tests if: ${{ ! inputs.phpunit-test-groups && ! inputs.coverage-report }} continue-on-error: ${{ inputs.allow-errors }} - run: node ./tools/local-env/scripts/docker.js run php ./vendor/bin/phpunit --verbose -c "${PHPUNIT_CONFIG}" --group ajax + run: node ./tools/local-env/scripts/docker.js run php ./vendor/bin/phpunit --filter=test_wp_hoist_late_printed_style --verbose -c "${PHPUNIT_CONFIG}" --group ajax - name: Run ms-files tests as a multisite install if: ${{ inputs.multisite && ! inputs.phpunit-test-groups && ! inputs.coverage-report }} continue-on-error: ${{ inputs.allow-errors }} - run: node ./tools/local-env/scripts/docker.js run php ./vendor/bin/phpunit --verbose -c "${PHPUNIT_CONFIG}" --group ms-files + run: node ./tools/local-env/scripts/docker.js run php ./vendor/bin/phpunit --filter=test_wp_hoist_late_printed_style --verbose -c "${PHPUNIT_CONFIG}" --group ms-files - name: Run external HTTP tests if: ${{ ! inputs.multisite && ! inputs.phpunit-test-groups && ! inputs.coverage-report }} continue-on-error: ${{ inputs.allow-errors }} - run: node ./tools/local-env/scripts/docker.js run php ./vendor/bin/phpunit --verbose -c "${PHPUNIT_CONFIG}" --group external-http + run: node ./tools/local-env/scripts/docker.js run php ./vendor/bin/phpunit --filter=test_wp_hoist_late_printed_style --verbose -c "${PHPUNIT_CONFIG}" --group external-http # __fakegroup__ is excluded to force PHPUnit to ignore the settings in phpunit.xml.dist. - name: Run (Xdebug) tests if: ${{ ! inputs.phpunit-test-groups && ! inputs.coverage-report }} continue-on-error: ${{ inputs.allow-errors }} - run: LOCAL_PHP_XDEBUG=true node ./tools/local-env/scripts/docker.js run php ./vendor/bin/phpunit -v --group xdebug --exclude-group __fakegroup__ + run: LOCAL_PHP_XDEBUG=true node ./tools/local-env/scripts/docker.js run php ./vendor/bin/phpunit --filter=test_wp_hoist_late_printed_style -v --group xdebug --exclude-group __fakegroup__ - name: Upload test coverage report to Codecov if: ${{ inputs.coverage-report }} From 3ed903967411448d21e4473e3f6e0b6633394189 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 9 Jan 2026 11:13:06 -0800 Subject: [PATCH 27/34] Revert "Debug: Reduce phpunit-tests for debugging" This reverts commit b1afb70f43530e165a9a2a224679750fa3ce41fa. --- .github/workflows/phpunit-tests.yml | 10 +++++----- .github/workflows/reusable-phpunit-tests-v3.yml | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/phpunit-tests.yml b/.github/workflows/phpunit-tests.yml index 23d507feb3d61..63615dfad19f8 100644 --- a/.github/workflows/phpunit-tests.yml +++ b/.github/workflows/phpunit-tests.yml @@ -69,7 +69,7 @@ jobs: secrets: inherit if: ${{ startsWith( github.repository, 'WordPress/' ) && ( github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' ) }} strategy: - fail-fast: true + fail-fast: false matrix: os: [ ubuntu-24.04 ] php: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5' ] @@ -146,7 +146,7 @@ jobs: secrets: inherit if: ${{ startsWith( github.repository, 'WordPress/' ) && ( github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' ) }} strategy: - fail-fast: true + fail-fast: false matrix: os: [ ubuntu-24.04 ] php: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5' ] @@ -198,7 +198,7 @@ jobs: secrets: inherit if: ${{ startsWith( github.repository, 'WordPress/' ) && ( github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' ) }} strategy: - fail-fast: true + fail-fast: false matrix: os: [ ubuntu-24.04 ] php: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5' ] @@ -246,7 +246,7 @@ jobs: secrets: inherit if: ${{ startsWith( github.repository, 'WordPress/' ) && ( github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' ) }} strategy: - fail-fast: true + fail-fast: false matrix: php: [ '7.2', '7.4', '8.0', '8.4' ] db-type: [ 'mysql' ] @@ -275,7 +275,7 @@ jobs: secrets: inherit if: ${{ ! startsWith( github.repository, 'WordPress/' ) && github.event_name == 'pull_request' }} strategy: - fail-fast: true + fail-fast: false matrix: php: [ '7.2', '8.4' ] db-version: [ '8.4', '11.8' ] diff --git a/.github/workflows/reusable-phpunit-tests-v3.yml b/.github/workflows/reusable-phpunit-tests-v3.yml index e52c926118af6..34fffdfad4f99 100644 --- a/.github/workflows/reusable-phpunit-tests-v3.yml +++ b/.github/workflows/reusable-phpunit-tests-v3.yml @@ -205,7 +205,7 @@ jobs: continue-on-error: ${{ inputs.allow-errors }} run: | node ./tools/local-env/scripts/docker.js run \ - php ./vendor/bin/phpunit --filter=test_wp_hoist_late_printed_style \ + php ./vendor/bin/phpunit \ --verbose \ -c "${PHPUNIT_CONFIG}" \ ${{ inputs.phpunit-test-groups && '--group "${TEST_GROUPS}"' || '' }} \ @@ -217,23 +217,23 @@ jobs: - name: Run AJAX tests if: ${{ ! inputs.phpunit-test-groups && ! inputs.coverage-report }} continue-on-error: ${{ inputs.allow-errors }} - run: node ./tools/local-env/scripts/docker.js run php ./vendor/bin/phpunit --filter=test_wp_hoist_late_printed_style --verbose -c "${PHPUNIT_CONFIG}" --group ajax + run: node ./tools/local-env/scripts/docker.js run php ./vendor/bin/phpunit --verbose -c "${PHPUNIT_CONFIG}" --group ajax - name: Run ms-files tests as a multisite install if: ${{ inputs.multisite && ! inputs.phpunit-test-groups && ! inputs.coverage-report }} continue-on-error: ${{ inputs.allow-errors }} - run: node ./tools/local-env/scripts/docker.js run php ./vendor/bin/phpunit --filter=test_wp_hoist_late_printed_style --verbose -c "${PHPUNIT_CONFIG}" --group ms-files + run: node ./tools/local-env/scripts/docker.js run php ./vendor/bin/phpunit --verbose -c "${PHPUNIT_CONFIG}" --group ms-files - name: Run external HTTP tests if: ${{ ! inputs.multisite && ! inputs.phpunit-test-groups && ! inputs.coverage-report }} continue-on-error: ${{ inputs.allow-errors }} - run: node ./tools/local-env/scripts/docker.js run php ./vendor/bin/phpunit --filter=test_wp_hoist_late_printed_style --verbose -c "${PHPUNIT_CONFIG}" --group external-http + run: node ./tools/local-env/scripts/docker.js run php ./vendor/bin/phpunit --verbose -c "${PHPUNIT_CONFIG}" --group external-http # __fakegroup__ is excluded to force PHPUnit to ignore the settings in phpunit.xml.dist. - name: Run (Xdebug) tests if: ${{ ! inputs.phpunit-test-groups && ! inputs.coverage-report }} continue-on-error: ${{ inputs.allow-errors }} - run: LOCAL_PHP_XDEBUG=true node ./tools/local-env/scripts/docker.js run php ./vendor/bin/phpunit --filter=test_wp_hoist_late_printed_style -v --group xdebug --exclude-group __fakegroup__ + run: LOCAL_PHP_XDEBUG=true node ./tools/local-env/scripts/docker.js run php ./vendor/bin/phpunit -v --group xdebug --exclude-group __fakegroup__ - name: Upload test coverage report to Codecov if: ${{ inputs.coverage-report }} From 61711e2716742d44a86c5b26477cad4d74edac83 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 9 Jan 2026 11:13:24 -0800 Subject: [PATCH 28/34] Add wp-block-template-skip-link-css to $ignored_styles --- tests/phpunit/tests/template.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/phpunit/tests/template.php b/tests/phpunit/tests/template.php index a965665360b05..dacc69fba6330 100644 --- a/tests/phpunit/tests/template.php +++ b/tests/phpunit/tests/template.php @@ -1797,6 +1797,7 @@ static function () { $ignored_styles = array( 'core-block-supports-duotone-inline-css', 'wp-block-library-theme-css', + 'wp-block-template-skip-link-css', 'wp-block-template-skip-link-inline-css', ); From 999acca698697a9e715a6c16fe3f2a696b140b7a Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 9 Jan 2026 11:19:31 -0800 Subject: [PATCH 29/34] Update docs for _block_template_add_skip_link() Co-authored-by: Dennis Snell --- src/wp-includes/block-template.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index e6651aab859c6..7374f57be3453 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -315,22 +315,24 @@ function get_the_block_template_html() { * Inserts the block template skip-link into the template HTML. * * When a `MAIN` element exists in the template, this function will ensure - * that the element contains a `id` attribute and will insert a link to - * that main element at the top of the first `DIV.wp-site-blocks` match. + * that the element contains a `id` attribute, and it will insert a link to + * that `MAIN` element before the first `DIV.wp-site-blocks` element, which + * is the wrapper for all blocks in a block template as constructed by + * {@see get_the_block_template_html()}. * * Example: * * // Input. - *
+ *
* - *
+ *
*

... * * // Output. - *
+ * + *
* - *
- * + *
*

... * * When the `MAIN` element already contains a non-empty `id` value it will be From 0db64ac7e19d52bb33f057895f9e47a260c278b2 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 9 Jan 2026 12:07:04 -0800 Subject: [PATCH 30/34] Fix comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/wp-includes/block-template.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 7374f57be3453..5acdb255847f2 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -315,7 +315,7 @@ function get_the_block_template_html() { * Inserts the block template skip-link into the template HTML. * * When a `MAIN` element exists in the template, this function will ensure - * that the element contains a `id` attribute, and it will insert a link to + * that the element contains an `id` attribute, and it will insert a link to * that `MAIN` element before the first `DIV.wp-site-blocks` element, which * is the wrapper for all blocks in a block template as constructed by * {@see get_the_block_template_html()}. From 357ea8ac2177aa34c4b436b2d47ac7132303975a Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sat, 10 Jan 2026 21:09:53 -0800 Subject: [PATCH 31/34] Add wp-block-template-skip-link to RTL styles --- src/wp-includes/script-loader.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 47da27d947455..2946f19656d4c 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -1803,6 +1803,7 @@ function wp_default_styles( $styles ) { 'media-views', 'wp-pointer', 'wp-jquery-ui-dialog', + 'wp-block-template-skip-link', // Package styles. 'wp-reset-editor-styles', 'wp-editor-classic-layout-styles', From 65fedcbd1f6b2acb502d15310d9867d710526fef Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sat, 10 Jan 2026 21:58:46 -0800 Subject: [PATCH 32/34] Remove unnecessary re-adding of action --- tests/phpunit/tests/block-template.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/tests/phpunit/tests/block-template.php b/tests/phpunit/tests/block-template.php index c9f41b96082d1..885168c030b07 100644 --- a/tests/phpunit/tests/block-template.php +++ b/tests/phpunit/tests/block-template.php @@ -344,16 +344,9 @@ public function test_get_the_block_template_html_does_not_add_skip_link_when_act $_wp_current_template_id = get_stylesheet() . '//index'; $_wp_current_template_content = '
Content
'; - $was_hooked = (bool) has_action( 'wp_footer', 'the_block_template_skip_link' ); remove_action( 'wp_footer', 'the_block_template_skip_link' ); - try { - $output = get_the_block_template_html(); - } finally { - if ( $was_hooked ) { - add_action( 'wp_footer', 'the_block_template_skip_link' ); - } - } + $output = get_the_block_template_html(); $this->assertStringNotContainsString( '