Make WordPress Core

Changeset 53333


Ignore:
Timestamp:
05/02/2022 01:58:48 PM (2 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.