<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[56765] trunk: Editor: Improve performance of _register_theme_block_patterns function.</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/56765">56765</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/56765","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>spacedmonkey</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2023-10-03 15:16:55 +0000 (Tue, 03 Oct 2023)</dd>
</dl>
<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Editor: Improve performance of _register_theme_block_patterns function.
The `_register_theme_block_patterns` function imposed a significant resource overhead. This issue primarily stems from themes, such as TT4, that register a substantial number of block patterns. These patterns necessitate numerous file operations, including file lookups, file reading into memory, and related processes. To provide an overview, the _register_theme_block_patterns function performed the following file operations:
- is_dir
- is_readable
- file_exists
- glob
- file_get_contents (utilized via get_file_data)
To address these issues, caching using a transient has been added to a new function call `_wp_get_block_patterns`. If theme development mode is disabled and theme exists, the block patterns are saved in a transient cache. This cache is used all requests after that, saving file lookups and reading files into memory. Cache invalidation is done, when themes are switched, deleted or updated. Meaning that block patterns are not stored in the cache incorrectly.
Props flixos90, joemcgill, peterwilsoncc, costdev, swissspidy, aristath, westonruter, spacedmonkey.
Fixes <a href="https://core.trac.wordpress.org/ticket/59490">#59490</a></pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpadminincludesthemephp">trunk/src/wp-admin/includes/theme.php</a></li>
<li><a href="#trunksrcwpincludesblockpatternsphp">trunk/src/wp-includes/block-patterns.php</a></li>
<li><a href="#trunksrcwpincludesclasswpthemephp">trunk/src/wp-includes/class-wp-theme.php</a></li>
<li><a href="#trunksrcwpincludesthemephp">trunk/src/wp-includes/theme.php</a></li>
<li><a href="#trunktestsphpunitteststhemethemeDirphp">trunk/tests/phpunit/tests/theme/themeDir.php</a></li>
</ul>
<h3>Added Paths</h3>
<ul>
<li>trunk/tests/phpunit/data/themedir1/block-theme-patterns/</li>
<li>trunk/tests/phpunit/data/themedir1/block-theme-patterns/patterns/</li>
<li><a href="#trunktestsphpunitdatathemedir1blockthemepatternspatternsctaphp">trunk/tests/phpunit/data/themedir1/block-theme-patterns/patterns/cta.php</a></li>
<li><a href="#trunktestsphpunitdatathemedir1blockthemepatternsstylecss">trunk/tests/phpunit/data/themedir1/block-theme-patterns/style.css</a></li>
<li><a href="#trunktestsphpunittestsblockswpGetBlockPatternsphp">trunk/tests/phpunit/tests/blocks/wpGetBlockPatterns.php</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpadminincludesthemephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/includes/theme.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/includes/theme.php 2023-10-03 15:12:30 UTC (rev 56764)
+++ trunk/src/wp-admin/includes/theme.php 2023-10-03 15:16:55 UTC (rev 56765)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -81,6 +81,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> do_action( 'delete_theme', $stylesheet );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $theme = wp_get_theme( $stylesheet );
+
</ins><span class="cx" style="display: block; padding: 0 10px"> $themes_dir = trailingslashit( $themes_dir );
</span><span class="cx" style="display: block; padding: 0 10px"> $theme_dir = trailingslashit( $themes_dir . $stylesheet );
</span><span class="cx" style="display: block; padding: 0 10px"> $deleted = $wp_filesystem->delete( $theme_dir, true );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -125,6 +127,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> WP_Theme::network_disable_theme( $stylesheet );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // Clear theme caches.
+ $theme->cache_delete();
+
</ins><span class="cx" style="display: block; padding: 0 10px"> // Force refresh of theme update information.
</span><span class="cx" style="display: block; padding: 0 10px"> delete_site_transient( 'update_themes' );
</span><span class="cx" style="display: block; padding: 0 10px">
</span></span></pre></div>
<a id="trunksrcwpincludesblockpatternsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/block-patterns.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/block-patterns.php 2023-10-03 15:12:30 UTC (rev 56764)
+++ trunk/src/wp-includes/block-patterns.php 2023-10-03 15:16:55 UTC (rev 56765)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -319,37 +319,132 @@
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px"> * Register any patterns that the active theme may provide under its
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * `./patterns/` directory. Each pattern is defined as a PHP file and defines
- * its metadata using plugin-style headers. The minimum required definition is:
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * `./patterns/` directory.
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * /**
- * * Title: My Pattern
- * * Slug: my-theme/my-pattern
- * *
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @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:
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * The output of the PHP source corresponds to the content of the pattern, e.g.:
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * /**
+ * * Title: My Pattern
+ * * Slug: my-theme/my-pattern
+ * *
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * <main><p><?php echo "Hello"; ?></p></main>
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * The output of the PHP source corresponds to the content of the pattern, e.g.:
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * If applicable, this will collect from both parent and child theme.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * <main><p><?php echo "Hello"; ?></p></main>
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Other settable fields include:
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * If applicable, this will collect from both parent and child theme.
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * - 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)
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Other settable fields include:
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @since 6.0.0
- * @since 6.1.0 The `postTypes` property was added.
- * @since 6.2.0 The `templateTypes` property was added.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * - 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
</ins><span class="cx" style="display: block; padding: 0 10px"> * @access private
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ *
+ * @param WP_Theme $theme Theme object.
+ * @return array Block pattern data.
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-function _register_theme_block_patterns() {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+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;
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> $default_headers = array(
</span><span class="cx" style="display: block; padding: 0 10px"> 'title' => 'Title',
</span><span class="cx" style="display: block; padding: 0 10px"> 'slug' => 'Slug',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -363,130 +458,94 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'templateTypes' => 'Template Types',
</span><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- /*
- * 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 );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $properties_to_parse = array(
+ 'categories',
+ 'keywords',
+ 'blockTypes',
+ 'postTypes',
+ 'templateTypes',
+ );
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- foreach ( $themes as $theme ) {
- $dirpath = $theme->get_stylesheet_directory() . '/patterns/';
- if ( ! is_dir( $dirpath ) || ! is_readable( $dirpath ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 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'
+ );
</ins><span class="cx" style="display: block; padding: 0 10px"> continue;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- if ( file_exists( $dirpath ) ) {
- $files = glob( $dirpath . '*.php' );
- if ( $files ) {
- foreach ( $files as $file ) {
- $pattern_data = get_file_data( $file, $default_headers );
</del><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- 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;
- }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 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'
+ );
+ }
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- 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'
- );
- }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // 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;
+ }
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- if ( WP_Block_Patterns_Registry::get_instance()->is_registered( $pattern_data['slug'] ) ) {
- continue;
- }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // 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 ] );
+ }
+ }
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- // 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;
- }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // Parse properties of type int.
+ $property = 'viewportWidth';
+ if ( ! empty( $pattern[ $property ] ) ) {
+ $pattern[ $property ] = (int) $pattern[ $property ];
+ } else {
+ unset( $pattern[ $property ] );
+ }
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- // 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 ] );
- }
- }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // 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 ] );
+ }
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- // 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 ] );
- }
- }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $key = str_replace( $dirpath, '', $file );
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- // 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 ] );
- }
- }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $pattern_data['patterns'][ $key ] = $pattern;
+ }
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- // 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 );
- }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( $can_use_cached ) {
+ set_transient( $transient_name, $pattern_data );
+ }
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- // 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 );
- }
- }
- }
- }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ return $pattern_data;
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-add_action( 'init', '_register_theme_block_patterns' );
</del></span></pre></div>
<a id="trunksrcwpincludesclasswpthemephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-theme.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-theme.php 2023-10-03 15:12:30 UTC (rev 56764)
+++ trunk/src/wp-includes/class-wp-theme.php 2023-10-03 15:16:55 UTC (rev 56765)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -821,9 +821,19 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $this->block_template_folders = null;
</span><span class="cx" style="display: block; padding: 0 10px"> $this->headers = array();
</span><span class="cx" style="display: block; padding: 0 10px"> $this->__construct( $this->stylesheet, $this->theme_root );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $this->delete_pattern_cache();
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Clear block pattern cache.
+ *
+ * @since 6.4.0
+ */
+ public function delete_pattern_cache() {
+ delete_transient( 'wp_theme_patterns_' . $this->stylesheet );
+ }
+
+ /**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Gets a raw, unformatted theme header.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * The header is sanitized, but is not translated, and is not marked up for display.
</span></span></pre></div>
<a id="trunksrcwpincludesthemephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/theme.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/theme.php 2023-10-03 15:12:30 UTC (rev 56764)
+++ trunk/src/wp-includes/theme.php 2023-10-03 15:16:55 UTC (rev 56765)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -873,6 +873,10 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $wp_stylesheet_path = null;
</span><span class="cx" style="display: block; padding: 0 10px"> $wp_template_path = null;
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // Clear pattern caches.
+ $new_theme->delete_pattern_cache();
+ $old_theme->delete_pattern_cache();
+
</ins><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px"> * Fires after the theme is switched.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span></span></pre></div>
<a id="trunktestsphpunitdatathemedir1blockthemepatternspatternsctaphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/data/themedir1/block-theme-patterns/patterns/cta.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/data/themedir1/block-theme-patterns/patterns/cta.php (rev 0)
+++ trunk/tests/phpunit/data/themedir1/block-theme-patterns/patterns/cta.php 2023-10-03 15:16:55 UTC (rev 56765)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,36 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Title: Centered Call To Action
+ * Slug: block-theme-patterns/cta
+ * Categories: call-to-action
+ */
+?>
+
+<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"top":"var:preset|spacing|50","bottom":"var:preset|spacing|50","left":"var:preset|spacing|50","right":"var:preset|spacing|50"}}},"layout":{"type":"constrained"}} -->
+<div class="wp-block-group alignfull" style="padding-top:var(--wp--preset--spacing--50);padding-right:var(--wp--preset--spacing--50);padding-bottom:var(--wp--preset--spacing--50);padding-left:var(--wp--preset--spacing--50)"><!-- wp:group {"align":"wide","style":{"border":{"radius":"16px"},"spacing":{"padding":{"top":"var:preset|spacing|40","bottom":"var:preset|spacing|40","left":"var:preset|spacing|50","right":"var:preset|spacing|50"}}},"backgroundColor":"base-2","layout":{"type":"constrained"}} -->
+ <div class="wp-block-group alignwide has-base-2-background-color has-background" style="border-radius:16px;padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--50);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--50)"><!-- wp:spacer {"height":"var:preset|spacing|10"} -->
+ <div style="height:var(--wp--preset--spacing--10)" aria-hidden="true" class="wp-block-spacer"></div>
+ <!-- /wp:spacer -->
+
+ <!-- wp:heading {"textAlign":"center","fontSize":"x-large"} -->
+ <h2 class="wp-block-heading has-text-align-center has-x-large-font-size"><?php echo esc_html_x( 'Join 900+ subscribers', 'Sample text for Subscriber Heading with numbers', 'twentytwentyfour' ); ?></h2>
+ <!-- /wp:heading -->
+
+ <!-- wp:paragraph {"align":"center"} -->
+ <p class="has-text-align-center"><?php echo esc_html_x( 'Stay in the loop with everything you need to know.', 'Sample text for Subscriber Description', 'twentytwentyfour' ); ?></p>
+ <!-- /wp:paragraph -->
+
+ <!-- wp:buttons {"layout":{"type":"flex","justifyContent":"center"}} -->
+ <div class="wp-block-buttons"><!-- wp:button -->
+ <div class="wp-block-button"><a class="wp-block-button__link wp-element-button"><?php echo esc_html_x( 'Sign up', 'Sample text for Sign Up Button', 'twentytwentyfour' ); ?></a></div>
+ <!-- /wp:button -->
+ </div>
+ <!-- /wp:buttons -->
+
+ <!-- wp:spacer {"height":"var:preset|spacing|10"} -->
+ <div style="height:var(--wp--preset--spacing--10)" aria-hidden="true" class="wp-block-spacer"></div>
+ <!-- /wp:spacer -->
+ </div>
+ <!-- /wp:group -->
+</div>
+<!-- /wp:group -->
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/data/themedir1/block-theme-patterns/patterns/cta.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunitdatathemedir1blockthemepatternsstylecss"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/data/themedir1/block-theme-patterns/style.css</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/data/themedir1/block-theme-patterns/style.css (rev 0)
+++ trunk/tests/phpunit/data/themedir1/block-theme-patterns/style.css 2023-10-03 15:16:55 UTC (rev 56765)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,8 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/*
+Theme Name: Block Theme Patterns
+Theme URI: https://wordpress.org/
+Description: For testing purposes only.
+Template: block-theme
+Version: 1.0.0
+Text Domain: block-theme-patterns
+*/
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/data/themedir1/block-theme-patterns/style.css
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestsblockswpGetBlockPatternsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/blocks/wpGetBlockPatterns.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/blocks/wpGetBlockPatterns.php (rev 0)
+++ trunk/tests/phpunit/tests/blocks/wpGetBlockPatterns.php 2023-10-03 15:16:55 UTC (rev 56765)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,145 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Tests for _wp_get_block_patterns.
+ *
+ * @package WordPress
+ * @subpackage Blocks
+ * @since 6.4.0
+ *
+ * @group blocks
+ *
+ * @covers ::_wp_get_block_patterns
+ */
+class Tests_Blocks_WpGetBlockPatterns extends WP_UnitTestCase {
+ /**
+ * @ticket 59490
+ *
+ * @dataProvider data_wp_get_block_patterns
+ *
+ * @param string $theme The theme's slug.
+ * @param array $expected The expected pattern data.
+
+ */
+ public function test_should_return_block_patterns( $theme, $expected ) {
+ $patterns = _wp_get_block_patterns( wp_get_theme( $theme ) );
+ $this->assertSameSets( $expected, $patterns );
+ }
+
+ /**
+ * @ticket 59490
+ */
+ public function test_delete_theme_cache() {
+ $theme = wp_get_theme( 'block-theme-patterns' );
+ _wp_get_block_patterns( $theme );
+ $transient = get_transient( 'wp_theme_patterns_block-theme-patterns' );
+ $this->assertSameSets(
+ array(
+ 'version' => '1.0.0',
+ 'patterns' => array(
+ 'cta.php' => array(
+ 'title' => 'Centered Call To Action',
+ 'slug' => 'block-theme-patterns/cta',
+ 'description' => '',
+ 'categories' => array( 'call-to-action' ),
+ ),
+ ),
+ ),
+ $transient,
+ 'The transient for block theme patterns should be set'
+ );
+ $theme->cache_delete();
+ $transient = get_transient( 'wp_theme_patterns_block-theme-patterns' );
+ $this->assertFalse(
+ $transient,
+ 'The transient for block theme patterns should have been cleared'
+ );
+ }
+
+ /**
+ * @ticket 59490
+ */
+ public function test_should_clear_transient_after_switching_theme() {
+ switch_theme( 'block-theme' );
+ _wp_get_block_patterns( wp_get_theme() );
+ $this->assertSameSets(
+ array(
+ 'version' => '1.0.0',
+ 'patterns' => array(),
+ ),
+ get_transient( 'wp_theme_patterns_block-theme' ),
+ 'The transient for block theme should be set'
+ );
+ switch_theme( 'block-theme-patterns' );
+ $this->assertFalse( get_transient( 'wp_theme_patterns_block-theme' ), 'Transient should not be set for block theme after switch theme' );
+ $this->assertFalse( get_transient( 'wp_theme_patterns_block-theme-patterns' ), 'Transient should not be set for block theme patterns before being requested' );
+ _wp_get_block_patterns( wp_get_theme() );
+ $transient = get_transient( 'wp_theme_patterns_block-theme-patterns' );
+ $this->assertSameSets(
+ array(
+ 'version' => '1.0.0',
+ 'patterns' => array(
+ 'cta.php' => array(
+ 'title' => 'Centered Call To Action',
+ 'slug' => 'block-theme-patterns/cta',
+ 'description' => '',
+ 'categories' => array( 'call-to-action' ),
+ ),
+ ),
+ ),
+ $transient,
+ 'The transient for block theme patterns should be set'
+ );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array[]
+ */
+ public function data_wp_get_block_patterns() {
+ return array(
+ array(
+ 'theme' => 'block-theme',
+ 'patterns' => array(
+ 'version' => '1.0.0',
+ 'patterns' => array(),
+ ),
+ ),
+ array(
+ 'theme' => 'block-theme-child',
+ 'patterns' => array(
+ 'version' => '1.0.0',
+ 'patterns' => array(),
+ ),
+ ),
+ array(
+ 'theme' => 'block-theme-patterns',
+ 'patterns' => array(
+ 'version' => '1.0.0',
+ 'patterns' => array(
+ 'cta.php' => array(
+ 'title' => 'Centered Call To Action',
+ 'slug' => 'block-theme-patterns/cta',
+ 'description' => '',
+ 'categories' => array( 'call-to-action' ),
+ ),
+ ),
+ ),
+ ),
+ array(
+ 'theme' => 'broken-theme',
+ 'patterns' => array(
+ 'version' => false,
+ 'patterns' => array(),
+ ),
+ ),
+ array(
+ 'theme' => 'invalid',
+ 'patterns' => array(
+ 'version' => false,
+ 'patterns' => array(),
+ ),
+ ),
+ );
+ }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/blocks/wpGetBlockPatterns.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunitteststhemethemeDirphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/theme/themeDir.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/theme/themeDir.php 2023-10-03 15:12:30 UTC (rev 56764)
+++ trunk/tests/phpunit/tests/theme/themeDir.php 2023-10-03 15:16:55 UTC (rev 56765)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -185,6 +185,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'Block Theme [0.4.0]',
</span><span class="cx" style="display: block; padding: 0 10px"> 'Block Theme [1.0.0] in subdirectory',
</span><span class="cx" style="display: block; padding: 0 10px"> 'Block Theme Deprecated Path',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'Block Theme Patterns',
</ins><span class="cx" style="display: block; padding: 0 10px"> 'Block Theme Post Content Default',
</span><span class="cx" style="display: block; padding: 0 10px"> 'Block Theme with defined Typography Fonts',
</span><span class="cx" style="display: block; padding: 0 10px"> 'Block Theme with Hooked Blocks',
</span></span></pre>
</div>
</div>
</body>
</html>