diff --git a/src/wp-includes/block-patterns.php b/src/wp-includes/block-patterns.php index a1a7a67d81..7f20d9a894 100644 --- a/src/wp-includes/block-patterns.php +++ b/src/wp-includes/block-patterns.php @@ -341,7 +341,7 @@ function _register_theme_block_patterns() { $registry = WP_Block_Patterns_Registry::get_instance(); foreach ( $themes as $theme ) { - $patterns = _wp_get_block_patterns( $theme ); + $patterns = $theme->get_block_patterns(); $dirpath = $theme->get_stylesheet_directory() . '/patterns/'; $text_domain = $theme->get( 'TextDomain' ); @@ -387,170 +387,3 @@ function _register_theme_block_patterns() { } } 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 ) { - $can_use_cached = ! wp_is_development_mode( 'theme' ); - - $pattern_data = $theme->get_pattern_cache(); - if ( is_array( $pattern_data ) ) { - if ( $can_use_cached ) { - return $pattern_data; - } - // If in development mode, clear pattern cache. - $theme->delete_pattern_cache(); - } - - $dirpath = $theme->get_stylesheet_directory() . '/patterns/'; - $pattern_data = array(); - - if ( ! file_exists( $dirpath ) ) { - if ( $can_use_cached ) { - $theme->set_pattern_cache( $pattern_data ); - } - return $pattern_data; - } - $files = glob( $dirpath . '*.php' ); - if ( ! $files ) { - if ( $can_use_cached ) { - $theme->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 ) { - $theme->set_pattern_cache( $pattern_data ); - } - - return $pattern_data; -} diff --git a/src/wp-includes/class-wp-theme.php b/src/wp-includes/class-wp-theme.php index 84219cd921..09905bee1b 100644 --- a/src/wp-includes/class-wp-theme.php +++ b/src/wp-includes/class-wp-theme.php @@ -844,48 +844,6 @@ final class WP_Theme implements ArrayAccess { $this->delete_pattern_cache(); } - /** - * Gets block pattern cache. - * - * @since 6.4.0 - * - * @return array|false Returns an array of patterns if cache is found, otherwise false. - */ - public function get_pattern_cache() { - if ( ! $this->exists() ) { - return false; - } - $pattern_data = get_transient( 'wp_theme_patterns_' . $this->stylesheet ); - 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 - * - * @param array $patterns Block patterns data to set in cache. - */ - public function set_pattern_cache( array $patterns ) { - $pattern_data = array( - 'version' => $this->get( 'Version' ), - 'patterns' => $patterns, - ); - set_transient( 'wp_theme_patterns_' . $this->stylesheet, $pattern_data ); - } - - /** - * Clears block pattern cache. - * - * @since 6.4.0 - */ - public function delete_pattern_cache() { - delete_transient( 'wp_theme_patterns_' . $this->stylesheet ); - } - /** * Gets a raw, unformatted theme header. * @@ -1840,6 +1798,213 @@ final class WP_Theme implements ArrayAccess { 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.: + * + *

+ * + * 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 + * + * @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 = wp_cache_get( 'wp_theme_patterns_' . $this->stylesheet ); + 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 + * + * @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, + ); + wp_cache_set( 'wp_theme_patterns_' . $this->stylesheet, $pattern_data ); + } + + /** + * Clears block pattern cache. + * + * @since 6.4.0 + */ + public function delete_pattern_cache() { + wp_cache_delete( 'wp_theme_patterns_' . $this->stylesheet ); + } + /** * Enables a theme for all sites on the current network. * diff --git a/tests/phpunit/tests/blocks/wpGetBlockPatterns.php b/tests/phpunit/tests/blocks/wpGetBlockPatterns.php deleted file mode 100644 index 4e7f67612b..0000000000 --- a/tests/phpunit/tests/blocks/wpGetBlockPatterns.php +++ /dev/null @@ -1,153 +0,0 @@ -assertSameSets( $expected, $patterns ); - } - - /** - * @ticket 59490 - */ - public function test_delete_theme_cache() { - $theme = wp_get_theme( 'block-theme-patterns' ); - _wp_get_block_patterns( $theme ); - $this->assertSameSets( - array( - 'cta.php' => array( - 'title' => 'Centered Call To Action', - 'slug' => 'block-theme-patterns/cta', - 'description' => '', - 'categories' => array( 'call-to-action' ), - ), - ), - $theme->get_pattern_cache(), - 'The transient for block theme patterns should be set' - ); - $theme->delete_pattern_cache(); - $this->assertFalse( - $theme->get_pattern_cache(), - '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' ); - $theme1 = wp_get_theme(); - _wp_get_block_patterns( $theme1 ); - $this->assertSameSets( - array(), - $theme1->get_pattern_cache(), - 'The transient for block theme should be set' - ); - switch_theme( 'block-theme-patterns' ); - $this->assertFalse( $theme1->get_pattern_cache(), 'Transient should not be set for block theme after switch theme' ); - $theme2 = wp_get_theme(); - $this->assertFalse( $theme2->get_pattern_cache(), 'Transient should not be set for block theme patterns before being requested' ); - _wp_get_block_patterns( $theme2 ); - $this->assertSameSets( - array( - 'cta.php' => array( - 'title' => 'Centered Call To Action', - 'slug' => 'block-theme-patterns/cta', - 'description' => '', - 'categories' => array( 'call-to-action' ), - ), - - ), - $theme2->get_pattern_cache(), - '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(), - ), - array( - 'theme' => 'block-theme-child', - 'patterns' => array(), - ), - array( - 'theme' => 'block-theme-patterns', - '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(), - ), - array( - 'theme' => 'invalid', - 'patterns' => array(), - ), - ); - } - - /** - * Tests that _wp_get_block_patterns() clears existing transient when in theme development mode. - * - * @ticket 59591 - */ - public function test_should_clear_existing_transient_when_in_development_mode() { - $theme = wp_get_theme( 'block-theme-patterns' ); - - // Calling the function should set the cache. - _wp_get_block_patterns( $theme ); - $this->assertSameSets( - array( - 'cta.php' => array( - 'title' => 'Centered Call To Action', - 'slug' => 'block-theme-patterns/cta', - 'description' => '', - 'categories' => array( 'call-to-action' ), - ), - ), - $theme->get_pattern_cache(), - 'The transient for block theme patterns should be set' - ); - - // Calling the function while in theme development mode should clear the cache. - $GLOBALS['_wp_tests_development_mode'] = 'theme'; - _wp_get_block_patterns( $theme ); - unset( $GLOBALS['_wp_tests_development_mode'] ); // Reset to not pollute other tests. - $this->assertFalse( - $theme->get_pattern_cache(), - 'The transient for block theme patterns should have been cleared due to theme development mode' - ); - } -} diff --git a/tests/phpunit/tests/theme/wpGetGlobalStylesheet.php b/tests/phpunit/tests/theme/wpGetGlobalStylesheet.php index 40d87940b4..3035467a01 100644 --- a/tests/phpunit/tests/theme/wpGetGlobalStylesheet.php +++ b/tests/phpunit/tests/theme/wpGetGlobalStylesheet.php @@ -26,6 +26,9 @@ class Tests_Theme_WpGetGlobalStylesheet extends WP_Theme_UnitTestCase { private $switch_to_default_theme_at_teardown = false; public function tear_down() { + // Reset development mode after each test. + unset( $GLOBALS['_wp_tests_development_mode'] ); + // Reset the theme support. if ( $this->remove_theme_support_at_teardown ) { $this->remove_theme_support_at_teardown = false; diff --git a/tests/phpunit/tests/theme/wpThemeGetBlockPatterns.php b/tests/phpunit/tests/theme/wpThemeGetBlockPatterns.php new file mode 100644 index 0000000000..caaf189a16 --- /dev/null +++ b/tests/phpunit/tests/theme/wpThemeGetBlockPatterns.php @@ -0,0 +1,198 @@ +setAccessible( true ); + + $pattern_cache = $reflection->invoke( $wp_theme, 'get_pattern_cache' ); + $reflection->setAccessible( false ); + + return $pattern_cache; + } + + /** + * @ticket 59490 + * + * @dataProvider data_get_block_patterns + * + * @param string $theme_slug The theme's slug. + * @param array $expected The expected pattern data. + */ + public function test_should_return_block_patterns( $theme_slug, $expected ) { + $theme = wp_get_theme( $theme_slug ); + $patterns = $theme->get_block_patterns(); + $this->assertSameSets( $expected, $patterns ); + } + + /** + * @ticket 59490 + * + * @covers WP_Theme::delete_pattern_cache + */ + public function test_delete_pattern_cache() { + $theme = wp_get_theme( 'block-theme-patterns' ); + + $this->assertTrue( $theme->exists(), 'The test theme could not be found.' ); + + $theme->get_block_patterns(); + + $this->assertSameSets( + array( + 'cta.php' => array( + 'title' => 'Centered Call To Action', + 'slug' => 'block-theme-patterns/cta', + 'description' => '', + 'categories' => array( 'call-to-action' ), + ), + ), + $this->get_pattern_cache( $theme ), + 'The cache for block theme patterns should match the expected.' + ); + $theme->delete_pattern_cache(); + $this->assertFalse( + $this->get_pattern_cache( $theme ), + 'The cache for block theme patterns should have been cleared.' + ); + } + + /** + * @ticket 59490 + */ + public function test_should_clear_cache_after_switching_theme() { + switch_theme( 'block-theme' ); + $theme1 = wp_get_theme(); + + $this->assertTrue( $theme1->exists(), 'The block-theme test theme could not be found.' ); + + $theme1->get_block_patterns(); + $this->assertSameSets( + array(), + $this->get_pattern_cache( $theme1 ), + 'The cache for block theme should be empty.' + ); + + switch_theme( 'block-theme-patterns' ); + + $theme2 = wp_get_theme(); + $this->assertTrue( $theme2->exists(), 'The block-theme-patterns test theme could not be found.' ); + + $this->assertFalse( $this->get_pattern_cache( $theme1 ), 'Cache should not be set for block theme after switch theme.' ); + $this->assertFalse( $this->get_pattern_cache( $theme2 ), 'Cache should not be set for block theme patterns before being requested.' ); + + $theme2->get_block_patterns( $theme2 ); + $this->assertSameSets( + array( + 'cta.php' => array( + 'title' => 'Centered Call To Action', + 'slug' => 'block-theme-patterns/cta', + 'description' => '', + 'categories' => array( 'call-to-action' ), + ), + + ), + $this->get_pattern_cache( $theme2 ), + 'The cache for block theme patterns should match the expected.' + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_get_block_patterns() { + return array( + array( + 'theme' => 'block-theme', + 'patterns' => array(), + ), + array( + 'theme' => 'block-theme-child', + 'patterns' => array(), + ), + array( + 'theme' => 'block-theme-patterns', + '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(), + ), + array( + 'theme' => 'invalid', + 'patterns' => array(), + ), + ); + } + + /** + * Tests that WP_Theme::get_block_patterns() clears existing cache when in theme development mode. + * + * @ticket 59591 + */ + public function test_should_clear_existing_cache_when_in_development_mode() { + $theme = wp_get_theme( 'block-theme-patterns' ); + + $this->assertTrue( $theme->exists(), 'The test theme could not be found.' ); + + // Calling the function should set the cache. + $theme->get_block_patterns(); + $this->assertSameSets( + array( + 'cta.php' => array( + 'title' => 'Centered Call To Action', + 'slug' => 'block-theme-patterns/cta', + 'description' => '', + 'categories' => array( 'call-to-action' ), + ), + ), + $this->get_pattern_cache( $theme ), + 'The cache for block theme patterns should be set.' + ); + + // Calling the function while in theme development mode should clear the cache. + $GLOBALS['_wp_tests_development_mode'] = 'theme'; + $theme->get_block_patterns( $theme ); + unset( $GLOBALS['_wp_tests_development_mode'] ); // Reset to not pollute other tests. + $this->assertFalse( + $this->get_pattern_cache( $theme ), + 'The cache for block theme patterns should have been cleared due to theme development mode.' + ); + } +}