Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/wp-includes/block-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,50 @@ function _block_template_render_title_tag() {
echo '<title>' . wp_get_document_title() . '</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 '<!DOCTYPE html>' . "\n";
echo '<html ';
language_attributes();
echo ">\n";
echo "<head>\n";
echo "\t" . '<meta charset="';
bloginfo( 'charset' );
echo '" />' . "\n";
echo "\t";
wp_head();
echo "</head>\n";
echo "\n";
Comment thread
AceMedia marked this conversation as resolved.
echo '<body ';
body_class();
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 "</body>\n";
echo "</html>\n";
}

/**
* Returns the markup for the current template.
*
Expand Down
19 changes: 19 additions & 0 deletions src/wp-includes/general-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
16 changes: 4 additions & 12 deletions src/wp-includes/template-canvas.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,10 @@
* This needs to run before <head> so that blocks can add scripts and styles in wp_head().
*/
$template_html = get_the_block_template_html();
?><!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo( 'charset' ); ?>" />
<?php wp_head(); ?>
</head>

<body <?php body_class(); ?>>
<?php wp_body_open(); ?>
_wp_block_theme_document_start();
?>

<?php echo $template_html; ?>

<?php wp_footer(); ?>
</body>
</html>
<?php
_wp_block_theme_document_end();
34 changes: 34 additions & 0 deletions src/wp-includes/template.php
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,40 @@ function locate_template( $template_names, $load = false, $load_once = true, $ar
return $located;
}

/**
* Determines whether the active theme provides one of the given template files.
*
* Unlike locate_template(), this ignores the deprecated fallbacks in wp-includes/theme-compat/,
* so a caller can tell a template the theme actually ships from one of those.
*
* @since 7.1.0
* @access private
*
* @param string[] $template_names Template file names to look for, in order of priority.
* @return bool Whether the theme, or its parent, provides one of the templates.
*/
function _wp_theme_has_template( $template_names ) {
$stylesheet_path = get_stylesheet_directory();
$template_path = get_template_directory();
$is_child_theme = $stylesheet_path !== $template_path;

foreach ( (array) $template_names as $template_name ) {
if ( ! $template_name ) {
continue;
}

if ( file_exists( $stylesheet_path . '/' . $template_name ) ) {
return true;
}

if ( $is_child_theme && file_exists( $template_path . '/' . $template_name ) ) {
return true;
}
}

return false;
}

/**
* Requires the template file with WordPress environment.
*
Expand Down
77 changes: 77 additions & 0 deletions tests/phpunit/tests/general/template.php
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,83 @@ public function test_get_footer_returns_nothing_on_success() {
$this->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( '<!DOCTYPE html>', $output, 'The document should be opened.' );
$this->assertStringContainsString( '<body', $output, 'The body element should be opened.' );
$this->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( '</body>', $output, 'The body element should be closed.' );
$this->assertStringContainsString( '</html>', $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
*
Expand Down
Loading