Skip to content

Themes: Open and close the block theme document in get_header() and get_footer() - #12746

Open
AceMedia wants to merge 1 commit into
WordPress:trunkfrom
AceMedia:fix/55023-block-theme-get-header
Open

Themes: Open and close the block theme document in get_header() and get_footer()#12746
AceMedia wants to merge 1 commit into
WordPress:trunkfrom
AceMedia:fix/55023-block-theme-get-header

Conversation

@AceMedia

Copy link
Copy Markdown

Trac ticket: https://core.trac.wordpress.org/ticket/55023

The problem, seen in production

I found this in the served markup of my own news site, which runs a block theme:

<!-- Gorgeous design by Michael Heilemann - http://binarybonsai.com/ -->

Tracing it back, a plugin of mine serves several routes from PHP and calls get_header() and get_footer() around its own markup, which plugins are allowed to do. A block theme ships no header.php or footer.php, so locate_template() falls through to wp-includes/theme-compat/, and those routes were being served like this:

<div id="page">
<div id="header" role="banner">
	<div id="headerimg">
		<h1><a href="https://example.com/">Site name</a></h1>
		<div class="description">Tagline</div>
	</div>
</div>
<hr />
...
<div id="footer" role="contentinfo">
	<p>Site name is proudly powered by <a href="https://wordpress.org/">WordPress</a></p>
</div>
</div>

<!-- Gorgeous design by Michael Heilemann - http://binarybonsai.com/ -->

Eight public URLs were affected. My own header and footer were nowhere in the markup; in their place were the old default theme's page frame, a duplicate site title and tagline, a "proudly powered by WordPress" promo, and a design credit for a theme the site has never used. The deprecation notice discussed on this ticket is that same fallback announcing itself. The markup is what visitors and crawlers actually received.

wp-signup.php and wp-activate.php reach the same fallback inside core, which is where the ticket started.

The change

get_header() and get_footer() 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. A block theme therefore opens the same document whether the page is rendered by a block template or by a caller of these functions.

Classic themes are untouched: with no header.php they still reach wp-includes/theme-compat/ and still raise the deprecation notice, which for them remains correct advice.

This follows the approach the ticket converged on in comment 23, that the markup needed is the one in template-canvas.php, rather than block_header_area() / block_footer_area(), which cannot open and close a document and were argued against there for exactly that reason.

  • _wp_block_theme_document_start() and _wp_block_theme_document_end() in block-template.php hold the document markup so it has a single definition. template-canvas.php calls them, and its output is byte for byte unchanged.
  • _wp_theme_has_template() in template.php answers whether the theme itself provides a template, which locate_template() cannot be asked, since it is the function doing the falling back.

Tests

Four tests added to tests/phpunit/tests/general/template.php:

  • a block theme's get_header() opens a document and prints none of the theme-compat frame;
  • its get_footer() closes the document and prints no "proudly powered by";
  • no deprecation notice is raised for a block theme;
  • a classic theme with no header.php still gets the fallback and still raises the notice.

Run against trunk:

tests/phpunit/tests/general/template.php   OK (39 tests, 84 assertions)
tests/phpunit/tests/theme.php              OK (93 tests, 5562 assertions)
tests/phpunit/tests/template.php           OK (86 tests, 464 assertions)
Block template classes                     OK (136 tests, 339 assertions)

phpcs is clean on every changed file.

Notes

  • Nothing here changes what block themes already render. The canvas output is identical; only the path that previously produced theme-compat markup behaves differently.
  • The deprecation notice stays where it belongs. Silencing it for block themes without giving them an alternative was raised and rejected in comments 22 and 23, as that un-deprecates files which should not be loaded at all.
  • If the shared helpers would be better public, or living somewhere other than block-template.php, I'm happy to move them.

…et_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.
Copilot AI review requested due to automatic review settings July 28, 2026 22:24
@github-actions

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props shanerounce.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes block-theme rendering when code calls get_header() / get_footer() but the active block theme does not provide header.php / footer.php, preventing WordPress from falling back to deprecated wp-includes/theme-compat/ markup and instead emitting the same document wrapper used by the block template canvas.

Changes:

  • Add shared private helpers to output the block theme document opening/closing markup and reuse them from the template canvas.
  • Add a private _wp_theme_has_template() helper so callers can detect whether the active (child/parent) theme actually ships a given template, ignoring theme-compat/ fallbacks.
  • Update get_header() / get_footer() to use the block theme document wrapper when appropriate, and add PHPUnit coverage for both block and classic themes.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/phpunit/tests/general/template.php Adds tests asserting block themes avoid theme-compat fallbacks and classic themes still trigger them (including deprecation).
src/wp-includes/template.php Introduces _wp_theme_has_template() to detect theme-provided templates without considering theme-compat/.
src/wp-includes/template-canvas.php Refactors canvas document wrapper markup to call shared helpers.
src/wp-includes/general-template.php Updates get_header() / get_footer() to open/close the block theme document when the theme provides no header/footer template.
src/wp-includes/block-template.php Adds _wp_block_theme_document_start() / _wp_block_theme_document_end() to centralize block theme document wrapper output.
Comments suppressed due to low confidence (2)

src/wp-includes/block-template.php:263

  • In _wp_block_theme_document_start(), there’s no unconditional newline after wp_body_open(). With no hooked callbacks, any following markup will start on the same line, whereas the previous template-canvas structure always had a newline after the wp_body_open() call. Adding an explicit line break keeps the document opening formatting consistent.
	echo '<body ';
	body_class();
	echo ">\n";
	wp_body_open();
}

src/wp-includes/block-template.php:277

  • In _wp_block_theme_document_end(), </body> is printed immediately after wp_footer() returns. Previously, wp_footer() was on its own line (with a guaranteed trailing newline from the file), so </body> was always on the next line even when wp_footer() printed nothing. Consider echoing a newline after wp_footer() to preserve the prior output formatting.
	echo "\n";
	wp_footer();
	echo "</body>\n";
	echo "</html>\n";

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/wp-includes/block-template.php
@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants