Make WordPress Core

Changeset 56978


Ignore:
Timestamp:
10/20/2023 07:06:46 PM (12 months ago)
Author:
joemcgill
Message:

Themes: Make caches for block patterns clearable.

In [56765], theme block pattern files were cached to a transient as a performance enhancement. However, transients are not easily clearable when caches are flushed on environments not using a persistent cache, which can lead to errors if the theme files are renamed, edited, or moved.

This changes the caching mechanism to use wp_cache_set() instead, and caches these values to the global group so they are still persistent on environments using an object cache, and will be cleared by a cache flush.

In addition, the helper _wp_get_block_patterns has been moved WP_Theme::get_block_patterns for consistency with other block related theme methods and cache helpers for these values, WP_Theme::get_pattern_cache and WP_Theme::set_pattern_cache, have been made private.

Relevant unit tests updated.

Props: afercia, flixos90, mukesh27, joemcgill.
Fixes #59633. See #59591, #59490.

Location:
trunk
Files:
3 edited
1 moved

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/block-patterns.php

    r56931 r56978  
    342342
    343343    foreach ( $themes as $theme ) {
    344         $patterns    = _wp_get_block_patterns( $theme );
     344        $patterns    = $theme->get_block_patterns();
    345345        $dirpath     = $theme->get_stylesheet_directory() . '/patterns/';
    346346        $text_domain = $theme->get( 'TextDomain' );
     
    388388}
    389389add_action( 'init', '_register_theme_block_patterns' );
    390 
    391 /**
    392  * Gets block pattern data for a specified theme.
    393  * Each pattern is defined as a PHP file and defines
    394  *  its metadata using plugin-style headers. The minimum required definition is:
    395  *
    396  *      /**
    397  *       * Title: My Pattern
    398  *       * Slug: my-theme/my-pattern
    399  *       *
    400  *
    401  *  The output of the PHP source corresponds to the content of the pattern, e.g.:
    402  *
    403  *      <main><p><?php echo "Hello"; ?></p></main>
    404  *
    405  *  If applicable, this will collect from both parent and child theme.
    406  *
    407  *  Other settable fields include:
    408  *
    409  *    - Description
    410  *    - Viewport Width
    411  *    - Inserter         (yes/no)
    412  *    - Categories       (comma-separated values)
    413  *    - Keywords         (comma-separated values)
    414  *    - Block Types      (comma-separated values)
    415  *    - Post Types       (comma-separated values)
    416  *    - Template Types   (comma-separated values)
    417  *
    418  * @since 6.4.0
    419  * @access private
    420  *
    421  * @param WP_Theme $theme Theme object.
    422  * @return array Block pattern data.
    423  */
    424 function _wp_get_block_patterns( WP_Theme $theme ) {
    425     $can_use_cached = ! wp_is_development_mode( 'theme' );
    426 
    427     $pattern_data = $theme->get_pattern_cache();
    428     if ( is_array( $pattern_data ) ) {
    429         if ( $can_use_cached ) {
    430             return $pattern_data;
    431         }
    432         // If in development mode, clear pattern cache.
    433         $theme->delete_pattern_cache();
    434     }
    435 
    436     $dirpath      = $theme->get_stylesheet_directory() . '/patterns/';
    437     $pattern_data = array();
    438 
    439     if ( ! file_exists( $dirpath ) ) {
    440         if ( $can_use_cached ) {
    441             $theme->set_pattern_cache( $pattern_data );
    442         }
    443         return $pattern_data;
    444     }
    445     $files = glob( $dirpath . '*.php' );
    446     if ( ! $files ) {
    447         if ( $can_use_cached ) {
    448             $theme->set_pattern_cache( $pattern_data );
    449         }
    450         return $pattern_data;
    451     }
    452 
    453     $default_headers = array(
    454         'title'         => 'Title',
    455         'slug'          => 'Slug',
    456         'description'   => 'Description',
    457         'viewportWidth' => 'Viewport Width',
    458         'inserter'      => 'Inserter',
    459         'categories'    => 'Categories',
    460         'keywords'      => 'Keywords',
    461         'blockTypes'    => 'Block Types',
    462         'postTypes'     => 'Post Types',
    463         'templateTypes' => 'Template Types',
    464     );
    465 
    466     $properties_to_parse = array(
    467         'categories',
    468         'keywords',
    469         'blockTypes',
    470         'postTypes',
    471         'templateTypes',
    472     );
    473 
    474     foreach ( $files as $file ) {
    475         $pattern = get_file_data( $file, $default_headers );
    476 
    477         if ( empty( $pattern['slug'] ) ) {
    478             _doing_it_wrong(
    479                 __FUNCTION__,
    480                 sprintf(
    481                     /* translators: 1: file name. */
    482                     __( 'Could not register file "%s" as a block pattern ("Slug" field missing)' ),
    483                     $file
    484                 ),
    485                 '6.0.0'
    486             );
    487             continue;
    488         }
    489 
    490         if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern['slug'] ) ) {
    491             _doing_it_wrong(
    492                 __FUNCTION__,
    493                 sprintf(
    494                     /* translators: 1: file name; 2: slug value found. */
    495                     __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")' ),
    496                     $file,
    497                     $pattern['slug']
    498                 ),
    499                 '6.0.0'
    500             );
    501         }
    502 
    503         // Title is a required property.
    504         if ( ! $pattern['title'] ) {
    505             _doing_it_wrong(
    506                 __FUNCTION__,
    507                 sprintf(
    508                     /* translators: 1: file name. */
    509                     __( 'Could not register file "%s" as a block pattern ("Title" field missing)' ),
    510                     $file
    511                 ),
    512                 '6.0.0'
    513             );
    514             continue;
    515         }
    516 
    517         // For properties of type array, parse data as comma-separated.
    518         foreach ( $properties_to_parse as $property ) {
    519             if ( ! empty( $pattern[ $property ] ) ) {
    520                 $pattern[ $property ] = array_filter( wp_parse_list( (string) $pattern[ $property ] ) );
    521             } else {
    522                 unset( $pattern[ $property ] );
    523             }
    524         }
    525 
    526         // Parse properties of type int.
    527         $property = 'viewportWidth';
    528         if ( ! empty( $pattern[ $property ] ) ) {
    529             $pattern[ $property ] = (int) $pattern[ $property ];
    530         } else {
    531             unset( $pattern[ $property ] );
    532         }
    533 
    534         // Parse properties of type bool.
    535         $property = 'inserter';
    536         if ( ! empty( $pattern[ $property ] ) ) {
    537             $pattern[ $property ] = in_array(
    538                 strtolower( $pattern[ $property ] ),
    539                 array( 'yes', 'true' ),
    540                 true
    541             );
    542         } else {
    543             unset( $pattern[ $property ] );
    544         }
    545 
    546         $key = str_replace( $dirpath, '', $file );
    547 
    548         $pattern_data[ $key ] = $pattern;
    549     }
    550 
    551     if ( $can_use_cached ) {
    552         $theme->set_pattern_cache( $pattern_data );
    553     }
    554 
    555     return $pattern_data;
    556 }
  • trunk/src/wp-includes/class-wp-theme.php

    r56943 r56978  
    846846
    847847    /**
    848      * Gets block pattern cache.
    849      *
    850      * @since 6.4.0
    851      *
    852      * @return array|false Returns an array of patterns if cache is found, otherwise false.
    853      */
    854     public function get_pattern_cache() {
    855         if ( ! $this->exists() ) {
    856             return false;
    857         }
    858         $pattern_data = get_transient( 'wp_theme_patterns_' . $this->stylesheet );
    859         if ( is_array( $pattern_data ) && $pattern_data['version'] === $this->get( 'Version' ) ) {
    860             return $pattern_data['patterns'];
    861         }
    862         return false;
    863     }
    864 
    865     /**
    866      * Sets block pattern cache.
    867      *
    868      * @since 6.4.0
    869      *
    870      * @param array $patterns Block patterns data to set in cache.
    871      */
    872     public function set_pattern_cache( array $patterns ) {
    873         $pattern_data = array(
    874             'version'  => $this->get( 'Version' ),
    875             'patterns' => $patterns,
    876         );
    877         set_transient( 'wp_theme_patterns_' . $this->stylesheet, $pattern_data );
    878     }
    879 
    880     /**
    881      * Clears block pattern cache.
    882      *
    883      * @since 6.4.0
    884      */
    885     public function delete_pattern_cache() {
    886         delete_transient( 'wp_theme_patterns_' . $this->stylesheet );
    887     }
    888 
    889     /**
    890848     * Gets a raw, unformatted theme header.
    891849     *
     
    18421800
    18431801    /**
     1802     * Gets block pattern data for a specified theme.
     1803     * Each pattern is defined as a PHP file and defines
     1804     * its metadata using plugin-style headers. The minimum required definition is:
     1805     *
     1806     *     /**
     1807     *      * Title: My Pattern
     1808     *      * Slug: my-theme/my-pattern
     1809     *      *
     1810     *
     1811     * The output of the PHP source corresponds to the content of the pattern, e.g.:
     1812     *
     1813     *     <main><p><?php echo "Hello"; ?></p></main>
     1814     *
     1815     * If applicable, this will collect from both parent and child theme.
     1816     *
     1817     * Other settable fields include:
     1818     *
     1819     *     - Description
     1820     *     - Viewport Width
     1821     *     - Inserter         (yes/no)
     1822     *     - Categories       (comma-separated values)
     1823     *     - Keywords         (comma-separated values)
     1824     *     - Block Types      (comma-separated values)
     1825     *     - Post Types       (comma-separated values)
     1826     *     - Template Types   (comma-separated values)
     1827     *
     1828     * @since 6.4.0
     1829     *
     1830     * @return array Block pattern data.
     1831     */
     1832    public function get_block_patterns() {
     1833        $can_use_cached = ! wp_is_development_mode( 'theme' );
     1834
     1835        $pattern_data = $this->get_pattern_cache();
     1836        if ( is_array( $pattern_data ) ) {
     1837            if ( $can_use_cached ) {
     1838                return $pattern_data;
     1839            }
     1840            // If in development mode, clear pattern cache.
     1841            $this->delete_pattern_cache();
     1842        }
     1843
     1844        $dirpath      = $this->get_stylesheet_directory() . '/patterns/';
     1845        $pattern_data = array();
     1846
     1847        if ( ! file_exists( $dirpath ) ) {
     1848            if ( $can_use_cached ) {
     1849                $this->set_pattern_cache( $pattern_data );
     1850            }
     1851            return $pattern_data;
     1852        }
     1853        $files = glob( $dirpath . '*.php' );
     1854        if ( ! $files ) {
     1855            if ( $can_use_cached ) {
     1856                $this->set_pattern_cache( $pattern_data );
     1857            }
     1858            return $pattern_data;
     1859        }
     1860
     1861        $default_headers = array(
     1862            'title'         => 'Title',
     1863            'slug'          => 'Slug',
     1864            'description'   => 'Description',
     1865            'viewportWidth' => 'Viewport Width',
     1866            'inserter'      => 'Inserter',
     1867            'categories'    => 'Categories',
     1868            'keywords'      => 'Keywords',
     1869            'blockTypes'    => 'Block Types',
     1870            'postTypes'     => 'Post Types',
     1871            'templateTypes' => 'Template Types',
     1872        );
     1873
     1874        $properties_to_parse = array(
     1875            'categories',
     1876            'keywords',
     1877            'blockTypes',
     1878            'postTypes',
     1879            'templateTypes',
     1880        );
     1881
     1882        foreach ( $files as $file ) {
     1883            $pattern = get_file_data( $file, $default_headers );
     1884
     1885            if ( empty( $pattern['slug'] ) ) {
     1886                _doing_it_wrong(
     1887                    __FUNCTION__,
     1888                    sprintf(
     1889                        /* translators: 1: file name. */
     1890                        __( 'Could not register file "%s" as a block pattern ("Slug" field missing)' ),
     1891                        $file
     1892                    ),
     1893                    '6.0.0'
     1894                );
     1895                continue;
     1896            }
     1897
     1898            if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern['slug'] ) ) {
     1899                _doing_it_wrong(
     1900                    __FUNCTION__,
     1901                    sprintf(
     1902                        /* translators: 1: file name; 2: slug value found. */
     1903                        __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")' ),
     1904                        $file,
     1905                        $pattern['slug']
     1906                    ),
     1907                    '6.0.0'
     1908                );
     1909            }
     1910
     1911            // Title is a required property.
     1912            if ( ! $pattern['title'] ) {
     1913                _doing_it_wrong(
     1914                    __FUNCTION__,
     1915                    sprintf(
     1916                        /* translators: 1: file name. */
     1917                        __( 'Could not register file "%s" as a block pattern ("Title" field missing)' ),
     1918                        $file
     1919                    ),
     1920                    '6.0.0'
     1921                );
     1922                continue;
     1923            }
     1924
     1925            // For properties of type array, parse data as comma-separated.
     1926            foreach ( $properties_to_parse as $property ) {
     1927                if ( ! empty( $pattern[ $property ] ) ) {
     1928                    $pattern[ $property ] = array_filter( wp_parse_list( (string) $pattern[ $property ] ) );
     1929                } else {
     1930                    unset( $pattern[ $property ] );
     1931                }
     1932            }
     1933
     1934            // Parse properties of type int.
     1935            $property = 'viewportWidth';
     1936            if ( ! empty( $pattern[ $property ] ) ) {
     1937                $pattern[ $property ] = (int) $pattern[ $property ];
     1938            } else {
     1939                unset( $pattern[ $property ] );
     1940            }
     1941
     1942            // Parse properties of type bool.
     1943            $property = 'inserter';
     1944            if ( ! empty( $pattern[ $property ] ) ) {
     1945                $pattern[ $property ] = in_array(
     1946                    strtolower( $pattern[ $property ] ),
     1947                    array( 'yes', 'true' ),
     1948                    true
     1949                );
     1950            } else {
     1951                unset( $pattern[ $property ] );
     1952            }
     1953
     1954            $key = str_replace( $dirpath, '', $file );
     1955
     1956            $pattern_data[ $key ] = $pattern;
     1957        }
     1958
     1959        if ( $can_use_cached ) {
     1960            $this->set_pattern_cache( $pattern_data );
     1961        }
     1962
     1963        return $pattern_data;
     1964    }
     1965
     1966    /**
     1967     * Gets block pattern cache.
     1968     *
     1969     * @since 6.4.0
     1970     *
     1971     * @return array|false Returns an array of patterns if cache is found, otherwise false.
     1972     */
     1973    private function get_pattern_cache() {
     1974        if ( ! $this->exists() ) {
     1975            return false;
     1976        }
     1977        $pattern_data = wp_cache_get( 'wp_theme_patterns_' . $this->stylesheet );
     1978        if ( is_array( $pattern_data ) && $pattern_data['version'] === $this->get( 'Version' ) ) {
     1979            return $pattern_data['patterns'];
     1980        }
     1981        return false;
     1982    }
     1983
     1984    /**
     1985     * Sets block pattern cache.
     1986     *
     1987     * @since 6.4.0
     1988     *
     1989     * @param array $patterns Block patterns data to set in cache.
     1990     */
     1991    private function set_pattern_cache( array $patterns ) {
     1992        $pattern_data = array(
     1993            'version'  => $this->get( 'Version' ),
     1994            'patterns' => $patterns,
     1995        );
     1996        wp_cache_set( 'wp_theme_patterns_' . $this->stylesheet, $pattern_data );
     1997    }
     1998
     1999    /**
     2000     * Clears block pattern cache.
     2001     *
     2002     * @since 6.4.0
     2003     */
     2004    public function delete_pattern_cache() {
     2005        wp_cache_delete( 'wp_theme_patterns_' . $this->stylesheet );
     2006    }
     2007
     2008    /**
    18442009     * Enables a theme for all sites on the current network.
    18452010     *
  • trunk/tests/phpunit/tests/theme/wpGetGlobalStylesheet.php

    r56042 r56978  
    2727
    2828    public function tear_down() {
     29        // Reset development mode after each test.
     30        unset( $GLOBALS['_wp_tests_development_mode'] );
     31
    2932        // Reset the theme support.
    3033        if ( $this->remove_theme_support_at_teardown ) {
  • trunk/tests/phpunit/tests/theme/wpThemeGetBlockPatterns.php

    r56977 r56978  
    11<?php
    22/**
    3  * Tests for _wp_get_block_patterns.
     3 * Tests for WP_Theme::get_block_patterns.
    44 *
    55 * @package WordPress
     
    88 *
    99 * @group blocks
     10 * @group themes
    1011 *
    11  * @covers ::_wp_get_block_patterns
     12 * @covers WP_Theme::get_block_patterns
    1213 */
    13 class Tests_Blocks_WpGetBlockPatterns extends WP_UnitTestCase {
     14class Tests_Theme_WPThemeGetBlockPatterns extends WP_UnitTestCase {
     15
     16    public static function wpSetUpBeforeClass() {
     17        // Ensure development mode is reset before running these tests.
     18        unset( $GLOBALS['_wp_tests_development_mode'] );
     19    }
     20
     21    public static function wpTearDownAfterClass() {
     22        // Ensure development mode is reset after running these tests.
     23        unset( $GLOBALS['_wp_tests_development_mode'] );
     24    }
     25
     26    /**
     27     * Test helper to access the private get_pattern_cache method of a theme.
     28     *
     29     * @param WP_Theme $wp_theme A WP_Theme object.
     30     * @return array|false Returns an array of patterns if cache is found, otherwise false.
     31     */
     32    private function get_pattern_cache( $wp_theme ) {
     33        $reflection = new ReflectionMethod( $wp_theme, 'get_pattern_cache' );
     34        $reflection->setAccessible( true );
     35
     36        $pattern_cache = $reflection->invoke( $wp_theme, 'get_pattern_cache' );
     37        $reflection->setAccessible( false );
     38
     39        return $pattern_cache;
     40    }
     41
    1442    /**
    1543     * @ticket 59490
    1644     *
    17      * @dataProvider data_wp_get_block_patterns
     45     * @dataProvider data_get_block_patterns
    1846     *
    19      * @param string $theme    The theme's slug.
    20      * @param array  $expected The expected pattern data.
     47     * @param string $theme_slug The theme's slug.
     48     * @param array  $expected   The expected pattern data.
    2149     */
    22     public function test_should_return_block_patterns( $theme, $expected ) {
    23         $patterns = _wp_get_block_patterns( wp_get_theme( $theme ) );
     50    public function test_should_return_block_patterns( $theme_slug, $expected ) {
     51        $theme    = wp_get_theme( $theme_slug );
     52        $patterns = $theme->get_block_patterns();
    2453        $this->assertSameSets( $expected, $patterns );
    2554    }
     
    2756    /**
    2857     * @ticket 59490
     58     *
     59     * @covers WP_Theme::delete_pattern_cache
    2960     */
    30     public function test_delete_theme_cache() {
     61    public function test_delete_pattern_cache() {
    3162        $theme = wp_get_theme( 'block-theme-patterns' );
    32         _wp_get_block_patterns( $theme );
     63
     64        $this->assertTrue( $theme->exists(), 'The test theme could not be found.' );
     65
     66        $theme->get_block_patterns();
     67
    3368        $this->assertSameSets(
    3469            array(
     
    4075                ),
    4176            ),
    42             $theme->get_pattern_cache(),
    43             'The transient for block theme patterns should be set'
     77            $this->get_pattern_cache( $theme ),
     78            'The cache for block theme patterns should match the expected.'
    4479        );
    4580        $theme->delete_pattern_cache();
    4681        $this->assertFalse(
    47             $theme->get_pattern_cache(),
    48             'The transient for block theme patterns should have been cleared'
     82            $this->get_pattern_cache( $theme ),
     83            'The cache for block theme patterns should have been cleared.'
    4984        );
    5085    }
     
    5388     * @ticket 59490
    5489     */
    55     public function test_should_clear_transient_after_switching_theme() {
     90    public function test_should_clear_cache_after_switching_theme() {
    5691        switch_theme( 'block-theme' );
    5792        $theme1 = wp_get_theme();
    58         _wp_get_block_patterns( $theme1 );
     93
     94        $this->assertTrue( $theme1->exists(), 'The block-theme test theme could not be found.' );
     95
     96        $theme1->get_block_patterns();
    5997        $this->assertSameSets(
    6098            array(),
    61             $theme1->get_pattern_cache(),
    62             'The transient for block theme should be set'
     99            $this->get_pattern_cache( $theme1 ),
     100            'The cache for block theme should be empty.'
    63101        );
     102
    64103        switch_theme( 'block-theme-patterns' );
    65         $this->assertFalse( $theme1->get_pattern_cache(), 'Transient should not be set for block theme after switch theme' );
     104
    66105        $theme2 = wp_get_theme();
    67         $this->assertFalse( $theme2->get_pattern_cache(), 'Transient should not be set for block theme patterns before being requested' );
    68         _wp_get_block_patterns( $theme2 );
     106        $this->assertTrue( $theme2->exists(), 'The block-theme-patterns test theme could not be found.' );
     107
     108        $this->assertFalse( $this->get_pattern_cache( $theme1 ), 'Cache should not be set for block theme after switch theme.' );
     109        $this->assertFalse( $this->get_pattern_cache( $theme2 ), 'Cache should not be set for block theme patterns before being requested.' );
     110
     111        $theme2->get_block_patterns( $theme2 );
    69112        $this->assertSameSets(
    70113            array(
     
    77120
    78121            ),
    79             $theme2->get_pattern_cache(),
    80             'The transient for block theme patterns should be set'
     122            $this->get_pattern_cache( $theme2 ),
     123            'The cache for block theme patterns should match the expected.'
    81124        );
    82125    }
     
    87130     * @return array[]
    88131     */
    89     public function data_wp_get_block_patterns() {
     132    public function data_get_block_patterns() {
    90133        return array(
    91134            array(
     
    120163
    121164    /**
    122      * Tests that _wp_get_block_patterns() clears existing transient when in theme development mode.
     165     * Tests that WP_Theme::get_block_patterns() clears existing cache when in theme development mode.
    123166     *
    124167     * @ticket 59591
    125168     */
    126     public function test_should_clear_existing_transient_when_in_development_mode() {
     169    public function test_should_clear_existing_cache_when_in_development_mode() {
    127170        $theme = wp_get_theme( 'block-theme-patterns' );
    128171
     172        $this->assertTrue( $theme->exists(), 'The test theme could not be found.' );
     173
    129174        // Calling the function should set the cache.
    130         _wp_get_block_patterns( $theme );
     175        $theme->get_block_patterns();
    131176        $this->assertSameSets(
    132177            array(
     
    138183                ),
    139184            ),
    140             $theme->get_pattern_cache(),
    141             'The transient for block theme patterns should be set'
     185            $this->get_pattern_cache( $theme ),
     186            'The cache for block theme patterns should be set.'
    142187        );
    143188
    144189        // Calling the function while in theme development mode should clear the cache.
    145190        $GLOBALS['_wp_tests_development_mode'] = 'theme';
    146         _wp_get_block_patterns( $theme );
     191        $theme->get_block_patterns( $theme );
    147192        unset( $GLOBALS['_wp_tests_development_mode'] ); // Reset to not pollute other tests.
    148193        $this->assertFalse(
    149             $theme->get_pattern_cache(),
    150             'The transient for block theme patterns should have been cleared due to theme development mode'
     194            $this->get_pattern_cache( $theme ),
     195            'The cache for block theme patterns should have been cleared due to theme development mode.'
    151196        );
    152197    }
Note: See TracChangeset for help on using the changeset viewer.