From ccb80820b53eef5cd38fe89cdcd1dd90dd51b12b Mon Sep 17 00:00:00 2001 From: Shane Rounce Date: Tue, 28 Jul 2026 21:08:06 +0000 Subject: [PATCH] Themes: Open and close the block theme document in get_header() and get_footer(). Block themes ship no header.php or footer.php, so both functions fall through to the templates in wp-includes/theme-compat/, deprecated since 3.0.0. On a block theme that prints the old default theme's page frame, the site title and description, a "proudly powered by WordPress" footer and a design credit comment into the page, on top of the deprecation notice. Both functions now print the document opening and closing that the template canvas already uses, whenever the active theme is a block theme providing no template of its own. Classic themes are untouched and still fall back. The canvas markup moves into _wp_block_theme_document_start() and _wp_block_theme_document_end() so the document has a single definition; template-canvas.php calls them and its output is byte for byte unchanged. See #55023. --- src/wp-includes/block-template.php | 44 ++++++++++++++ src/wp-includes/general-template.php | 19 ++++++ src/wp-includes/template-canvas.php | 16 ++--- src/wp-includes/template.php | 34 +++++++++++ tests/phpunit/tests/general/template.php | 77 ++++++++++++++++++++++++ 5 files changed, 178 insertions(+), 12 deletions(-) diff --git a/src/wp-includes/block-template.php b/src/wp-includes/block-template.php index 85ad05cfc91ae..af2ed79fa6c30 100644 --- a/src/wp-includes/block-template.php +++ b/src/wp-includes/block-template.php @@ -233,6 +233,50 @@ function _block_template_render_title_tag() { echo '' . wp_get_document_title() . '' . "\n"; } +/** + * Prints the opening of the document a block theme renders into. + * + * Block themes have no header.php, so the markup that opens their documents lives in the + * template canvas rather than in the theme. Printing it from here lets anything that has to + * open a document on a block theme's behalf produce exactly what the canvas produces. + * + * @since 7.1.0 + * @access private + */ +function _wp_block_theme_document_start() { + echo '' . "\n"; + echo '\n"; + echo "\n"; + echo "\t" . '' . "\n"; + echo "\t"; + wp_head(); + echo "\n"; + echo "\n"; + echo '\n"; + wp_body_open(); +} + +/** + * Prints the closing of the document a block theme renders into. + * + * The counterpart to _wp_block_theme_document_start(). + * + * @since 7.1.0 + * @access private + */ +function _wp_block_theme_document_end() { + echo "\n"; + wp_footer(); + echo "\n"; + echo "\n"; +} + /** * Returns the markup for the current template. * diff --git a/src/wp-includes/general-template.php b/src/wp-includes/general-template.php index 29c30137e173d..c7c0df5cd9fb5 100644 --- a/src/wp-includes/general-template.php +++ b/src/wp-includes/general-template.php @@ -45,6 +45,19 @@ function get_header( $name = null, $args = array() ) { $templates[] = 'header.php'; + /* + * A block theme has no header.php by design, so locate_template() falls through to + * wp-includes/theme-compat/header.php: markup deprecated since 3.0.0 that opens the + * document with the old default theme's page frame and its site title and description. + * Print the same document opening the template canvas uses instead, so a block theme + * gets its own markup whether the page is rendered by a block template or by a caller + * of this function. + */ + if ( wp_is_block_theme() && ! _wp_theme_has_template( $templates ) ) { + _wp_block_theme_document_start(); + return; + } + if ( ! locate_template( $templates, true, true, $args ) ) { return false; } @@ -89,6 +102,12 @@ function get_footer( $name = null, $args = array() ) { $templates[] = 'footer.php'; + // See get_header(): block themes close their document the way the canvas does. + if ( wp_is_block_theme() && ! _wp_theme_has_template( $templates ) ) { + _wp_block_theme_document_end(); + return; + } + if ( ! locate_template( $templates, true, true, $args ) ) { return false; } diff --git a/src/wp-includes/template-canvas.php b/src/wp-includes/template-canvas.php index 8f631e5a77dfb..1631832b91fd6 100644 --- a/src/wp-includes/template-canvas.php +++ b/src/wp-includes/template-canvas.php @@ -10,18 +10,10 @@ * This needs to run before so that blocks can add scripts and styles in wp_head(). */ $template_html = get_the_block_template_html(); -?> -> - - - - -> - +_wp_block_theme_document_start(); +?> - - - - +assertNull( get_footer() ); } + /** + * Tests that a block theme opens its own document rather than the deprecated fallback. + * + * A block theme ships no header.php, so locate_template() reaches + * wp-includes/theme-compat/header.php, whose markup opens the page with the old default + * theme's frame, site title and description. + * + * @ticket 55023 + * + * @covers ::get_header + */ + public function test_get_header_uses_block_theme_document_when_theme_has_no_header() { + switch_theme( 'block-theme' ); + + $output = get_echo( 'get_header' ); + + $this->assertStringContainsString( '', $output, 'The document should be opened.' ); + $this->assertStringContainsString( 'assertStringNotContainsString( 'id="page"', $output, 'The theme-compat page frame should not be printed.' ); + $this->assertStringNotContainsString( 'id="header"', $output, 'The theme-compat header should not be printed.' ); + } + + /** + * Tests that a block theme closes the document it opened. + * + * @ticket 55023 + * + * @covers ::get_footer + */ + public function test_get_footer_uses_block_theme_document_when_theme_has_no_footer() { + switch_theme( 'block-theme' ); + + // As a page does: wp_head() is where the back-compat skip link action is unhooked. + get_echo( 'get_header' ); + $output = get_echo( 'get_footer' ); + + $this->assertStringContainsString( '', $output, 'The body element should be closed.' ); + $this->assertStringContainsString( '', $output, 'The document should be closed.' ); + $this->assertStringNotContainsString( 'proudly powered by', $output, 'The theme-compat footer should not be printed.' ); + $this->assertStringNotContainsString( 'id="footer"', $output, 'The theme-compat footer should not be printed.' ); + } + + /** + * Tests that the deprecation notice for a missing header.php is not raised for block themes, + * which are not expected to provide one. + * + * @ticket 55023 + * + * @covers ::get_header + */ + public function test_get_header_does_not_deprecate_for_block_theme() { + switch_theme( 'block-theme' ); + + // Would trigger the deprecation notice handled by WP_UnitTestCase and fail the test. + get_echo( 'get_header' ); + + $this->assertTrue( wp_is_block_theme(), 'The test should run on a block theme.' ); + } + + /** + * Tests that a classic theme without header.php keeps the deprecated fallback, so themes + * relying on it are unaffected. camelCase is a classic fixture theme with no header.php. + * + * @ticket 55023 + * + * @expectedDeprecated Theme without header.php + * + * @covers ::get_header + */ + public function test_get_header_still_falls_back_for_classic_theme() { + switch_theme( 'camelCase' ); + + $output = get_echo( 'get_header' ); + + $this->assertStringContainsString( 'id="page"', $output, 'The theme-compat markup should still be printed for classic themes.' ); + } + /** * @ticket 40969 *