diff --git a/wp-admin/includes/theme.php b/wp-admin/includes/theme.php
index b5579052a1..f822e57cf2 100644
--- a/wp-admin/includes/theme.php
+++ b/wp-admin/includes/theme.php
@@ -81,6 +81,8 @@ function delete_theme( $stylesheet, $redirect = '' ) {
*/
do_action( 'delete_theme', $stylesheet );
+ $theme = wp_get_theme( $stylesheet );
+
$themes_dir = trailingslashit( $themes_dir );
$theme_dir = trailingslashit( $themes_dir . $stylesheet );
$deleted = $wp_filesystem->delete( $theme_dir, true );
@@ -125,6 +127,9 @@ function delete_theme( $stylesheet, $redirect = '' ) {
WP_Theme::network_disable_theme( $stylesheet );
}
+ // Clear theme caches.
+ $theme->cache_delete();
+
// Force refresh of theme update information.
delete_site_transient( 'update_themes' );
diff --git a/wp-includes/block-patterns.php b/wp-includes/block-patterns.php
index 6f18672505..0bd5f4bdc8 100644
--- a/wp-includes/block-patterns.php
+++ b/wp-includes/block-patterns.php
@@ -319,37 +319,132 @@ function _register_remote_theme_patterns() {
/**
* Register any patterns that the active theme may provide under its
- * `./patterns/` directory. 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.:
- *
- *
- *
- * 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)
+ * `./patterns/` directory.
*
* @since 6.0.0
* @since 6.1.0 The `postTypes` property was added.
* @since 6.2.0 The `templateTypes` property was added.
+ * @since 6.4.0 Uses the `_wp_get_block_patterns` function.
* @access private
*/
function _register_theme_block_patterns() {
+ /*
+ * Register patterns for the active theme. If the theme is a child theme,
+ * let it override any patterns from the parent theme that shares the same slug.
+ */
+ $themes = array();
+ $theme = wp_get_theme();
+ $themes[] = $theme;
+ if ( $theme->parent() ) {
+ $themes[] = $theme->parent();
+ }
+ $registry = WP_Block_Patterns_Registry::get_instance();
+
+ foreach ( $themes as $theme ) {
+ $pattern_data = _wp_get_block_patterns( $theme );
+ $dirpath = $theme->get_stylesheet_directory() . '/patterns/';
+ $text_domain = $theme->get( 'TextDomain' );
+
+ foreach ( $pattern_data['patterns'] as $file => $pattern_data ) {
+ if ( $registry->is_registered( $pattern_data['slug'] ) ) {
+ continue;
+ }
+
+ // The actual pattern content is the output of the file.
+ ob_start();
+ include $dirpath . $file;
+ $pattern_data['content'] = ob_get_clean();
+ if ( ! $pattern_data['content'] ) {
+ continue;
+ }
+
+ // Translate the pattern metadata.
+ // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain,WordPress.WP.I18n.LowLevelTranslationFunction
+ $pattern_data['title'] = translate_with_gettext_context( $pattern_data['title'], 'Pattern title', $text_domain );
+ if ( ! empty( $pattern_data['description'] ) ) {
+ // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain,WordPress.WP.I18n.LowLevelTranslationFunction
+ $pattern_data['description'] = translate_with_gettext_context( $pattern_data['description'], 'Pattern description', $text_domain );
+ }
+
+ register_block_pattern( $pattern_data['slug'], $pattern_data );
+ }
+ }
+}
+add_action( 'init', '_register_theme_block_patterns' );
+
+/**
+ * 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.:
+ *
+ *
+ *
+ * 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
+ * @access private
+ *
+ * @param WP_Theme $theme Theme object.
+ * @return array Block pattern data.
+ */
+
+function _wp_get_block_patterns( WP_Theme $theme ) {
+ if ( ! $theme->exists() ) {
+ return array(
+ 'version' => false,
+ 'patterns' => array(),
+ );
+ }
+
+ $transient_name = 'wp_theme_patterns_' . $theme->get_stylesheet();
+ $version = $theme->get( 'Version' );
+ $can_use_cached = ! wp_is_development_mode( 'theme' );
+
+ if ( $can_use_cached ) {
+ $pattern_data = get_transient( $transient_name );
+ if ( is_array( $pattern_data ) && $pattern_data['version'] === $version ) {
+ return $pattern_data;
+ }
+ }
+
+ $pattern_data = array(
+ 'version' => $version,
+ 'patterns' => array(),
+ );
+ $dirpath = $theme->get_stylesheet_directory() . '/patterns/';
+
+ if ( ! file_exists( $dirpath ) ) {
+ if ( $can_use_cached ) {
+ set_transient( $transient_name, $pattern_data );
+ }
+ return $pattern_data;
+ }
+ $files = glob( $dirpath . '*.php' );
+ if ( ! $files ) {
+ if ( $can_use_cached ) {
+ set_transient( $transient_name, $pattern_data );
+ }
+ return $pattern_data;
+ }
+
$default_headers = array(
'title' => 'Title',
'slug' => 'Slug',
@@ -363,130 +458,94 @@ function _register_theme_block_patterns() {
'templateTypes' => 'Template Types',
);
- /*
- * Register patterns for the active theme. If the theme is a child theme,
- * let it override any patterns from the parent theme that shares the same slug.
- */
- $themes = array();
- $stylesheet = get_stylesheet();
- $template = get_template();
- if ( $stylesheet !== $template ) {
- $themes[] = wp_get_theme( $stylesheet );
- }
- $themes[] = wp_get_theme( $template );
+ $properties_to_parse = array(
+ 'categories',
+ 'keywords',
+ 'blockTypes',
+ 'postTypes',
+ 'templateTypes',
+ );
- foreach ( $themes as $theme ) {
- $dirpath = $theme->get_stylesheet_directory() . '/patterns/';
- if ( ! is_dir( $dirpath ) || ! is_readable( $dirpath ) ) {
+ foreach ( $files as $file ) {
+ $pattern = get_file_data( $file, $default_headers );
+
+ if ( empty( $pattern['slug'] ) ) {
+ _doing_it_wrong(
+ __FUNCTION__,
+ sprintf(
+ /* translators: %s: file name. */
+ __( 'Could not register file "%s" as a block pattern ("Slug" field missing)' ),
+ $file
+ ),
+ '6.0.0'
+ );
continue;
}
- if ( file_exists( $dirpath ) ) {
- $files = glob( $dirpath . '*.php' );
- if ( $files ) {
- foreach ( $files as $file ) {
- $pattern_data = get_file_data( $file, $default_headers );
- if ( empty( $pattern_data['slug'] ) ) {
- _doing_it_wrong(
- '_register_theme_block_patterns',
- sprintf(
- /* translators: %s: 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: %1s: file name; %2s: slug value found. */
+ __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")' ),
+ $file,
+ $pattern['slug']
+ ),
+ '6.0.0'
+ );
+ }
- if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern_data['slug'] ) ) {
- _doing_it_wrong(
- '_register_theme_block_patterns',
- sprintf(
- /* translators: %1s: file name; %2s: slug value found. */
- __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")' ),
- $file,
- $pattern_data['slug']
- ),
- '6.0.0'
- );
- }
+ // Title is a required property.
+ if ( ! $pattern['title'] ) {
+ _doing_it_wrong(
+ __FUNCTION__,
+ sprintf(
+ /* translators: %1s: file name. */
+ __( 'Could not register file "%s" as a block pattern ("Title" field missing)' ),
+ $file
+ ),
+ '6.0.0'
+ );
+ continue;
+ }
- if ( WP_Block_Patterns_Registry::get_instance()->is_registered( $pattern_data['slug'] ) ) {
- continue;
- }
-
- // Title is a required property.
- if ( ! $pattern_data['title'] ) {
- _doing_it_wrong(
- '_register_theme_block_patterns',
- sprintf(
- /* translators: %1s: file name; %2s: slug value found. */
- __( '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 ( array( 'categories', 'keywords', 'blockTypes', 'postTypes', 'templateTypes' ) as $property ) {
- if ( ! empty( $pattern_data[ $property ] ) ) {
- $pattern_data[ $property ] = array_filter(
- preg_split(
- '/[\s,]+/',
- (string) $pattern_data[ $property ]
- )
- );
- } else {
- unset( $pattern_data[ $property ] );
- }
- }
-
- // Parse properties of type int.
- foreach ( array( 'viewportWidth' ) as $property ) {
- if ( ! empty( $pattern_data[ $property ] ) ) {
- $pattern_data[ $property ] = (int) $pattern_data[ $property ];
- } else {
- unset( $pattern_data[ $property ] );
- }
- }
-
- // Parse properties of type bool.
- foreach ( array( 'inserter' ) as $property ) {
- if ( ! empty( $pattern_data[ $property ] ) ) {
- $pattern_data[ $property ] = in_array(
- strtolower( $pattern_data[ $property ] ),
- array( 'yes', 'true' ),
- true
- );
- } else {
- unset( $pattern_data[ $property ] );
- }
- }
-
- // Translate the pattern metadata.
- $text_domain = $theme->get( 'TextDomain' );
- // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain,WordPress.WP.I18n.LowLevelTranslationFunction
- $pattern_data['title'] = translate_with_gettext_context( $pattern_data['title'], 'Pattern title', $text_domain );
- if ( ! empty( $pattern_data['description'] ) ) {
- // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain,WordPress.WP.I18n.LowLevelTranslationFunction
- $pattern_data['description'] = translate_with_gettext_context( $pattern_data['description'], 'Pattern description', $text_domain );
- }
-
- // The actual pattern content is the output of the file.
- ob_start();
- include $file;
- $pattern_data['content'] = ob_get_clean();
- if ( ! $pattern_data['content'] ) {
- continue;
- }
-
- register_block_pattern( $pattern_data['slug'], $pattern_data );
- }
+ // 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['patterns'][ $key ] = $pattern;
}
+
+ if ( $can_use_cached ) {
+ set_transient( $transient_name, $pattern_data );
+ }
+
+ return $pattern_data;
}
-add_action( 'init', '_register_theme_block_patterns' );
diff --git a/wp-includes/class-wp-theme.php b/wp-includes/class-wp-theme.php
index d7c36440ee..e5142d0f08 100644
--- a/wp-includes/class-wp-theme.php
+++ b/wp-includes/class-wp-theme.php
@@ -821,6 +821,16 @@ final class WP_Theme implements ArrayAccess {
$this->block_template_folders = null;
$this->headers = array();
$this->__construct( $this->stylesheet, $this->theme_root );
+ $this->delete_pattern_cache();
+ }
+
+ /**
+ * Clear block pattern cache.
+ *
+ * @since 6.4.0
+ */
+ public function delete_pattern_cache() {
+ delete_transient( 'wp_theme_patterns_' . $this->stylesheet );
}
/**
diff --git a/wp-includes/theme.php b/wp-includes/theme.php
index 1eccd460b1..b5fba76159 100644
--- a/wp-includes/theme.php
+++ b/wp-includes/theme.php
@@ -873,6 +873,10 @@ function switch_theme( $stylesheet ) {
$wp_stylesheet_path = null;
$wp_template_path = null;
+ // Clear pattern caches.
+ $new_theme->delete_pattern_cache();
+ $old_theme->delete_pattern_cache();
+
/**
* Fires after the theme is switched.
*
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 370974d850..da12e892f6 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
-$wp_version = '6.4-beta1-56764';
+$wp_version = '6.4-beta1-56765';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.