From d57be3b93d2a55b00d92109ed115aeff176417c3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 04:38:45 +0000 Subject: [PATCH 1/5] Initial plan From 2eed96ebf269e2dc8d893371d59b7e40a5a2891b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 04:52:29 +0000 Subject: [PATCH 2/5] Remove link rel alternate output from global header REST endpoint Remove feed_links, feed_links_extra, and wp_oembed_add_discovery_links from wp_head when rendering the global header via REST API. These tags are not relevant for the reusable header component served to other sites. Co-authored-by: dd32 <767313+dd32@users.noreply.github.com> --- mu-plugins/blocks/global-header-footer/blocks.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mu-plugins/blocks/global-header-footer/blocks.php b/mu-plugins/blocks/global-header-footer/blocks.php index f54931f7..d9578897 100644 --- a/mu-plugins/blocks/global-header-footer/blocks.php +++ b/mu-plugins/blocks/global-header-footer/blocks.php @@ -231,6 +231,11 @@ function restore_inner_group_container() { */ function rest_render_global_header( $request ) { + // Remove tags from the head, as they're not relevant for the reusable header. + remove_action( 'wp_head', 'feed_links', 2 ); + remove_action( 'wp_head', 'feed_links_extra', 3 ); + remove_action( 'wp_head', 'wp_oembed_add_discovery_links' ); + // Remove the theme stylesheet from rest requests. add_filter( 'wp_enqueue_scripts', From eb1df623ab16bd5b8877f355430a6fd2f53f0e9b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 05:00:36 +0000 Subject: [PATCH 3/5] Use WP_HTML_Tag_Processor to strip all link rel alternate tags from global header output Replace the remove_action approach (which only handled known core actions) with WP_HTML_Tag_Processor-based filtering that catches tags from any source, including plugins. Uses token-based rel attribute matching to avoid false positives with non-alternate rel values. Co-authored-by: dd32 <767313+dd32@users.noreply.github.com> --- .../blocks/global-header-footer/blocks.php | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/mu-plugins/blocks/global-header-footer/blocks.php b/mu-plugins/blocks/global-header-footer/blocks.php index d9578897..c5859359 100644 --- a/mu-plugins/blocks/global-header-footer/blocks.php +++ b/mu-plugins/blocks/global-header-footer/blocks.php @@ -231,11 +231,6 @@ function restore_inner_group_container() { */ function rest_render_global_header( $request ) { - // Remove tags from the head, as they're not relevant for the reusable header. - remove_action( 'wp_head', 'feed_links', 2 ); - remove_action( 'wp_head', 'feed_links_extra', 3 ); - remove_action( 'wp_head', 'wp_oembed_add_discovery_links' ); - // Remove the theme stylesheet from rest requests. add_filter( 'wp_enqueue_scripts', @@ -410,6 +405,12 @@ function render_global_header( $attributes = array() ) { $head_markup = ob_get_clean(); } + // Strip tags from the head, as they're not relevant for the global header. + // This catches tags from core (feed links, oEmbed) and any added by plugins. + if ( $is_rest_request ) { + $head_markup = remove_head_alternate_links( $head_markup ); + } + $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => 'global-header wp-block-group' ) ); @@ -422,6 +423,33 @@ function render_global_header( $attributes = array() ) { ); } +/** + * Remove `` tags from HTML markup. + * + * Uses WP_HTML_Tag_Processor to reliably find and remove `` tags + * whose `rel` attribute contains "alternate", regardless of which plugin + * or core function added them. + * + * @param string $markup The HTML markup to filter. + * + * @return string The markup with alternate link tags removed. + */ +function remove_head_alternate_links( $markup ) { + $p = new \WP_HTML_Tag_Processor( $markup ); + + while ( $p->next_tag( 'LINK' ) ) { + $rel = $p->get_attribute( 'rel' ); + + if ( is_string( $rel ) && in_array( 'alternate', preg_split( '/\s+/', $rel ), true ) ) { + $p->set_attribute( 'data-wp-remove', 'true' ); + } + } + + // Remove the marked link tags from the output. + // This is safe because is a void element (self-closing, no nested content). + return preg_replace( '/]*\bdata-wp-remove=[^>]*>/i', '', $p->get_updated_html() ); +} + /** * Determine if the current site is a Rosetta site (e.g., `es-mx.wordpress.org`). * From 51b7225d7b0a5a37fcd5fae9b55777611874bb7f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 05:07:20 +0000 Subject: [PATCH 4/5] Simplify remove_head_alternate_links to use regex directly Replace the two-step WP_HTML_Tag_Processor + regex approach with a single regex that directly matches and removes tags whose rel attribute contains the word "alternate". Co-authored-by: dd32 <767313+dd32@users.noreply.github.com> --- .../blocks/global-header-footer/blocks.php | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/mu-plugins/blocks/global-header-footer/blocks.php b/mu-plugins/blocks/global-header-footer/blocks.php index c5859359..1ffe1492 100644 --- a/mu-plugins/blocks/global-header-footer/blocks.php +++ b/mu-plugins/blocks/global-header-footer/blocks.php @@ -426,28 +426,16 @@ function render_global_header( $attributes = array() ) { /** * Remove `` tags from HTML markup. * - * Uses WP_HTML_Tag_Processor to reliably find and remove `` tags - * whose `rel` attribute contains "alternate", regardless of which plugin - * or core function added them. + * Uses a regex to match and remove `` tags whose `rel` attribute + * contains "alternate", regardless of which plugin or core function + * added them. * * @param string $markup The HTML markup to filter. * * @return string The markup with alternate link tags removed. */ function remove_head_alternate_links( $markup ) { - $p = new \WP_HTML_Tag_Processor( $markup ); - - while ( $p->next_tag( 'LINK' ) ) { - $rel = $p->get_attribute( 'rel' ); - - if ( is_string( $rel ) && in_array( 'alternate', preg_split( '/\s+/', $rel ), true ) ) { - $p->set_attribute( 'data-wp-remove', 'true' ); - } - } - - // Remove the marked link tags from the output. - // This is safe because is a void element (self-closing, no nested content). - return preg_replace( '/]*\bdata-wp-remove=[^>]*>/i', '', $p->get_updated_html() ); + return preg_replace( '/]*\brel=["\'][^"\']*\balternate\b[^"\']*["\'][^>]*\/?>/i', '', $markup ); } /** From 06421af26555b9a7d0cc9e4eccb66987ebcfbb29 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 05:20:21 +0000 Subject: [PATCH 5/5] Add PHPUnit tests for remove_head_alternate_links() Co-authored-by: dd32 <767313+dd32@users.noreply.github.com> --- phpunit/test-global-header-footer.php | 127 ++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 phpunit/test-global-header-footer.php diff --git a/phpunit/test-global-header-footer.php b/phpunit/test-global-header-footer.php new file mode 100644 index 00000000..aa609745 --- /dev/null +++ b/phpunit/test-global-header-footer.php @@ -0,0 +1,127 @@ +'; + $this->assertEmpty( trim( remove_head_alternate_links( $input ) ) ); + } + + /** + * Test that Atom feed link tags are removed. + */ + public function test_removes_atom_feed_link() { + $input = ''; + $this->assertEmpty( trim( remove_head_alternate_links( $input ) ) ); + } + + /** + * Test that oEmbed discovery link tags are removed. + */ + public function test_removes_oembed_link() { + $input = ''; + $this->assertEmpty( trim( remove_head_alternate_links( $input ) ) ); + } + + /** + * Test that hreflang alternate link tags are removed. + */ + public function test_removes_hreflang_link() { + $input = ''; + $this->assertEmpty( trim( remove_head_alternate_links( $input ) ) ); + } + + /** + * Test that alternate links with single-quoted attributes are removed. + */ + public function test_removes_single_quoted_alternate() { + $input = ""; + $this->assertEmpty( trim( remove_head_alternate_links( $input ) ) ); + } + + /** + * Test that alternate links without self-closing slash are removed. + */ + public function test_removes_non_self_closing() { + $input = ''; + $this->assertEmpty( trim( remove_head_alternate_links( $input ) ) ); + } + + /** + * Test that compound rel values containing "alternate" are removed. + */ + public function test_removes_compound_rel_with_alternate() { + $input = ''; + $this->assertEmpty( trim( remove_head_alternate_links( $input ) ) ); + } + + /** + * Test that stylesheet links are preserved. + */ + public function test_preserves_stylesheet_link() { + $input = ''; + $this->assertSame( $input, remove_head_alternate_links( $input ) ); + } + + /** + * Test that canonical links are preserved. + */ + public function test_preserves_canonical_link() { + $input = ''; + $this->assertSame( $input, remove_head_alternate_links( $input ) ); + } + + /** + * Test that preconnect links are preserved. + */ + public function test_preserves_preconnect_link() { + $input = ''; + $this->assertSame( $input, remove_head_alternate_links( $input ) ); + } + + /** + * Test that icon links are preserved. + */ + public function test_preserves_icon_link() { + $input = ''; + $this->assertSame( $input, remove_head_alternate_links( $input ) ); + } + + /** + * Test that mixed markup keeps non-alternate links and removes alternate ones. + */ + public function test_mixed_markup() { + $input = ' + + + + +'; + + $result = remove_head_alternate_links( $input ); + + $this->assertStringContainsString( 'stylesheet', $result ); + $this->assertStringContainsString( 'preconnect', $result ); + $this->assertStringNotContainsString( 'alternate', $result ); + } + + /** + * Test that empty string input returns empty string. + */ + public function test_empty_input() { + $this->assertSame( '', remove_head_alternate_links( '' ) ); + } + + /** + * Test that markup with no link tags is returned unchanged. + */ + public function test_no_link_tags() { + $input = '
Hello
'; + $this->assertSame( $input, remove_head_alternate_links( $input ) ); + } +}