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.