Themes: Deprecate usage of TEMPLATEPATH and STYLESHEETPATH constants.

While generally the functions `get_template_directory()` and `get_stylesheet_directory()` were long recommended to use to get the parent or child theme directory, the `TEMPLATEPATH` and `STYLESHEETPATH` constants were still used in a few places in core, most importantly in template related logic.

The remaining usage was problematic as it prevented testability of certain key components of WordPress core.

This changeset replaces all remaining usage with the corresponding functions and effectively marks these constants as deprecated. It also adds test coverage accordingly and even unlocks some existing, previously commented out test coverage to work as expected.

Performance of the new approach has been benchmarked and shows no notable differences. Yet, given that the current theme directories are not expected to change within a regular WordPress page load, the `get_template_directory()` and `get_stylesheet_directory()` functions were amended with in-memory caching of the result, unless one of the defining values is being filtered.

Props thekt12, spacedmonkey, mukesh27, aaroncampbell, scribu, lloydbudd, cais, chipbennett, toscho, omarabid, CrazyJaco, DrewAPicture, obenland, wonderboymusic, nacin, helen, dd32, chriscct7, SergeyBiryukov, swissspidy, joemcgill, flixos90.
Fixes #18298.


git-svn-id: https://develop.svn.wordpress.org/trunk@56635 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Felix Arntz 2023-09-20 17:25:26 +00:00
parent 8c0e0b3f89
commit ec21b604e0
11 changed files with 374 additions and 55 deletions

View File

@ -1166,11 +1166,14 @@ function resume_theme( $theme, $redirect = '' ) {
* creating a fatal error.
*/
if ( ! empty( $redirect ) ) {
$stylesheet_path = get_stylesheet_directory();
$template_path = get_template_directory();
$functions_path = '';
if ( str_contains( STYLESHEETPATH, $extension ) ) {
$functions_path = STYLESHEETPATH . '/functions.php';
} elseif ( str_contains( TEMPLATEPATH, $extension ) ) {
$functions_path = TEMPLATEPATH . '/functions.php';
if ( str_contains( $stylesheet_path, $extension ) ) {
$functions_path = $stylesheet_path . '/functions.php';
} elseif ( str_contains( $template_path, $extension ) ) {
$functions_path = $template_path . '/functions.php';
}
if ( ! empty( $functions_path ) ) {

View File

@ -1381,7 +1381,7 @@ function wp_comment_form_unfiltered_html_nonce() {
* and the post ID respectively.
*
* The `$file` path is passed through a filter hook called {@see 'comments_template'},
* which includes the TEMPLATEPATH and $file combined. Tries the $filtered path
* which includes the template directory and $file combined. Tries the $filtered path
* first and if it fails it will require the default comment template from the
* default theme. If either does not exist, then the WordPress process will be
* halted. It is advised for that reason, that the default theme is not deleted.
@ -1600,7 +1600,10 @@ function comments_template( $file = '/comments.php', $separate_comments = false
define( 'COMMENTS_TEMPLATE', true );
}
$theme_template = STYLESHEETPATH . $file;
$stylesheet_path = get_stylesheet_directory();
$template_path = get_template_directory();
$theme_template = $stylesheet_path . $file;
/**
* Filters the path to the theme template file used for the comments template.
@ -1613,8 +1616,8 @@ function comments_template( $file = '/comments.php', $separate_comments = false
if ( file_exists( $include ) ) {
require $include;
} elseif ( file_exists( TEMPLATEPATH . $file ) ) {
require TEMPLATEPATH . $file;
} elseif ( file_exists( $template_path . $file ) ) {
require $template_path . $file;
} else { // Backward compat code will be removed in a future release.
require ABSPATH . WPINC . '/theme-compat/comments.php';
}

View File

@ -407,6 +407,8 @@ function wp_templating_constants() {
* Filesystem path to the current active template directory.
*
* @since 1.5.0
* @deprecated 6.4.0 Use get_template_directory() instead.
* @see get_template_directory()
*/
define( 'TEMPLATEPATH', get_template_directory() );
@ -414,6 +416,8 @@ function wp_templating_constants() {
* Filesystem path to the current active template stylesheet directory.
*
* @since 2.1.0
* @deprecated 6.4.0 Use get_stylesheet_directory() instead.
* @see get_stylesheet_directory()
*/
define( 'STYLESHEETPATH', get_stylesheet_directory() );

View File

@ -1049,11 +1049,14 @@ function wp_get_active_and_valid_themes() {
return $themes;
}
if ( TEMPLATEPATH !== STYLESHEETPATH ) {
$themes[] = STYLESHEETPATH;
$stylesheet_path = get_stylesheet_directory();
$template_path = get_template_directory();
if ( $template_path !== $stylesheet_path ) {
$themes[] = $stylesheet_path;
}
$themes[] = TEMPLATEPATH;
$themes[] = $template_path;
/*
* Remove themes from the list of active themes when we're on an endpoint

View File

@ -684,8 +684,9 @@ function get_attachment_template() {
/**
* Retrieves the name of the highest priority template file that exists.
*
* Searches in the STYLESHEETPATH before TEMPLATEPATH and wp-includes/theme-compat
* so that themes which inherit from a parent theme can just overload one file.
* Searches in the stylesheet directory before the template directory and
* wp-includes/theme-compat so that themes which inherit from a parent theme
* can just overload one file.
*
* @since 2.7.0
* @since 5.5.0 The `$args` parameter was added.
@ -699,16 +700,20 @@ function get_attachment_template() {
* @return string The template filename if one is located.
*/
function locate_template( $template_names, $load = false, $load_once = true, $args = array() ) {
$stylesheet_path = get_stylesheet_directory();
$template_path = get_template_directory();
$is_child_theme = $stylesheet_path !== $template_path;
$located = '';
foreach ( (array) $template_names as $template_name ) {
if ( ! $template_name ) {
continue;
}
if ( file_exists( STYLESHEETPATH . '/' . $template_name ) ) {
$located = STYLESHEETPATH . '/' . $template_name;
if ( file_exists( $stylesheet_path . '/' . $template_name ) ) {
$located = $stylesheet_path . '/' . $template_name;
break;
} elseif ( is_child_theme() && file_exists( TEMPLATEPATH . '/' . $template_name ) ) {
$located = TEMPLATEPATH . '/' . $template_name;
} elseif ( $is_child_theme && file_exists( $template_path . '/' . $template_name ) ) {
$located = $template_path . '/' . $template_name;
break;
} elseif ( file_exists( ABSPATH . WPINC . '/theme-compat/' . $template_name ) ) {
$located = ABSPATH . WPINC . '/theme-compat/' . $template_name;

View File

@ -157,7 +157,7 @@ function wp_clean_themes_cache( $clear_update_cache = true ) {
* @return bool True if a child theme is in use, false otherwise.
*/
function is_child_theme() {
return ( TEMPLATEPATH !== STYLESHEETPATH );
return get_template_directory() !== get_stylesheet_directory();
}
/**
@ -187,24 +187,40 @@ function get_stylesheet() {
* Retrieves stylesheet directory path for the active theme.
*
* @since 1.5.0
* @since 6.4.0 Memoizes filter execution so that it only runs once for the current theme.
*
* @global string $wp_stylesheet_path Current theme stylesheet directory path.
*
* @return string Path to active theme's stylesheet directory.
*/
function get_stylesheet_directory() {
$stylesheet = get_stylesheet();
$theme_root = get_theme_root( $stylesheet );
$stylesheet_dir = "$theme_root/$stylesheet";
global $wp_stylesheet_path;
/**
* Filters the stylesheet directory path for the active theme.
*
* @since 1.5.0
*
* @param string $stylesheet_dir Absolute path to the active theme.
* @param string $stylesheet Directory name of the active theme.
* @param string $theme_root Absolute path to themes directory.
*/
return apply_filters( 'stylesheet_directory', $stylesheet_dir, $stylesheet, $theme_root );
if ( null === $wp_stylesheet_path ) {
$stylesheet = get_stylesheet();
$theme_root = get_theme_root( $stylesheet );
$stylesheet_dir = "$theme_root/$stylesheet";
/**
* Filters the stylesheet directory path for the active theme.
*
* @since 1.5.0
*
* @param string $stylesheet_dir Absolute path to the active theme.
* @param string $stylesheet Directory name of the active theme.
* @param string $theme_root Absolute path to themes directory.
*/
$stylesheet_dir = apply_filters( 'stylesheet_directory', $stylesheet_dir, $stylesheet, $theme_root );
// If there are filter callbacks, force the logic to execute on every call.
if ( has_filter( 'stylesheet' ) || has_filter( 'theme_root' ) || has_filter( 'stylesheet_directory' ) ) {
return $stylesheet_dir;
}
$wp_stylesheet_path = $stylesheet_dir;
}
return $wp_stylesheet_path;
}
/**
@ -321,24 +337,40 @@ function get_template() {
* Retrieves template directory path for the active theme.
*
* @since 1.5.0
* @since 6.4.0 Memoizes filter execution so that it only runs once for the current theme.
*
* @global string $wp_template_path Current theme template directory path.
*
* @return string Path to active theme's template directory.
*/
function get_template_directory() {
$template = get_template();
$theme_root = get_theme_root( $template );
$template_dir = "$theme_root/$template";
global $wp_template_path;
/**
* Filters the active theme directory path.
*
* @since 1.5.0
*
* @param string $template_dir The path of the active theme directory.
* @param string $template Directory name of the active theme.
* @param string $theme_root Absolute path to the themes directory.
*/
return apply_filters( 'template_directory', $template_dir, $template, $theme_root );
if ( null === $wp_template_path ) {
$template = get_template();
$theme_root = get_theme_root( $template );
$template_dir = "$theme_root/$template";
/**
* Filters the active theme directory path.
*
* @since 1.5.0
*
* @param string $template_dir The path of the active theme directory.
* @param string $template Directory name of the active theme.
* @param string $theme_root Absolute path to the themes directory.
*/
$template_dir = apply_filters( 'template_directory', $template_dir, $template, $theme_root );
// If there are filter callbacks, force the logic to execute on every call.
if ( has_filter( 'template' ) || has_filter( 'theme_root' ) || has_filter( 'template_directory' ) ) {
return $template_dir;
}
$wp_template_path = $template_dir;
}
return $wp_template_path;
}
/**
@ -744,11 +776,13 @@ function locale_stylesheet() {
* @global WP_Customize_Manager $wp_customize
* @global array $sidebars_widgets
* @global array $wp_registered_sidebars
* @global string $wp_stylesheet_path
* @global string $wp_template_path
*
* @param string $stylesheet Stylesheet name.
*/
function switch_theme( $stylesheet ) {
global $wp_theme_directories, $wp_customize, $sidebars_widgets, $wp_registered_sidebars;
global $wp_theme_directories, $wp_customize, $sidebars_widgets, $wp_registered_sidebars, $wp_stylesheet_path, $wp_template_path;
$requirements = validate_theme_requirements( $stylesheet );
if ( is_wp_error( $requirements ) ) {
@ -832,6 +866,13 @@ function switch_theme( $stylesheet ) {
update_option( 'theme_switched', $old_theme->get_stylesheet() );
/*
* Reset globals to force refresh the next time these directories are
* accessed via `get_stylesheet_directory()` / `get_template_directory()`.
*/
$wp_stylesheet_path = null;
$wp_template_path = null;
/**
* Fires after the theme is switched.
*

View File

@ -9,6 +9,14 @@
*/
class Tests_Comment_CommentsTemplate extends WP_UnitTestCase {
/**
* Performs setup tasks for every test.
*/
public function set_up() {
parent::set_up();
switch_theme( 'default' );
}
/**
* @ticket 8071
*/

View File

@ -6,6 +6,15 @@
* @covers ::wp_list_comments
*/
class Tests_Comment_WpListComments extends WP_UnitTestCase {
/**
* Performs setup tasks for every test.
*/
public function set_up() {
parent::set_up();
switch_theme( 'default' );
}
/**
* @ticket 35175
*/

View File

@ -10,6 +10,7 @@
require_once ABSPATH . 'wp-admin/includes/class-wp-site-icon.php';
class Tests_General_Template extends WP_UnitTestCase {
protected $wp_site_icon;
public $site_icon_id;
public $site_icon_url;
@ -41,6 +42,7 @@ class Tests_General_Template extends WP_UnitTestCase {
public function set_up() {
parent::set_up();
switch_theme( 'default' );
$this->wp_site_icon = new WP_Site_Icon();
}

View File

@ -629,6 +629,44 @@ class Tests_Template extends WP_UnitTestCase {
);
}
/**
* Tests that `locate_template()` uses the current theme even after switching the theme.
*
* @ticket 18298
*
* @covers ::locate_template
*/
public function test_locate_template_uses_current_theme() {
$themes = wp_get_themes();
// Look for parent themes with an index.php template.
$relevant_themes = array();
foreach ( $themes as $theme ) {
if ( $theme->get_stylesheet() !== $theme->get_template() ) {
continue;
}
$php_templates = $theme['Template Files'];
if ( ! isset( $php_templates['index.php'] ) ) {
continue;
}
$relevant_themes[] = $theme;
}
if ( count( $relevant_themes ) < 2 ) {
$this->markTestSkipped( 'Test requires at least two parent themes with an index.php template.' );
}
$template_names = array( 'index.php' );
$old_theme = $relevant_themes[0];
$new_theme = $relevant_themes[1];
switch_theme( $old_theme->get_stylesheet() );
$this->assertSame( $old_theme->get_stylesheet_directory() . '/index.php', locate_template( $template_names ), 'Incorrect index template found in initial theme.' );
switch_theme( $new_theme->get_stylesheet() );
$this->assertSame( $new_theme->get_stylesheet_directory() . '/index.php', locate_template( $template_names ), 'Incorrect index template found in theme after switch.' );
}
public function assertTemplateHierarchy( $url, array $expected, $message = '' ) {
$this->go_to( $url );
$hierarchy = $this->get_template_hierarchy();

View File

@ -36,8 +36,9 @@ class Tests_Theme extends WP_UnitTestCase {
parent::set_up();
// Sets up the `wp-content/themes/` directory to ensure consistency when running tests.
$this->orig_theme_dir = $wp_theme_directories;
$wp_theme_directories = array( WP_CONTENT_DIR . '/themes' );
$wp_theme_directories = array( WP_CONTENT_DIR . '/themes', realpath( DIR_TESTDATA . '/themedir1' ) );
add_filter( 'extra_theme_headers', array( $this, 'theme_data_extra_headers' ) );
wp_clean_themes_cache();
@ -282,6 +283,11 @@ class Tests_Theme extends WP_UnitTestCase {
for ( $i = 0; $i < 3; $i++ ) {
foreach ( $themes as $name => $theme ) {
// Skip invalid theme directory names (such as `block_theme-[0.4.0]`).
if ( ! preg_match( '/^[a-z0-9-]+$/', $theme['Stylesheet'] ) ) {
continue;
}
// Switch to this theme.
if ( 2 === $i ) {
switch_theme( $theme['Template'], $theme['Stylesheet'] );
@ -289,16 +295,16 @@ class Tests_Theme extends WP_UnitTestCase {
switch_theme( $theme['Stylesheet'] );
}
$this->assertSame( $name, get_current_theme() );
$this->assertSame( $theme['Name'], get_current_theme() );
// Make sure the various get_* functions return the correct values.
$this->assertSame( $theme['Template'], get_template() );
$this->assertSame( $theme['Stylesheet'], get_stylesheet() );
$root_fs = get_theme_root();
$root_fs = $theme->get_theme_root();
$this->assertTrue( is_dir( $root_fs ) );
$root_uri = get_theme_root_uri();
$root_uri = $theme->get_theme_root_uri();
$this->assertNotEmpty( $root_uri );
$this->assertSame( $root_fs . '/' . get_stylesheet(), get_stylesheet_directory() );
@ -309,19 +315,38 @@ class Tests_Theme extends WP_UnitTestCase {
$this->assertSame( $root_fs . '/' . get_template(), get_template_directory() );
$this->assertSame( $root_uri . '/' . get_template(), get_template_directory_uri() );
// get_query_template()
// Skip block themes for get_query_template() tests since this test is focused on classic templates.
if ( wp_is_block_theme() && current_theme_supports( 'block-templates' ) ) {
continue;
}
// Template file that doesn't exist.
$this->assertSame( '', get_query_template( 'nonexistant' ) );
// Template files that do exist.
/*
foreach ( $theme['Template Files'] as $path ) {
$file = basename($path, '.php');
FIXME: untestable because get_query_template() uses TEMPLATEPATH.
$this->assertSame('', get_query_template($file));
$file = basename( $path, '.php' );
// The functions.php file is not a template.
if ( 'functions' === $file ) {
continue;
}
// Underscores are not supported by `locate_template()`.
if ( 'taxonomy-post_format' === $file ) {
$file = 'taxonomy';
}
$child_theme_file = get_stylesheet_directory() . '/' . $file . '.php';
$parent_theme_file = get_template_directory() . '/' . $file . '.php';
if ( file_exists( $child_theme_file ) ) {
$this->assertSame( $child_theme_file, get_query_template( $file ) );
} elseif ( file_exists( $parent_theme_file ) ) {
$this->assertSame( $parent_theme_file, get_query_template( $file ) );
} else {
$this->assertSame( '', get_query_template( $file ) );
}
}
*/
// These are kind of tautologies but at least exercise the code.
$this->assertSame( get_404_template(), get_query_template( '404' ) );
@ -854,6 +879,184 @@ class Tests_Theme extends WP_UnitTestCase {
);
}
/**
* Tests that a theme in the custom test data theme directory is recognized.
*
* @ticket 18298
*/
public function test_theme_in_custom_theme_dir_is_valid() {
switch_theme( 'block-theme' );
$this->assertTrue( wp_get_theme()->exists() );
}
/**
* Tests that `is_child_theme()` returns true for child theme.
*
* @ticket 18298
*
* @covers ::is_child_theme
*/
public function test_is_child_theme_true() {
switch_theme( 'block-theme-child' );
$this->assertTrue( is_child_theme() );
}
/**
* Tests that `is_child_theme()` returns false for parent theme.
*
* @ticket 18298
*
* @covers ::is_child_theme
*/
public function test_is_child_theme_false() {
switch_theme( 'block-theme' );
$this->assertFalse( is_child_theme() );
}
/**
* Tests that the child theme directory is correctly detected.
*
* @ticket 18298
*
* @covers ::get_stylesheet_directory
*/
public function test_get_stylesheet_directory() {
switch_theme( 'block-theme-child' );
$this->assertSame( realpath( DIR_TESTDATA ) . '/themedir1/block-theme-child', get_stylesheet_directory() );
}
/**
* Tests that the parent theme directory is correctly detected.
*
* @ticket 18298
*
* @covers ::get_template_directory
*/
public function test_get_template_directory() {
switch_theme( 'block-theme-child' );
$this->assertSame( realpath( DIR_TESTDATA ) . '/themedir1/block-theme', get_template_directory() );
}
/**
* Tests that get_stylesheet_directory() behaves correctly with filters.
*
* @ticket 18298
* @dataProvider data_get_stylesheet_directory_with_filter
*
* @covers ::get_stylesheet_directory
*
* @param string $theme Theme slug / directory name.
* @param string $hook_name Filter hook name.
* @param callable $callback Filter callback.
* @param string $expected Expected stylesheet directory with the filter active.
*/
public function test_get_stylesheet_directory_with_filter( $theme, $hook_name, $callback, $expected ) {
switch_theme( $theme );
// Add filter, then call get_stylesheet_directory() to compute value.
add_filter( $hook_name, $callback );
$this->assertSame( $expected, get_stylesheet_directory(), 'Stylesheet directory returned incorrect result not considering filters' );
// Remove filter again, then ensure result is recalculated and not the same as before.
remove_filter( $hook_name, $callback );
$this->assertNotSame( $expected, get_stylesheet_directory(), 'Stylesheet directory returned previous value even though filters were removed' );
}
/**
* Data provider for `test_get_stylesheet_directory_with_filter()`.
*
* @return array[]
*/
public function data_get_stylesheet_directory_with_filter() {
return array(
'with stylesheet_directory filter' => array(
'block-theme',
'stylesheet_directory',
static function ( $dir ) {
return str_replace( realpath( DIR_TESTDATA ) . '/themedir1', '/fantasy-dir', $dir );
},
'/fantasy-dir/block-theme',
),
'with theme_root filter' => array(
'block-theme',
'theme_root',
static function () {
return '/fantasy-dir';
},
'/fantasy-dir/block-theme',
),
'with stylesheet filter' => array(
'block-theme',
'stylesheet',
static function () {
return 'another-theme';
},
// Because the theme does not exist, `get_theme_root()` returns the default themes directory.
WP_CONTENT_DIR . '/themes/another-theme',
),
);
}
/**
* Tests that get_template_directory() behaves correctly with filters.
*
* @ticket 18298
* @dataProvider data_get_template_directory_with_filter
*
* @covers ::get_template_directory
*
* @param string $theme Theme slug / directory name.
* @param string $hook_name Filter hook name.
* @param callable $callback Filter callback.
* @param string $expected Expected template directory with the filter active.
*/
public function test_get_template_directory_with_filter( $theme, $hook_name, $callback, $expected ) {
switch_theme( $theme );
// Add filter, then call get_template_directory() to compute value.
add_filter( $hook_name, $callback );
$this->assertSame( $expected, get_template_directory(), 'Template directory returned incorrect result not considering filters' );
// Remove filter again, then ensure result is recalculated and not the same as before.
remove_filter( $hook_name, $callback );
$this->assertNotSame( $expected, get_template_directory(), 'Template directory returned previous value even though filters were removed' );
}
/**
* Data provider for `test_get_template_directory_with_filter()`.
*
* @return array[]
*/
public function data_get_template_directory_with_filter() {
return array(
'with template_directory filter' => array(
'block-theme',
'template_directory',
static function ( $dir ) {
return str_replace( realpath( DIR_TESTDATA ) . '/themedir1', '/fantasy-dir', $dir );
},
'/fantasy-dir/block-theme',
),
'with theme_root filter' => array(
'block-theme',
'theme_root',
static function () {
return '/fantasy-dir';
},
'/fantasy-dir/block-theme',
),
'with template filter' => array(
'block-theme',
'template',
static function () {
return 'another-theme';
},
// Because the theme does not exist, `get_theme_root()` returns the default themes directory.
WP_CONTENT_DIR . '/themes/another-theme',
),
);
}
/**
* Helper function to ensure that a block theme is available and active.
*/