: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
* @param string|array $value Value to mark up. An array for Tags header, string otherwise.
* @param string $translate Whether the header has been translated.
* @return string Value, marked up.
private function markup_header( $header, $value, $translate ) {
$value = esc_html( $this->get_stylesheet() );
$value = wptexturize( $value );
if ( $this->get( 'AuthorURI' ) ) {
$value = sprintf( '<a href="%1$s">%2$s</a>', $this->display( 'AuthorURI', true, $translate ), $value );
$value = __( 'Anonymous' );
if ( ! isset( $comma ) ) {
$comma = wp_get_list_item_separator();
$value = implode( $comma, $value );
$value = esc_url( $value );
* Translates a theme header.
* @param string $header Theme header. Name, Description, Author, Version, ThemeURI, AuthorURI, Status, Tags.
* @param string|array $value Value to translate. An array for Tags header, string otherwise.
* @return string|array Translated value. An array for Tags header, string otherwise.
private function translate_header( $header, $value ) {
// Cached for sorting reasons.
if ( isset( $this->name_translated ) ) {
return $this->name_translated;
// phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain
$this->name_translated = translate( $value, $this->get( 'TextDomain' ) );
return $this->name_translated;
if ( empty( $value ) || ! function_exists( 'get_theme_feature_list' ) ) {
if ( ! isset( $tags_list ) ) {
// As of 4.6, deprecated tags which are only used to provide translation for older themes.
'black' => __( 'Black' ),
'brown' => __( 'Brown' ),
'green' => __( 'Green' ),
'orange' => __( 'Orange' ),
'purple' => __( 'Purple' ),
'silver' => __( 'Silver' ),
'white' => __( 'White' ),
'yellow' => __( 'Yellow' ),
'dark' => _x( 'Dark', 'color scheme' ),
'light' => _x( 'Light', 'color scheme' ),
'fixed-layout' => __( 'Fixed Layout' ),
'fluid-layout' => __( 'Fluid Layout' ),
'responsive-layout' => __( 'Responsive Layout' ),
'blavatar' => __( 'Blavatar' ),
'photoblogging' => __( 'Photoblogging' ),
'seasonal' => __( 'Seasonal' ),
$feature_list = get_theme_feature_list( false ); // No API.
foreach ( $feature_list as $tags ) {
foreach ( $value as &$tag ) {
if ( isset( $tags_list[ $tag ] ) ) {
$tag = $tags_list[ $tag ];
} elseif ( isset( self::$tag_map[ $tag ] ) ) {
$tag = $tags_list[ self::$tag_map[ $tag ] ];
// phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain
$value = translate( $value, $this->get( 'TextDomain' ) );
* Returns the directory name of the theme's "stylesheet" files, inside the theme root.
* In the case of a child theme, this is directory name of the child theme.
* Otherwise, get_stylesheet() is the same as get_template().
* @return string Stylesheet
public function get_stylesheet() {
return $this->stylesheet;
* Returns the directory name of the theme's "template" files, inside the theme root.
* In the case of a child theme, this is the directory name of the parent theme.
* Otherwise, the get_template() is the same as get_stylesheet().
* @return string Template
public function get_template() {
* Returns the absolute path to the directory of a theme's "stylesheet" files.
* In the case of a child theme, this is the absolute path to the directory
* of the child theme's files.
* @return string Absolute path of the stylesheet directory.
public function get_stylesheet_directory() {
if ( $this->errors() && in_array( 'theme_root_missing', $this->errors()->get_error_codes(), true ) ) {
return $this->theme_root . '/' . $this->stylesheet;
* Returns the absolute path to the directory of a theme's "template" files.
* In the case of a child theme, this is the absolute path to the directory
* of the parent theme's files.
* @return string Absolute path of the template directory.
public function get_template_directory() {
$theme_root = $this->parent()->theme_root;
$theme_root = $this->theme_root;
return $theme_root . '/' . $this->template;
* Returns the URL to the directory of a theme's "stylesheet" files.
* In the case of a child theme, this is the URL to the directory of the
* @return string URL to the stylesheet directory.
public function get_stylesheet_directory_uri() {
return $this->get_theme_root_uri() . '/' . str_replace( '%2F', '/', rawurlencode( $this->stylesheet ) );
* Returns the URL to the directory of a theme's "template" files.
* In the case of a child theme, this is the URL to the directory of the
* @return string URL to the template directory.
public function get_template_directory_uri() {
$theme_root_uri = $this->parent()->get_theme_root_uri();
$theme_root_uri = $this->get_theme_root_uri();
return $theme_root_uri . '/' . str_replace( '%2F', '/', rawurlencode( $this->template ) );
* Returns the absolute path to the directory of the theme root.
* This is typically the absolute path to wp-content/themes.
* @return string Theme root.
public function get_theme_root() {
return $this->theme_root;
* Returns the URL to the directory of the theme root.
* This is typically the absolute URL to wp-content/themes. This forms the basis
* for all other URLs returned by WP_Theme, so we pass it to the public function
* get_theme_root_uri() and allow it to run the {@see 'theme_root_uri'} filter.
* @return string Theme root URI.
public function get_theme_root_uri() {
if ( ! isset( $this->theme_root_uri ) ) {
$this->theme_root_uri = get_theme_root_uri( $this->stylesheet, $this->theme_root );
return $this->theme_root_uri;
* Returns the main screenshot file for the theme.
* The main screenshot is called screenshot.png. gif and jpg extensions are also allowed.
* Screenshots for a theme must be in the stylesheet directory. (In the case of child
* themes, parent theme screenshots are not inherited.)
* @param string $uri Type of URL to return, either 'relative' or an absolute URI. Defaults to absolute URI.
* @return string|false Screenshot file. False if the theme does not have a screenshot.
public function get_screenshot( $uri = 'uri' ) {
$screenshot = $this->cache_get( 'screenshot' );
if ( 'relative' === $uri ) {
return $this->get_stylesheet_directory_uri() . '/' . $screenshot;
} elseif ( 0 === $screenshot ) {
foreach ( array( 'png', 'gif', 'jpg', 'jpeg', 'webp', 'avif' ) as $ext ) {
if ( file_exists( $this->get_stylesheet_directory() . "/screenshot.$ext" ) ) {
$this->cache_add( 'screenshot', 'screenshot.' . $ext );
if ( 'relative' === $uri ) {
return 'screenshot.' . $ext;
return $this->get_stylesheet_directory_uri() . '/' . 'screenshot.' . $ext;
$this->cache_add( 'screenshot', 0 );
* Returns files in the theme's directory.
* @param string[]|string $type Optional. Array of extensions to find, string of a single extension,
* or null for all extensions. Default null.
* @param int $depth Optional. How deep to search for files. Defaults to a flat scan (0 depth).
* @param bool $search_parent Optional. Whether to return parent files. Default false.
* @return string[] Array of files, keyed by the path to the file relative to the theme's directory, with the values
public function get_files( $type = null, $depth = 0, $search_parent = false ) {
$files = (array) self::scandir( $this->get_stylesheet_directory(), $type, $depth );
if ( $search_parent && $this->parent() ) {
$files += (array) self::scandir( $this->get_template_directory(), $type, $depth );
return array_filter( $files );
* Returns the theme's post templates.
* @since 5.8.0 Include block templates.
* @return array[] Array of page template arrays, keyed by post type and filename,
* with the value of the translated header name.
public function get_post_templates() {
// If you screw up your active theme and we invalidate your parent, most things still work. Let it slide.
if ( $this->errors() && $this->errors()->get_error_codes() !== array( 'theme_parent_invalid' ) ) {
$post_templates = $this->cache_get( 'post_templates' );
if ( ! is_array( $post_templates ) ) {
$post_templates = array();
$files = (array) $this->get_files( 'php', 1, true );
foreach ( $files as $file => $full_path ) {
if ( ! preg_match( '|Template Name:(.*)$|mi', file_get_contents( $full_path ), $header ) ) {
$types = array( 'page' );
if ( preg_match( '|Template Post Type:(.*)$|mi', file_get_contents( $full_path ), $type ) ) {
$types = explode( ',', _cleanup_header_comment( $type[1] ) );
foreach ( $types as $type ) {
$type = sanitize_key( $type );
if ( ! isset( $post_templates[ $type ] ) ) {
$post_templates[ $type ] = array();
$post_templates[ $type ][ $file ] = _cleanup_header_comment( $header[1] );
$this->cache_add( 'post_templates', $post_templates );
if ( current_theme_supports( 'block-templates' ) ) {
$block_templates = get_block_templates( array(), 'wp_template' );
foreach ( get_post_types( array( 'public' => true ) ) as $type ) {
foreach ( $block_templates as $block_template ) {
if ( ! $block_template->is_custom ) {
if ( isset( $block_template->post_types ) && ! in_array( $type, $block_template->post_types, true ) ) {
$post_templates[ $type ][ $block_template->slug ] = $block_template->title;
if ( $this->load_textdomain() ) {
foreach ( $post_templates as &$post_type ) {
foreach ( $post_type as &$post_template ) {
$post_template = $this->translate_header( 'Template Name', $post_template );
* Returns the theme's post templates for a given post type.
* @since 4.7.0 Added the `$post_type` parameter.
* @param WP_Post|null $post Optional. The post being edited, provided for context.
* @param string $post_type Optional. Post type to get the templates for. Default 'page'.
* If a post is provided, its post type is used.
* @return string[] Array of template header names keyed by the template file name.
public function get_page_templates( $post = null, $post_type = 'page' ) {
$post_type = get_post_type( $post );
$post_templates = $this->get_post_templates();
$post_templates = isset( $post_templates[ $post_type ] ) ? $post_templates[ $post_type ] : array();
* Filters list of page templates for a theme.
* @param string[] $post_templates Array of template header names keyed by the template file name.
* @param WP_Theme $theme The theme object.
* @param WP_Post|null $post The post being edited, provided for context, or null.
* @param string $post_type Post type to get the templates for.
$post_templates = (array) apply_filters( 'theme_templates', $post_templates, $this, $post, $post_type );
* Filters list of page templates for a theme.
* The dynamic portion of the hook name, `$post_type`, refers to the post type.
* Possible hook names include:
* - `theme_post_templates`
* - `theme_page_templates`
* - `theme_attachment_templates`
* @since 4.4.0 Converted to allow complete control over the `$page_templates` array.
* @since 4.7.0 Added the `$post_type` parameter.
* @param string[] $post_templates Array of template header names keyed by the template file name.
* @param WP_Theme $theme The theme object.
* @param WP_Post|null $post The post being edited, provided for context, or null.
* @param string $post_type Post type to get the templates for.
$post_templates = (array) apply_filters( "theme_{$post_type}_templates", $post_templates, $this, $post, $post_type );
* Scans a directory for files of a certain extension.
* @param string $path Absolute path to search.
* @param array|string|null $extensions Optional. Array of extensions to find, string of a single extension,
* or null for all extensions. Default null.
* @param int $depth Optional. How many levels deep to search for files. Accepts 0, 1+, or
* -1 (infinite depth). Default 0.
* @param string $relative_path Optional. The basename of the absolute path. Used to control the
* returned path for the found files, particularly when this function
* recurses to lower depths. Default empty.
* @return string[]|false Array of files, keyed by the path to the file relative to the `$path` directory prepended
* with `$relative_path`, with the values being absolute paths. False otherwise.
private static function scandir( $path, $extensions = null, $depth = 0, $relative_path = '' ) {
if ( ! is_dir( $path ) ) {
$extensions = (array) $extensions;
$_extensions = implode( '|', $extensions );
$relative_path = trailingslashit( $relative_path );
if ( '/' === $relative_path ) {
$results = scandir( $path );
* Filters the array of excluded directories and files while scanning theme folder.
* @param string[] $exclusions Array of excluded directories and files.
$exclusions = (array) apply_filters( 'theme_scandir_exclusions', array( 'CVS', 'node_modules', 'vendor', 'bower_components' ) );
foreach ( $results as $result ) {
if ( '.' === $result[0] || in_array( $result, $exclusions, true ) ) {
if ( is_dir( $path . '/' . $result ) ) {
$found = self::scandir( $path . '/' . $result, $extensions, $depth - 1, $relative_path . $result );
$files = array_merge_recursive( $files, $found );
} elseif ( ! $extensions || preg_match( '~\.(' . $_extensions . ')$~', $result ) ) {
$files[ $relative_path . $result ] = $path . '/' . $result;
* Loads the theme's textdomain.