--- a/wp/wp-includes/class-wp-theme.php Thu Sep 29 08:06:27 2022 +0200
+++ b/wp/wp-includes/class-wp-theme.php Fri Sep 05 18:40:08 2025 +0200
@@ -6,6 +6,7 @@
* @subpackage Theme
* @since 3.4.0
*/
+#[AllowDynamicProperties]
final class WP_Theme implements ArrayAccess {
/**
@@ -23,6 +24,7 @@
*
* @since 3.4.0
* @since 5.4.0 Added `Requires at least` and `Requires PHP` headers.
+ * @since 6.1.0 Added `Update URI` header.
* @var string[]
*/
private static $file_headers = array(
@@ -39,6 +41,7 @@
'DomainPath' => 'Domain Path',
'RequiresWP' => 'Requires at least',
'RequiresPHP' => 'Requires PHP',
+ 'UpdateURI' => 'Update URI',
);
/**
@@ -55,23 +58,27 @@
* @since 5.3.0 Added the Twenty Twenty theme.
* @since 5.6.0 Added the Twenty Twenty-One theme.
* @since 5.9.0 Added the Twenty Twenty-Two theme.
+ * @since 6.1.0 Added the Twenty Twenty-Three theme.
+ * @since 6.4.0 Added the Twenty Twenty-Four theme.
* @var string[]
*/
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',
- 'twentytwentytwo' => 'Twenty Twenty-Two',
+ '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',
+ 'twentytwentytwo' => 'Twenty Twenty-Two',
+ 'twentytwentythree' => 'Twenty Twenty-Three',
+ 'twentytwentyfour' => 'Twenty Twenty-Four',
);
/**
@@ -110,6 +117,14 @@
private $headers_sanitized;
/**
+ * Is this theme a block theme.
+ *
+ * @since 6.2.0
+ * @var bool
+ */
+ private $block_theme;
+
+ /**
* Header name from the theme's style.css after being translated.
*
* Cached due to sorting functions running over the translated name.
@@ -182,6 +197,25 @@
private $cache_hash;
/**
+ * Block template folders.
+ *
+ * @since 6.4.0
+ * @var string[]
+ */
+ private $block_template_folders;
+
+ /**
+ * Default values for template folders.
+ *
+ * @since 6.4.0
+ * @var string[]
+ */
+ private $default_template_folders = array(
+ 'wp_template' => 'templates',
+ 'wp_template_part' => 'parts',
+ );
+
+ /**
* 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.
@@ -229,6 +263,9 @@
}
}
+ // Handle a numeric theme directory as a string.
+ $theme_dir = (string) $theme_dir;
+
$this->theme_root = $theme_root;
$this->stylesheet = $theme_dir;
@@ -246,7 +283,7 @@
$cache = $this->cache_get( 'theme' );
if ( is_array( $cache ) ) {
- foreach ( array( 'errors', 'headers', 'template' ) as $key ) {
+ foreach ( array( 'block_template_folders', 'block_theme', 'errors', 'headers', 'template' ) as $key ) {
if ( isset( $cache[ $key ] ) ) {
$this->$key = $cache[ $key ];
}
@@ -271,41 +308,51 @@
} else {
$this->errors = new WP_Error( 'theme_no_stylesheet', __( 'Stylesheet is missing.' ) );
}
- $this->template = $this->stylesheet;
+ $this->template = $this->stylesheet;
+ $this->block_theme = false;
+ $this->block_template_folders = $this->default_template_folders;
$this->cache_add(
'theme',
array(
- 'headers' => $this->headers,
- 'errors' => $this->errors,
- 'stylesheet' => $this->stylesheet,
- 'template' => $this->template,
+ 'block_template_folders' => $this->block_template_folders,
+ 'block_theme' => $this->block_theme,
+ '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', __( '<strong>Error</strong>: The themes directory is either empty or does not exist. Please check your installation.' ) );
+ $this->errors->add( 'theme_root_missing', __( '<strong>Error:</strong> The themes directory is either empty or does not exist. Please check your installation.' ) );
}
return;
} 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;
+ $this->headers['Name'] = $this->stylesheet;
+ $this->errors = new WP_Error( 'theme_stylesheet_not_readable', __( 'Stylesheet is not readable.' ) );
+ $this->template = $this->stylesheet;
+ $this->block_theme = false;
+ $this->block_template_folders = $this->default_template_folders;
$this->cache_add(
'theme',
array(
- 'headers' => $this->headers,
- 'errors' => $this->errors,
- 'stylesheet' => $this->stylesheet,
- 'template' => $this->template,
+ 'block_template_folders' => $this->block_template_folders,
+ 'block_theme' => $this->block_theme,
+ 'headers' => $this->headers,
+ 'errors' => $this->errors,
+ 'stylesheet' => $this->stylesheet,
+ 'template' => $this->template,
)
);
return;
} else {
$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 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 ) {
+ if ( basename( $this->stylesheet ) !== $default_theme_slug ) {
$this->headers['Name'] .= '/' . $this->stylesheet;
}
}
@@ -323,9 +370,11 @@
$this->cache_add(
'theme',
array(
- 'headers' => $this->headers,
- 'errors' => $this->errors,
- 'stylesheet' => $this->stylesheet,
+ 'block_template_folders' => $this->get_block_template_folders(),
+ 'block_theme' => $this->is_block_theme(),
+ 'headers' => $this->headers,
+ 'errors' => $this->errors,
+ 'stylesheet' => $this->stylesheet,
)
);
@@ -341,11 +390,7 @@
$this->template = $this->stylesheet;
$theme_path = $this->theme_root . '/' . $this->stylesheet;
- if (
- ! file_exists( $theme_path . '/templates/index.html' )
- && ! file_exists( $theme_path . '/block-templates/index.html' ) // Deprecated path support since 5.9.0.
- && ! file_exists( $theme_path . '/index.php' )
- ) {
+ if ( ! $this->is_block_theme() && ! file_exists( $theme_path . '/index.php' ) ) {
$error_message = sprintf(
/* translators: 1: templates/index.html, 2: index.php, 3: Documentation URL, 4: Template, 5: style.css */
__( 'Template is missing. Standalone themes need to have a %1$s or %2$s template file. <a href="%3$s">Child themes</a> need to have a %4$s header in the %5$s stylesheet.' ),
@@ -359,10 +404,12 @@
$this->cache_add(
'theme',
array(
- 'headers' => $this->headers,
- 'errors' => $this->errors,
- 'stylesheet' => $this->stylesheet,
- 'template' => $this->template,
+ 'block_template_folders' => $this->get_block_template_folders(),
+ 'block_theme' => $this->block_theme,
+ 'headers' => $this->headers,
+ 'errors' => $this->errors,
+ 'stylesheet' => $this->stylesheet,
+ 'template' => $this->template,
)
);
return;
@@ -370,17 +417,26 @@
}
// 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/*
+ 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' ) ) {
+ 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.
+ /*
+ * 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'];
} else {
// Parent theme is missing.
@@ -395,10 +451,12 @@
$this->cache_add(
'theme',
array(
- 'headers' => $this->headers,
- 'errors' => $this->errors,
- 'stylesheet' => $this->stylesheet,
- 'template' => $this->template,
+ 'block_template_folders' => $this->get_block_template_folders(),
+ 'block_theme' => $this->is_block_theme(),
+ 'headers' => $this->headers,
+ 'errors' => $this->errors,
+ 'stylesheet' => $this->stylesheet,
+ 'template' => $this->template,
)
);
$this->parent = new WP_Theme( $this->template, $this->theme_root, $this );
@@ -407,9 +465,9 @@
}
// Set the parent, if we're a child theme.
- if ( $this->template != $this->stylesheet ) {
+ 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 ) {
+ if ( $_child instanceof WP_Theme && $_child->template === $this->stylesheet ) {
$_child->parent = null;
$_child->errors = new WP_Error(
'theme_parent_invalid',
@@ -422,14 +480,16 @@
$_child->cache_add(
'theme',
array(
- 'headers' => $_child->headers,
- 'errors' => $_child->errors,
- 'stylesheet' => $_child->stylesheet,
- 'template' => $_child->template,
+ 'block_template_folders' => $_child->get_block_template_folders(),
+ 'block_theme' => $_child->is_block_theme(),
+ '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 ) {
+ if ( $_child->stylesheet === $this->template ) {
$this->errors = new WP_Error(
'theme_parent_invalid',
sprintf(
@@ -441,16 +501,18 @@
$this->cache_add(
'theme',
array(
- 'headers' => $this->headers,
- 'errors' => $this->errors,
- 'stylesheet' => $this->stylesheet,
- 'template' => $this->template,
+ 'block_template_folders' => $this->get_block_template_folders(),
+ 'block_theme' => $this->is_block_theme(),
+ 'headers' => $this->headers,
+ 'errors' => $this->errors,
+ 'stylesheet' => $this->stylesheet,
+ 'template' => $this->template,
)
);
}
return;
}
- // Set the parent. Pass the current instance so we can do the crazy checks above and assess errors.
+ // Set the parent. Pass the current instance so we can do the checks above and assess errors.
$this->parent = new WP_Theme( $this->template, isset( $theme_root_template ) ? $theme_root_template : $this->theme_root, $this );
}
@@ -461,10 +523,12 @@
// We're good. If we didn't retrieve from cache, set it.
if ( ! is_array( $cache ) ) {
$cache = array(
- 'headers' => $this->headers,
- 'errors' => $this->errors,
- 'stylesheet' => $this->stylesheet,
- 'template' => $this->template,
+ 'block_theme' => $this->is_block_theme(),
+ 'block_template_folders' => $this->get_block_template_folders(),
+ '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 ) ) {
@@ -714,6 +778,26 @@
}
/**
+ * Perform reinitialization tasks.
+ *
+ * Prevents a callback from being injected during unserialization of an object.
+ */
+ public function __wakeup() {
+ if ( $this->parent && ! $this->parent instanceof self ) {
+ throw new UnexpectedValueException();
+ }
+ if ( $this->headers && ! is_array( $this->headers ) ) {
+ throw new UnexpectedValueException();
+ }
+ foreach ( $this->headers as $value ) {
+ if ( ! is_string( $value ) ) {
+ throw new UnexpectedValueException();
+ }
+ }
+ $this->headers_sanitized = array();
+ }
+
+ /**
* Adds theme data to cache.
*
* Cache entries keyed by the theme and the type of data.
@@ -751,15 +835,18 @@
foreach ( array( 'theme', 'screenshot', 'headers', 'post_templates' ) as $key ) {
wp_cache_delete( $key . '-' . $this->cache_hash, 'themes' );
}
- $this->template = null;
- $this->textdomain_loaded = null;
- $this->theme_root_uri = null;
- $this->parent = null;
- $this->errors = null;
- $this->headers_sanitized = null;
- $this->name_translated = null;
- $this->headers = array();
+ $this->template = null;
+ $this->textdomain_loaded = null;
+ $this->theme_root_uri = null;
+ $this->parent = null;
+ $this->errors = null;
+ $this->headers_sanitized = null;
+ $this->name_translated = null;
+ $this->block_theme = null;
+ $this->block_template_folders = null;
+ $this->headers = array();
$this->__construct( $this->stylesheet, $this->theme_root );
+ $this->delete_pattern_cache();
}
/**
@@ -844,9 +931,11 @@
*
* @since 3.4.0
* @since 5.4.0 Added support for `Requires at least` and `Requires PHP` headers.
+ * @since 6.1.0 Added support for `Update URI` header.
*
* @param string $header Theme header. Accepts 'Name', 'Description', 'Author', 'Version',
- * 'ThemeURI', 'AuthorURI', 'Status', 'Tags', 'RequiresWP', 'RequiresPHP'.
+ * 'ThemeURI', 'AuthorURI', 'Status', 'Tags', 'RequiresWP', 'RequiresPHP',
+ * 'UpdateURI'.
* @param string $value Value to sanitize.
* @return string|array An array for Tags header, string otherwise.
*/
@@ -888,7 +977,7 @@
break;
case 'ThemeURI':
case 'AuthorURI':
- $value = esc_url_raw( $value );
+ $value = sanitize_url( $value );
break;
case 'Tags':
$value = array_filter( array_map( 'trim', explode( ',', strip_tags( $value ) ) ) );
@@ -896,6 +985,7 @@
case 'Version':
case 'RequiresWP':
case 'RequiresPHP':
+ case 'UpdateURI':
$value = strip_tags( $value );
break;
}
@@ -1178,7 +1268,7 @@
return false;
}
- foreach ( array( 'png', 'gif', 'jpg', 'jpeg', 'webp' ) as $ext ) {
+ 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 ) {
@@ -1257,24 +1347,24 @@
}
}
- 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 ) {
- continue;
- }
+ $this->cache_add( 'post_templates', $post_templates );
+ }
- if ( isset( $block_template->post_types ) && ! in_array( $type, $block_template->post_types, true ) ) {
- continue;
- }
+ 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 ) {
+ continue;
+ }
- $post_templates[ $type ][ $block_template->slug ] = $block_template->title;
+ if ( isset( $block_template->post_types ) && ! in_array( $type, $block_template->post_types, true ) ) {
+ continue;
}
+
+ $post_templates[ $type ][ $block_template->slug ] = $block_template->title;
}
}
-
- $this->cache_add( 'post_templates', $post_templates );
}
if ( $this->load_textdomain() ) {
@@ -1484,18 +1574,25 @@
* @return bool
*/
public function is_block_theme() {
+ if ( isset( $this->block_theme ) ) {
+ return $this->block_theme;
+ }
+
$paths_to_index_block_template = array(
+ $this->get_file_path( '/templates/index.html' ),
$this->get_file_path( '/block-templates/index.html' ),
- $this->get_file_path( '/templates/index.html' ),
);
+ $this->block_theme = false;
+
foreach ( $paths_to_index_block_template as $path_to_index_block_template ) {
if ( is_file( $path_to_index_block_template ) && is_readable( $path_to_index_block_template ) ) {
- return true;
+ $this->block_theme = true;
+ break;
}
}
- return false;
+ return $this->block_theme;
}
/**
@@ -1517,7 +1614,7 @@
if ( empty( $file ) ) {
$path = $stylesheet_directory;
- } elseif ( file_exists( $stylesheet_directory . '/' . $file ) ) {
+ } elseif ( $stylesheet_directory !== $template_directory && file_exists( $stylesheet_directory . '/' . $file ) ) {
$path = $stylesheet_directory . '/' . $file;
} else {
$path = $template_directory . '/' . $file;
@@ -1622,7 +1719,7 @@
return (array) apply_filters( 'site_allowed_themes', $allowed_themes[ $blog_id ], $blog_id );
}
- $current = get_current_blog_id() == $blog_id;
+ $current = get_current_blog_id() === $blog_id;
if ( $current ) {
$allowed_themes[ $blog_id ] = get_option( 'allowedthemes' );
@@ -1632,8 +1729,10 @@
restore_current_blog();
}
- // This is all super old MU back compat joy.
- // 'allowedthemes' keys things by stylesheet. 'allowed_themes' keyed things by name.
+ /*
+ * This is all super old MU back compat joy.
+ * 'allowedthemes' keys things by stylesheet. 'allowed_themes' keyed things by name.
+ */
if ( false === $allowed_themes[ $blog_id ] ) {
if ( $current ) {
$allowed_themes[ $blog_id ] = get_option( 'allowed_themes' );
@@ -1674,6 +1773,275 @@
}
/**
+ * Returns the folder names of the block template directories.
+ *
+ * @since 6.4.0
+ *
+ * @return string[] {
+ * Folder names used by block themes.
+ *
+ * @type string $wp_template Theme-relative directory name for block templates.
+ * @type string $wp_template_part Theme-relative directory name for block template parts.
+ * }
+ */
+ public function get_block_template_folders() {
+ // Return set/cached value if available.
+ if ( isset( $this->block_template_folders ) ) {
+ return $this->block_template_folders;
+ }
+
+ $this->block_template_folders = $this->default_template_folders;
+
+ $stylesheet_directory = $this->get_stylesheet_directory();
+ // If the theme uses deprecated block template folders.
+ if ( file_exists( $stylesheet_directory . '/block-templates' ) || file_exists( $stylesheet_directory . '/block-template-parts' ) ) {
+ $this->block_template_folders = array(
+ 'wp_template' => 'block-templates',
+ 'wp_template_part' => 'block-template-parts',
+ );
+ }
+ return $this->block_template_folders;
+ }
+
+ /**
+ * Gets block pattern data for a specified theme.
+ * Each pattern is defined as a PHP file and defines
+ * its metadata using plugin-style headers. The minimum required definition is:
+ *
+ * /**
+ * * Title: My Pattern
+ * * Slug: my-theme/my-pattern
+ * *
+ *
+ * The output of the PHP source corresponds to the content of the pattern, e.g.:
+ *
+ * <main><p><?php echo "Hello"; ?></p></main>
+ *
+ * If applicable, this will collect from both parent and child theme.
+ *
+ * Other settable fields include:
+ *
+ * - Description
+ * - Viewport Width
+ * - Inserter (yes/no)
+ * - Categories (comma-separated values)
+ * - Keywords (comma-separated values)
+ * - Block Types (comma-separated values)
+ * - Post Types (comma-separated values)
+ * - Template Types (comma-separated values)
+ *
+ * @since 6.4.0
+ *
+ * @return array Block pattern data.
+ */
+ public function get_block_patterns() {
+ $can_use_cached = ! wp_is_development_mode( 'theme' );
+
+ $pattern_data = $this->get_pattern_cache();
+ if ( is_array( $pattern_data ) ) {
+ if ( $can_use_cached ) {
+ return $pattern_data;
+ }
+ // If in development mode, clear pattern cache.
+ $this->delete_pattern_cache();
+ }
+
+ $dirpath = $this->get_stylesheet_directory() . '/patterns/';
+ $pattern_data = array();
+
+ if ( ! file_exists( $dirpath ) ) {
+ if ( $can_use_cached ) {
+ $this->set_pattern_cache( $pattern_data );
+ }
+ return $pattern_data;
+ }
+ $files = glob( $dirpath . '*.php' );
+ if ( ! $files ) {
+ if ( $can_use_cached ) {
+ $this->set_pattern_cache( $pattern_data );
+ }
+ return $pattern_data;
+ }
+
+ $default_headers = array(
+ 'title' => 'Title',
+ 'slug' => 'Slug',
+ 'description' => 'Description',
+ 'viewportWidth' => 'Viewport Width',
+ 'inserter' => 'Inserter',
+ 'categories' => 'Categories',
+ 'keywords' => 'Keywords',
+ 'blockTypes' => 'Block Types',
+ 'postTypes' => 'Post Types',
+ 'templateTypes' => 'Template Types',
+ );
+
+ $properties_to_parse = array(
+ 'categories',
+ 'keywords',
+ 'blockTypes',
+ 'postTypes',
+ 'templateTypes',
+ );
+
+ foreach ( $files as $file ) {
+ $pattern = get_file_data( $file, $default_headers );
+
+ if ( empty( $pattern['slug'] ) ) {
+ _doing_it_wrong(
+ __FUNCTION__,
+ sprintf(
+ /* translators: 1: file name. */
+ __( 'Could not register file "%s" as a block pattern ("Slug" field missing)' ),
+ $file
+ ),
+ '6.0.0'
+ );
+ continue;
+ }
+
+ if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern['slug'] ) ) {
+ _doing_it_wrong(
+ __FUNCTION__,
+ sprintf(
+ /* translators: 1: file name; 2: slug value found. */
+ __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")' ),
+ $file,
+ $pattern['slug']
+ ),
+ '6.0.0'
+ );
+ }
+
+ // Title is a required property.
+ if ( ! $pattern['title'] ) {
+ _doing_it_wrong(
+ __FUNCTION__,
+ sprintf(
+ /* translators: 1: file name. */
+ __( 'Could not register file "%s" as a block pattern ("Title" field missing)' ),
+ $file
+ ),
+ '6.0.0'
+ );
+ continue;
+ }
+
+ // For properties of type array, parse data as comma-separated.
+ foreach ( $properties_to_parse as $property ) {
+ if ( ! empty( $pattern[ $property ] ) ) {
+ $pattern[ $property ] = array_filter( wp_parse_list( (string) $pattern[ $property ] ) );
+ } else {
+ unset( $pattern[ $property ] );
+ }
+ }
+
+ // Parse properties of type int.
+ $property = 'viewportWidth';
+ if ( ! empty( $pattern[ $property ] ) ) {
+ $pattern[ $property ] = (int) $pattern[ $property ];
+ } else {
+ unset( $pattern[ $property ] );
+ }
+
+ // Parse properties of type bool.
+ $property = 'inserter';
+ if ( ! empty( $pattern[ $property ] ) ) {
+ $pattern[ $property ] = in_array(
+ strtolower( $pattern[ $property ] ),
+ array( 'yes', 'true' ),
+ true
+ );
+ } else {
+ unset( $pattern[ $property ] );
+ }
+
+ $key = str_replace( $dirpath, '', $file );
+
+ $pattern_data[ $key ] = $pattern;
+ }
+
+ if ( $can_use_cached ) {
+ $this->set_pattern_cache( $pattern_data );
+ }
+
+ return $pattern_data;
+ }
+
+ /**
+ * Gets block pattern cache.
+ *
+ * @since 6.4.0
+ * @since 6.6.0 Uses transients to cache regardless of site environment.
+ *
+ * @return array|false Returns an array of patterns if cache is found, otherwise false.
+ */
+ private function get_pattern_cache() {
+ if ( ! $this->exists() ) {
+ return false;
+ }
+
+ $pattern_data = get_site_transient( 'wp_theme_files_patterns-' . $this->cache_hash );
+
+ if ( is_array( $pattern_data ) && $pattern_data['version'] === $this->get( 'Version' ) ) {
+ return $pattern_data['patterns'];
+ }
+ return false;
+ }
+
+ /**
+ * Sets block pattern cache.
+ *
+ * @since 6.4.0
+ * @since 6.6.0 Uses transients to cache regardless of site environment.
+ *
+ * @param array $patterns Block patterns data to set in cache.
+ */
+ private function set_pattern_cache( array $patterns ) {
+ $pattern_data = array(
+ 'version' => $this->get( 'Version' ),
+ 'patterns' => $patterns,
+ );
+
+ /**
+ * Filters the cache expiration time for theme files.
+ *
+ * @since 6.6.0
+ *
+ * @param int $cache_expiration Cache expiration time in seconds.
+ * @param string $cache_type Type of cache being set.
+ */
+ $cache_expiration = (int) apply_filters( 'wp_theme_files_cache_ttl', self::$cache_expiration, 'theme_block_patterns' );
+
+ // We don't want to cache patterns infinitely.
+ if ( $cache_expiration <= 0 ) {
+ _doing_it_wrong(
+ __METHOD__,
+ sprintf(
+ /* translators: %1$s: The filter name.*/
+ __( 'The %1$s filter must return an integer value greater than 0.' ),
+ '<code>wp_theme_files_cache_ttl</code>'
+ ),
+ '6.6.0'
+ );
+
+ $cache_expiration = self::$cache_expiration;
+ }
+
+ set_site_transient( 'wp_theme_files_patterns-' . $this->cache_hash, $pattern_data, $cache_expiration );
+ }
+
+ /**
+ * Clears block pattern cache.
+ *
+ * @since 6.4.0
+ * @since 6.6.0 Uses transients to cache regardless of site environment.
+ */
+ public function delete_pattern_cache() {
+ delete_site_transient( 'wp_theme_files_patterns-' . $this->cache_hash );
+ }
+
+ /**
* Enables a theme for all sites on the current network.
*
* @since 4.6.0
@@ -1731,7 +2099,7 @@
* @param WP_Theme[] $themes Array of theme objects to sort (passed by reference).
*/
public static function sort_by_name( &$themes ) {
- if ( 0 === strpos( get_user_locale(), 'en_' ) ) {
+ if ( str_starts_with( get_user_locale(), 'en_' ) ) {
uasort( $themes, array( 'WP_Theme', '_name_sort' ) );
} else {
foreach ( $themes as $key => $theme ) {
@@ -1771,4 +2139,16 @@
private static function _name_sort_i18n( $a, $b ) {
return strnatcasecmp( $a->name_translated, $b->name_translated );
}
+
+ private static function _check_headers_property_has_correct_type( $headers ) {
+ if ( ! is_array( $headers ) ) {
+ return false;
+ }
+ foreach ( $headers as $key => $value ) {
+ if ( ! is_string( $key ) || ! is_string( $value ) ) {
+ return false;
+ }
+ }
+ return true;
+ }
}