Make WordPress Core

Changeset 53333


Ignore:
Timestamp:
05/02/2022 01:58:48 PM (4 years ago)
Author:
hellofromTonya
Message:

REST API: Fixes /wp/v2/pattern-directory/patterns endpoint response for slug parameter.

[53218] introduced a bug of a wrong response from the wp/v2/pattern-directory/patterns endpoint with a slug parameter. As the response is cached, it can result in an incorrect list of available patterns supported by the current theme.

This commit resolves by:

  • Limiting the slug to an array in the query parameters.
  • When set, parsing and sorting the slug(s) and then serializing the sorted query args as part of the hashed transient keys.

Props antonvlasenko, timothyblynjacobs, spacedmonkey, costdev, hellofromTonya.

Follow-up to [53218], [53152], [51208].
Fixes #55617.

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php

    r53218 r53333  
    120120                }
    121121
    122                 /*
    123                  * Include a hash of the query args, so that different requests are stored in
    124                  * separate caches.
    125                  *
    126                  * MD5 is chosen for its speed, low-collision rate, universal availability, and to stay
    127                  * under the character limit for `_site_transient_timeout_{...}` keys.
    128                  *
    129                  * @link https://stackoverflow.com/questions/3665247/fastest-hash-for-non-cryptographic-uses
    130                  */
    131                 $transient_key = 'wp_remote_block_patterns_' . md5( implode( '-', $query_args ) );
     122                $transient_key = $this->get_transient_key( $query_args );
    132123
    133124                /*
     
    338329                );
    339330
     331                $query_params['slug'] = array(
     332                        'description' => __( 'Limit results to those matching a pattern (slug).' ),
     333                        'type'        => 'array',
     334                );
     335
    340336                /**
    341337                 * Filter collection parameters for the block pattern directory controller.
     
    347343                return apply_filters( 'rest_pattern_directory_collection_params', $query_params );
    348344        }
     345
     346        /*
     347         * Include a hash of the query args, so that different requests are stored in
     348         * separate caches.
     349         *
     350         * MD5 is chosen for its speed, low-collision rate, universal availability, and to stay
     351         * under the character limit for `_site_transient_timeout_{...}` keys.
     352         *
     353         * @link https://stackoverflow.com/questions/3665247/fastest-hash-for-non-cryptographic-uses
     354         *
     355         * @since 6.0.0
     356         *
     357         * @param array $query_args Query arguments to generate a transient key from.
     358         * @return string Transient key.
     359         */
     360        protected function get_transient_key( $query_args ) {
     361
     362                if ( isset( $query_args['slug'] ) ) {
     363                        // This is an additional precaution because the "sort" function expects an array.
     364                        $query_args['slug'] = wp_parse_list( $query_args['slug'] );
     365
     366                        // Empty arrays should not affect the transient key.
     367                        if ( empty( $query_args['slug'] ) ) {
     368                                unset( $query_args['slug'] );
     369                        } else {
     370                                // Sort the array so that the transient key doesn't depend on the order of slugs.
     371                                sort( $query_args['slug'] );
     372                        }
     373                }
     374
     375                return 'wp_remote_block_patterns_' . md5( serialize( $query_args ) );
     376        }
    349377}
  • trunk/tests/phpunit/tests/rest-api/rest-pattern-directory-controller.php

    r53152 r53333  
    2323
    2424        /**
     25         * An instance of WP_REST_Pattern_Directory_Controller class.
     26         *
     27         * @since 6.0.0
     28         *
     29         * @var WP_REST_Pattern_Directory_Controller
     30         */
     31        private static $controller;
     32
     33        /**
    2534         * Set up class test fixtures.
    2635         *
     
    3544                        )
    3645                );
     46
     47                static::$controller = new WP_REST_Pattern_Directory_Controller();
    3748        }
    3849
     
    4354         */
    4455        public function assertPatternMatchesSchema( $pattern ) {
    45                 $schema     = ( new WP_REST_Pattern_Directory_Controller() )->get_item_schema();
     56                $schema     = static::$controller->get_item_schema();
    4657                $pattern_id = isset( $pattern->id ) ? $pattern->id : '{pattern ID is missing}';
    4758
     
    323334         */
    324335        public function test_prepare_item() {
    325                 $controller                   = new WP_REST_Pattern_Directory_Controller();
    326336                $raw_patterns                 = json_decode( self::get_raw_response( 'browse-all' ) );
    327337                $raw_patterns[0]->extra_field = 'this should be removed';
    328338
    329                 $prepared_pattern = $controller->prepare_response_for_collection(
    330                         $controller->prepare_item_for_response( $raw_patterns[0], new WP_REST_Request() )
     339                $prepared_pattern = static::$controller->prepare_response_for_collection(
     340                        static::$controller->prepare_item_for_response( $raw_patterns[0], new WP_REST_Request() )
    331341                );
    332342
     
    341351         */
    342352        public function test_prepare_item_search() {
    343                 $controller                   = new WP_REST_Pattern_Directory_Controller();
    344353                $raw_patterns                 = json_decode( self::get_raw_response( 'search' ) );
    345354                $raw_patterns[0]->extra_field = 'this should be removed';
    346355
    347                 $prepared_pattern = $controller->prepare_response_for_collection(
    348                         $controller->prepare_item_for_response( $raw_patterns[0], new WP_REST_Request() )
     356                $prepared_pattern = static::$controller->prepare_response_for_collection(
     357                        static::$controller->prepare_item_for_response( $raw_patterns[0], new WP_REST_Request() )
    349358                );
    350359
     
    398407        public function test_get_item_schema() {
    399408                $this->markTestSkipped( "The controller's schema is hardcoded, so tests would not be meaningful." );
     409        }
     410
     411        /**
     412         * Tests if the transient key gets generated correctly.
     413         *
     414         * @dataProvider data_get_query_parameters
     415         *
     416         * @covers WP_REST_Pattern_Directory_Controller::get_transient_key
     417         *
     418         * @since 6.0.0
     419         *
     420         * @ticket 55617
     421         *
     422         * @param array     $parameters_1   Expected query arguments.
     423         * @param array     $parameters_2   Actual query arguments.
     424         * @param string    $message        An error message to display.
     425         * @param bool      $assert_same    Assertion type (assertSame vs assertNotSame).
     426         */
     427        public function test_transient_keys_get_generated_correctly( $parameters_1, $parameters_2, $message, $assert_same = true ) {
     428                $reflection_method = new ReflectionMethod( static::$controller, 'get_transient_key' );
     429                $reflection_method->setAccessible( true );
     430
     431                $result_1 = $reflection_method->invoke( self::$controller, $parameters_1 );
     432                $result_2 = $reflection_method->invoke( self::$controller, $parameters_2 );
     433
     434                $this->assertIsString( $result_1, 'Transient key #1 must be a string.' );
     435                $this->assertNotEmpty( $result_1, 'Transient key #1 must not be empty.' );
     436
     437                $this->assertIsString( $result_2, 'Transient key #2 must be a string.' );
     438                $this->assertNotEmpty( $result_2, 'Transient key #2 must not be empty.' );
     439
     440                if ( $assert_same ) {
     441                        $this->assertSame( $result_1, $result_2, $message );
     442                } else {
     443                        $this->assertNotSame( $result_1, $result_2, $message );
     444                }
     445
     446        }
     447
     448        /**
     449         * @since 6.0.0
     450         *
     451         * @ticket 55617
     452         */
     453        public function data_get_query_parameters() {
     454                return array(
     455                        'same key and empty slugs'              => array(
     456                                'parameters_1' => array(
     457                                        'parameter_1' => 1,
     458                                        'slug'        => array(),
     459                                ),
     460                                'parameters_2' => array(
     461                                        'parameter_1' => 1,
     462                                ),
     463                                'message'      => 'Empty slugs should not affect the transient key.',
     464                        ),
     465                        'same key and slugs in different order' => array(
     466                                'parameters_1' => array(
     467                                        'parameter_1' => 1,
     468                                        'slug'        => array( 0, 2 ),
     469                                ),
     470                                'parameters_2' => array(
     471                                        'parameter_1' => 1,
     472                                        'slug'        => array( 2, 0 ),
     473                                ),
     474                                'message'      => 'The order of slugs should not affect the transient key.',
     475                        ),
     476                        'same key and different slugs'          => array(
     477                                'parameters_1' => array(
     478                                        'parameter_1' => 1,
     479                                        'slug'        => array( 'some_slug' ),
     480                                ),
     481                                'parameters_2' => array(
     482                                        'parameter_1' => 1,
     483                                        'slug'        => array( 'some_other_slug' ),
     484                                ),
     485                                'message'      => 'Transient keys must not match.',
     486                                false,
     487                        ),
     488                        'different keys'                        => array(
     489                                'parameters_1' => array(
     490                                        'parameter_1' => 1,
     491                                ),
     492                                'parameters_2' => array(
     493                                        'parameter_2' => 1,
     494                                ),
     495                                'message'      => 'Transient keys must depend on array keys.',
     496                                false,
     497                        ),
     498                );
    400499        }
    401500
  • trunk/tests/qunit/fixtures/wp-api-generated.js

    r53152 r53333  
    1038610386                            "minimum": 1,
    1038710387                            "required": false
     10388                        },
     10389                        "slug": {
     10390                            "description": "Limit results to those matching a pattern (slug).",
     10391                            "type": "array",
     10392                            "required": false
    1038810393                        }
    1038910394                    }
Note: See TracChangeset for help on using the changeset viewer.