final class WP_Theme implements ArrayAccess {
* Whether the theme has been marked as updateable.
* @see WP_MS_Themes_List_Table
* Headers for style.css files.
* @since 5.4.0 Added `Requires at least` and `Requires PHP` headers.
private static $file_headers = array(
'ThemeURI' => 'Theme URI',
'Description' => 'Description',
'AuthorURI' => 'Author URI',
'Template' => 'Template',
'TextDomain' => 'Text Domain',
'DomainPath' => 'Domain Path',
'RequiresWP' => 'Requires at least',
'RequiresPHP' => 'Requires PHP',
private static $default_themes = array(
'classic' => 'WordPress Classic',
'default' => 'WordPress Default',
'twentyten' => 'Twenty Ten',
'twentyeleven' => 'Twenty Eleven',
'twentytwelve' => 'Twenty Twelve',
'twentythirteen' => 'Twenty Thirteen',
'twentyfourteen' => 'Twenty Fourteen',
'twentyfifteen' => 'Twenty Fifteen',
'twentysixteen' => 'Twenty Sixteen',
'twentyseventeen' => 'Twenty Seventeen',
'twentynineteen' => 'Twenty Nineteen',
'twentytwenty' => 'Twenty Twenty',
'twentytwentyone' => 'Twenty Twenty-One',
private static $tag_map = array(
'fixed-width' => 'fixed-layout',
'flexible-width' => 'fluid-layout',
* Absolute path to the theme root, usually wp-content/themes
* Header data from the theme's style.css file.
private $headers = array();
* Header data from the theme's style.css file after being sanitized.
private $headers_sanitized;
* Header name from the theme's style.css after being translated.
* Cached due to sorting functions running over the translated name.
private $name_translated;
* Errors encountered when initializing the theme.
* The directory name of the theme's files, inside the theme root.
* In the case of a child theme, this is directory name of the child theme.
* Otherwise, 'stylesheet' is the same as 'template'.
* The directory name of the theme's files, inside the theme root.
* In the case of a child theme, this is the directory name of the parent theme.
* Otherwise, 'template' is the same as 'stylesheet'.
* A reference to the parent theme, in the case of a child theme.
* URL to the theme root, usually an absolute URL to wp-content/themes
* Flag for whether the theme's textdomain is loaded.
private $textdomain_loaded;
* Stores an md5 hash of the theme root, to function as the cache key.
* Flag for whether the themes cache bucket should be persistently cached.
* Default is false. Can be set with the {@see 'wp_cache_themes_persistently'} filter.
private static $persistently_cache;
* Expiration time for the themes cache bucket.
* By default the bucket is not cached, so this value is useless.
private static $cache_expiration = 1800;
* Constructor for WP_Theme.
* @global array $wp_theme_directories
* @param string $theme_dir Directory of the theme within the theme_root.
* @param string $theme_root Theme root.
* @param WP_Theme|null $_child If this theme is a parent theme, the child may be passed for validation purposes.
public function __construct( $theme_dir, $theme_root, $_child = null ) {
global $wp_theme_directories;
// Initialize caching on first run.
if ( ! isset( self::$persistently_cache ) ) {
/** This action is documented in wp-includes/theme.php */
self::$persistently_cache = apply_filters( 'wp_cache_themes_persistently', false, 'WP_Theme' );
if ( self::$persistently_cache ) {
wp_cache_add_global_groups( 'themes' );
if ( is_int( self::$persistently_cache ) ) {
self::$cache_expiration = self::$persistently_cache;
wp_cache_add_non_persistent_groups( 'themes' );
$this->theme_root = $theme_root;
$this->stylesheet = $theme_dir;
// Correct a situation where the theme is 'some-directory/some-theme' but 'some-directory' was passed in as part of the theme root instead.
if ( ! in_array( $theme_root, (array) $wp_theme_directories, true )
&& in_array( dirname( $theme_root ), (array) $wp_theme_directories, true )
$this->stylesheet = basename( $this->theme_root ) . '/' . $this->stylesheet;
$this->theme_root = dirname( $theme_root );
$this->cache_hash = md5( $this->theme_root . '/' . $this->stylesheet );
$theme_file = $this->stylesheet . '/style.css';
$cache = $this->cache_get( 'theme' );
if ( is_array( $cache ) ) {
foreach ( array( 'errors', 'headers', 'template' ) as $key ) {
if ( isset( $cache[ $key ] ) ) {
$this->$key = $cache[ $key ];
if ( isset( $cache['theme_root_template'] ) ) {
$theme_root_template = $cache['theme_root_template'];
} elseif ( ! file_exists( $this->theme_root . '/' . $theme_file ) ) {
$this->headers['Name'] = $this->stylesheet;
if ( ! file_exists( $this->theme_root . '/' . $this->stylesheet ) ) {
$this->errors = new WP_Error(
/* translators: %s: Theme directory name. */
__( 'The theme directory "%s" does not exist.' ),
esc_html( $this->stylesheet )
$this->errors = new WP_Error( 'theme_no_stylesheet', __( 'Stylesheet is missing.' ) );
$this->template = $this->stylesheet;
'headers' => $this->headers,
'errors' => $this->errors,
'stylesheet' => $this->stylesheet,
'template' => $this->template,
if ( ! file_exists( $this->theme_root ) ) { // Don't cache this one.
$this->errors->add( 'theme_root_missing', __( 'Error: The themes directory is either empty or doesn’t exist. Please check your installation.' ) );
} elseif ( ! is_readable( $this->theme_root . '/' . $theme_file ) ) {
$this->headers['Name'] = $this->stylesheet;
$this->errors = new WP_Error( 'theme_stylesheet_not_readable', __( 'Stylesheet is not readable.' ) );
$this->template = $this->stylesheet;
'headers' => $this->headers,
'errors' => $this->errors,
'stylesheet' => $this->stylesheet,
'template' => $this->template,
$this->headers = get_file_data( $this->theme_root . '/' . $theme_file, self::$file_headers, 'theme' );
// Default themes always trump their pretenders.
// Properly identify default themes that are inside a directory within wp-content/themes.
$default_theme_slug = array_search( $this->headers['Name'], self::$default_themes, true );
if ( $default_theme_slug ) {
if ( basename( $this->stylesheet ) != $default_theme_slug ) {
$this->headers['Name'] .= '/' . $this->stylesheet;
if ( ! $this->template && $this->stylesheet === $this->headers['Template'] ) {
$this->errors = new WP_Error(
/* translators: %s: Template. */
__( 'The theme defines itself as its parent theme. Please check the %s header.' ),
'headers' => $this->headers,
'errors' => $this->errors,
'stylesheet' => $this->stylesheet,
// (If template is set from cache [and there are no errors], we know it's good.)
if ( ! $this->template ) {
$this->template = $this->headers['Template'];
if ( ! $this->template ) {
$this->template = $this->stylesheet;
if ( ! file_exists( $this->theme_root . '/' . $this->stylesheet . '/index.php' ) ) {
$error_message = sprintf(
/* translators: 1: index.php, 2: Documentation URL, 3: style.css */
__( 'Template is missing. Standalone themes need to have a %1$s template file. <a href="%2$s">Child themes</a> need to have a Template header in the %3$s stylesheet.' ),
'<code>index.php</code>',
__( 'https://developer.wordpress.org/themes/advanced-topics/child-themes/' ),
$this->errors = new WP_Error( 'theme_no_index', $error_message );
'headers' => $this->headers,
'errors' => $this->errors,
'stylesheet' => $this->stylesheet,
'template' => $this->template,
// If we got our data from cache, we can assume that 'template' is pointing to the right place.
if ( ! is_array( $cache ) && $this->template != $this->stylesheet && ! file_exists( $this->theme_root . '/' . $this->template . '/index.php' ) ) {
// If we're in a directory of themes inside /themes, look for the parent nearby.
// wp-content/themes/directory-of-themes/*
$parent_dir = dirname( $this->stylesheet );
$directories = search_theme_directories();
if ( '.' !== $parent_dir && file_exists( $this->theme_root . '/' . $parent_dir . '/' . $this->template . '/index.php' ) ) {
$this->template = $parent_dir . '/' . $this->template;
} elseif ( $directories && isset( $directories[ $this->template ] ) ) {
// Look for the template in the search_theme_directories() results, in case it is in another theme root.
// We don't look into directories of themes, just the theme root.
$theme_root_template = $directories[ $this->template ]['theme_root'];
// Parent theme is missing.
$this->errors = new WP_Error(
/* translators: %s: Theme directory name. */
__( 'The parent theme is missing. Please install the "%s" parent theme.' ),
esc_html( $this->template )
'headers' => $this->headers,
'errors' => $this->errors,
'stylesheet' => $this->stylesheet,
'template' => $this->template,
$this->parent = new WP_Theme( $this->template, $this->theme_root, $this );
// Set the parent, if we're a child theme.
if ( $this->template != $this->stylesheet ) {
// If we are a parent, then there is a problem. Only two generations allowed! Cancel things out.
if ( $_child instanceof WP_Theme && $_child->template == $this->stylesheet ) {
$_child->errors = new WP_Error(
/* translators: %s: Theme directory name. */
__( 'The "%s" theme is not a valid parent theme.' ),
esc_html( $_child->template )
'headers' => $_child->headers,
'errors' => $_child->errors,
'stylesheet' => $_child->stylesheet,
'template' => $_child->template,
// The two themes actually reference each other with the Template header.
if ( $_child->stylesheet == $this->template ) {
$this->errors = new WP_Error(
/* translators: %s: Theme directory name. */
__( 'The "%s" theme is not a valid parent theme.' ),
esc_html( $this->template )
'headers' => $this->headers,
'errors' => $this->errors,
'stylesheet' => $this->stylesheet,
'template' => $this->template,
// Set the parent. Pass the current instance so we can do the crazy checks above and assess errors.
$this->parent = new WP_Theme( $this->template, isset( $theme_root_template ) ? $theme_root_template : $this->theme_root, $this );
if ( wp_paused_themes()->get( $this->stylesheet ) && ( ! is_wp_error( $this->errors ) || ! isset( $this->errors->errors['theme_paused'] ) ) ) {
$this->errors = new WP_Error( 'theme_paused', __( 'This theme failed to load properly and was paused within the admin backend.' ) );
// We're good. If we didn't retrieve from cache, set it.
if ( ! is_array( $cache ) ) {
'headers' => $this->headers,
'errors' => $this->errors,
'stylesheet' => $this->stylesheet,
'template' => $this->template,
// If the parent theme is in another root, we'll want to cache this. Avoids an entire branch of filesystem calls above.
if ( isset( $theme_root_template ) ) {
$cache['theme_root_template'] = $theme_root_template;
$this->cache_add( 'theme', $cache );
* When converting the object to a string, the theme name is returned.
* @return string Theme name, ready for display (translated)
public function __toString() {
return (string) $this->display( 'Name' );
* __isset() magic method for properties formerly returned by current_theme_info()
* @param string $offset Property to check if set.
* @return bool Whether the given property is set.
public function __isset( $offset ) {
static $properties = array(
return in_array( $offset, $properties, true );
* __get() magic method for properties formerly returned by current_theme_info()
* @param string $offset Property to get.
* @return mixed Property value.
public function __get( $offset ) {
return $this->get( 'Name' );
return $this->get( 'Version' );
return $this->parent() ? $this->parent()->get( 'Name' ) : '';