Skip to content

Commit 3ad2018

Browse files
committed
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 #59490 git-svn-id: https://develop.svn.wordpress.org/trunk@56765 602fd350-edb4-49c9-b593-d223f7449a82
1 parent d5505fc commit 3ad2018

8 files changed

Lines changed: 410 additions & 142 deletions

File tree

src/wp-admin/includes/theme.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ function delete_theme( $stylesheet, $redirect = '' ) {
8181
*/
8282
do_action( 'delete_theme', $stylesheet );
8383

84+
$theme = wp_get_theme( $stylesheet );
85+
8486
$themes_dir = trailingslashit( $themes_dir );
8587
$theme_dir = trailingslashit( $themes_dir . $stylesheet );
8688
$deleted = $wp_filesystem->delete( $theme_dir, true );
@@ -125,6 +127,9 @@ function delete_theme( $stylesheet, $redirect = '' ) {
125127
WP_Theme::network_disable_theme( $stylesheet );
126128
}
127129

130+
// Clear theme caches.
131+
$theme->cache_delete();
132+
128133
// Force refresh of theme update information.
129134
delete_site_transient( 'update_themes' );
130135

src/wp-includes/block-patterns.php

Lines changed: 201 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -319,37 +319,132 @@ function _register_remote_theme_patterns() {
319319

320320
/**
321321
* Register any patterns that the active theme may provide under its
322-
* `./patterns/` directory. Each pattern is defined as a PHP file and defines
323-
* its metadata using plugin-style headers. The minimum required definition is:
322+
* `./patterns/` directory.
324323
*
325-
* /**
326-
* * Title: My Pattern
327-
* * Slug: my-theme/my-pattern
328-
* *
324+
* @since 6.0.0
325+
* @since 6.1.0 The `postTypes` property was added.
326+
* @since 6.2.0 The `templateTypes` property was added.
327+
* @since 6.4.0 Uses the `_wp_get_block_patterns` function.
328+
* @access private
329+
*/
330+
function _register_theme_block_patterns() {
331+
/*
332+
* Register patterns for the active theme. If the theme is a child theme,
333+
* let it override any patterns from the parent theme that shares the same slug.
334+
*/
335+
$themes = array();
336+
$theme = wp_get_theme();
337+
$themes[] = $theme;
338+
if ( $theme->parent() ) {
339+
$themes[] = $theme->parent();
340+
}
341+
$registry = WP_Block_Patterns_Registry::get_instance();
342+
343+
foreach ( $themes as $theme ) {
344+
$pattern_data = _wp_get_block_patterns( $theme );
345+
$dirpath = $theme->get_stylesheet_directory() . '/patterns/';
346+
$text_domain = $theme->get( 'TextDomain' );
347+
348+
foreach ( $pattern_data['patterns'] as $file => $pattern_data ) {
349+
if ( $registry->is_registered( $pattern_data['slug'] ) ) {
350+
continue;
351+
}
352+
353+
// The actual pattern content is the output of the file.
354+
ob_start();
355+
include $dirpath . $file;
356+
$pattern_data['content'] = ob_get_clean();
357+
if ( ! $pattern_data['content'] ) {
358+
continue;
359+
}
360+
361+
// Translate the pattern metadata.
362+
// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain,WordPress.WP.I18n.LowLevelTranslationFunction
363+
$pattern_data['title'] = translate_with_gettext_context( $pattern_data['title'], 'Pattern title', $text_domain );
364+
if ( ! empty( $pattern_data['description'] ) ) {
365+
// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain,WordPress.WP.I18n.LowLevelTranslationFunction
366+
$pattern_data['description'] = translate_with_gettext_context( $pattern_data['description'], 'Pattern description', $text_domain );
367+
}
368+
369+
register_block_pattern( $pattern_data['slug'], $pattern_data );
370+
}
371+
}
372+
}
373+
add_action( 'init', '_register_theme_block_patterns' );
374+
375+
/**
376+
* Gets block pattern data for a specified theme.
377+
* Each pattern is defined as a PHP file and defines
378+
* its metadata using plugin-style headers. The minimum required definition is:
329379
*
330-
* The output of the PHP source corresponds to the content of the pattern, e.g.:
380+
* /**
381+
* * Title: My Pattern
382+
* * Slug: my-theme/my-pattern
383+
* *
331384
*
332-
* <main><p><?php echo "Hello"; ?></p></main>
385+
* The output of the PHP source corresponds to the content of the pattern, e.g.:
333386
*
334-
* If applicable, this will collect from both parent and child theme.
387+
* <main><p><?php echo "Hello"; ?></p></main>
335388
*
336-
* Other settable fields include:
389+
* If applicable, this will collect from both parent and child theme.
337390
*
338-
* - Description
339-
* - Viewport Width
340-
* - Inserter (yes/no)
341-
* - Categories (comma-separated values)
342-
* - Keywords (comma-separated values)
343-
* - Block Types (comma-separated values)
344-
* - Post Types (comma-separated values)
345-
* - Template Types (comma-separated values)
391+
* Other settable fields include:
346392
*
347-
* @since 6.0.0
348-
* @since 6.1.0 The `postTypes` property was added.
349-
* @since 6.2.0 The `templateTypes` property was added.
393+
* - Description
394+
* - Viewport Width
395+
* - Inserter (yes/no)
396+
* - Categories (comma-separated values)
397+
* - Keywords (comma-separated values)
398+
* - Block Types (comma-separated values)
399+
* - Post Types (comma-separated values)
400+
* - Template Types (comma-separated values)
401+
*
402+
* @since 6.4.0
350403
* @access private
404+
*
405+
* @param WP_Theme $theme Theme object.
406+
* @return array Block pattern data.
351407
*/
352-
function _register_theme_block_patterns() {
408+
409+
function _wp_get_block_patterns( WP_Theme $theme ) {
410+
if ( ! $theme->exists() ) {
411+
return array(
412+
'version' => false,
413+
'patterns' => array(),
414+
);
415+
}
416+
417+
$transient_name = 'wp_theme_patterns_' . $theme->get_stylesheet();
418+
$version = $theme->get( 'Version' );
419+
$can_use_cached = ! wp_is_development_mode( 'theme' );
420+
421+
if ( $can_use_cached ) {
422+
$pattern_data = get_transient( $transient_name );
423+
if ( is_array( $pattern_data ) && $pattern_data['version'] === $version ) {
424+
return $pattern_data;
425+
}
426+
}
427+
428+
$pattern_data = array(
429+
'version' => $version,
430+
'patterns' => array(),
431+
);
432+
$dirpath = $theme->get_stylesheet_directory() . '/patterns/';
433+
434+
if ( ! file_exists( $dirpath ) ) {
435+
if ( $can_use_cached ) {
436+
set_transient( $transient_name, $pattern_data );
437+
}
438+
return $pattern_data;
439+
}
440+
$files = glob( $dirpath . '*.php' );
441+
if ( ! $files ) {
442+
if ( $can_use_cached ) {
443+
set_transient( $transient_name, $pattern_data );
444+
}
445+
return $pattern_data;
446+
}
447+
353448
$default_headers = array(
354449
'title' => 'Title',
355450
'slug' => 'Slug',
@@ -363,130 +458,94 @@ function _register_theme_block_patterns() {
363458
'templateTypes' => 'Template Types',
364459
);
365460

366-
/*
367-
* Register patterns for the active theme. If the theme is a child theme,
368-
* let it override any patterns from the parent theme that shares the same slug.
369-
*/
370-
$themes = array();
371-
$stylesheet = get_stylesheet();
372-
$template = get_template();
373-
if ( $stylesheet !== $template ) {
374-
$themes[] = wp_get_theme( $stylesheet );
375-
}
376-
$themes[] = wp_get_theme( $template );
461+
$properties_to_parse = array(
462+
'categories',
463+
'keywords',
464+
'blockTypes',
465+
'postTypes',
466+
'templateTypes',
467+
);
377468

378-
foreach ( $themes as $theme ) {
379-
$dirpath = $theme->get_stylesheet_directory() . '/patterns/';
380-
if ( ! is_dir( $dirpath ) || ! is_readable( $dirpath ) ) {
469+
foreach ( $files as $file ) {
470+
$pattern = get_file_data( $file, $default_headers );
471+
472+
if ( empty( $pattern['slug'] ) ) {
473+
_doing_it_wrong(
474+
__FUNCTION__,
475+
sprintf(
476+
/* translators: %s: file name. */
477+
__( 'Could not register file "%s" as a block pattern ("Slug" field missing)' ),
478+
$file
479+
),
480+
'6.0.0'
481+
);
381482
continue;
382483
}
383-
if ( file_exists( $dirpath ) ) {
384-
$files = glob( $dirpath . '*.php' );
385-
if ( $files ) {
386-
foreach ( $files as $file ) {
387-
$pattern_data = get_file_data( $file, $default_headers );
388-
389-
if ( empty( $pattern_data['slug'] ) ) {
390-
_doing_it_wrong(
391-
'_register_theme_block_patterns',
392-
sprintf(
393-
/* translators: %s: file name. */
394-
__( 'Could not register file "%s" as a block pattern ("Slug" field missing)' ),
395-
$file
396-
),
397-
'6.0.0'
398-
);
399-
continue;
400-
}
401-
402-
if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern_data['slug'] ) ) {
403-
_doing_it_wrong(
404-
'_register_theme_block_patterns',
405-
sprintf(
406-
/* translators: %1s: file name; %2s: slug value found. */
407-
__( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")' ),
408-
$file,
409-
$pattern_data['slug']
410-
),
411-
'6.0.0'
412-
);
413-
}
414-
415-
if ( WP_Block_Patterns_Registry::get_instance()->is_registered( $pattern_data['slug'] ) ) {
416-
continue;
417-
}
418-
419-
// Title is a required property.
420-
if ( ! $pattern_data['title'] ) {
421-
_doing_it_wrong(
422-
'_register_theme_block_patterns',
423-
sprintf(
424-
/* translators: %1s: file name; %2s: slug value found. */
425-
__( 'Could not register file "%s" as a block pattern ("Title" field missing)' ),
426-
$file
427-
),
428-
'6.0.0'
429-
);
430-
continue;
431-
}
432-
433-
// For properties of type array, parse data as comma-separated.
434-
foreach ( array( 'categories', 'keywords', 'blockTypes', 'postTypes', 'templateTypes' ) as $property ) {
435-
if ( ! empty( $pattern_data[ $property ] ) ) {
436-
$pattern_data[ $property ] = array_filter(
437-
preg_split(
438-
'/[\s,]+/',
439-
(string) $pattern_data[ $property ]
440-
)
441-
);
442-
} else {
443-
unset( $pattern_data[ $property ] );
444-
}
445-
}
446-
447-
// Parse properties of type int.
448-
foreach ( array( 'viewportWidth' ) as $property ) {
449-
if ( ! empty( $pattern_data[ $property ] ) ) {
450-
$pattern_data[ $property ] = (int) $pattern_data[ $property ];
451-
} else {
452-
unset( $pattern_data[ $property ] );
453-
}
454-
}
455-
456-
// Parse properties of type bool.
457-
foreach ( array( 'inserter' ) as $property ) {
458-
if ( ! empty( $pattern_data[ $property ] ) ) {
459-
$pattern_data[ $property ] = in_array(
460-
strtolower( $pattern_data[ $property ] ),
461-
array( 'yes', 'true' ),
462-
true
463-
);
464-
} else {
465-
unset( $pattern_data[ $property ] );
466-
}
467-
}
468-
469-
// Translate the pattern metadata.
470-
$text_domain = $theme->get( 'TextDomain' );
471-
// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain,WordPress.WP.I18n.LowLevelTranslationFunction
472-
$pattern_data['title'] = translate_with_gettext_context( $pattern_data['title'], 'Pattern title', $text_domain );
473-
if ( ! empty( $pattern_data['description'] ) ) {
474-
// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralDomain,WordPress.WP.I18n.LowLevelTranslationFunction
475-
$pattern_data['description'] = translate_with_gettext_context( $pattern_data['description'], 'Pattern description', $text_domain );
476-
}
477-
478-
// The actual pattern content is the output of the file.
479-
ob_start();
480-
include $file;
481-
$pattern_data['content'] = ob_get_clean();
482-
if ( ! $pattern_data['content'] ) {
483-
continue;
484-
}
485-
486-
register_block_pattern( $pattern_data['slug'], $pattern_data );
487-
}
484+
485+
if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern['slug'] ) ) {
486+
_doing_it_wrong(
487+
__FUNCTION__,
488+
sprintf(
489+
/* translators: %1s: file name; %2s: slug value found. */
490+
__( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")' ),
491+
$file,
492+
$pattern['slug']
493+
),
494+
'6.0.0'
495+
);
496+
}
497+
498+
// Title is a required property.
499+
if ( ! $pattern['title'] ) {
500+
_doing_it_wrong(
501+
__FUNCTION__,
502+
sprintf(
503+
/* translators: %1s: file name. */
504+
__( 'Could not register file "%s" as a block pattern ("Title" field missing)' ),
505+
$file
506+
),
507+
'6.0.0'
508+
);
509+
continue;
510+
}
511+
512+
// For properties of type array, parse data as comma-separated.
513+
foreach ( $properties_to_parse as $property ) {
514+
if ( ! empty( $pattern[ $property ] ) ) {
515+
$pattern[ $property ] = array_filter( wp_parse_list( (string) $pattern[ $property ] ) );
516+
} else {
517+
unset( $pattern[ $property ] );
488518
}
489519
}
520+
521+
// Parse properties of type int.
522+
$property = 'viewportWidth';
523+
if ( ! empty( $pattern[ $property ] ) ) {
524+
$pattern[ $property ] = (int) $pattern[ $property ];
525+
} else {
526+
unset( $pattern[ $property ] );
527+
}
528+
529+
// Parse properties of type bool.
530+
$property = 'inserter';
531+
if ( ! empty( $pattern[ $property ] ) ) {
532+
$pattern[ $property ] = in_array(
533+
strtolower( $pattern[ $property ] ),
534+
array( 'yes', 'true' ),
535+
true
536+
);
537+
} else {
538+
unset( $pattern[ $property ] );
539+
}
540+
541+
$key = str_replace( $dirpath, '', $file );
542+
543+
$pattern_data['patterns'][ $key ] = $pattern;
490544
}
545+
546+
if ( $can_use_cached ) {
547+
set_transient( $transient_name, $pattern_data );
548+
}
549+
550+
return $pattern_data;
491551
}
492-
add_action( 'init', '_register_theme_block_patterns' );

0 commit comments

Comments
 (0)