Skip to content

Commit

Permalink
Pattern Directory: Move caching to endpoint for unique responses.
Browse files Browse the repository at this point in the history
Now that the pattern API request includes the locale and version, the cache key needs to contain a hash of the query args.

Merges https://core.trac.wordpress.org/changeset/51208
See https://core.trac.wordpress.org/ticket/53435

Co-authored-by: Dominik Schilling <[email protected]>
Co-authored-by: Dion Hulse <[email protected]>
Co-authored-by: Timothy Jacobs <[email protected]>
  • Loading branch information
4 people committed Jul 13, 2021
1 parent b70a352 commit 4264cac
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 38 deletions.
23 changes: 11 additions & 12 deletions lib/block-patterns.php
Original file line number Diff line number Diff line change
Expand Up @@ -200,25 +200,24 @@ function remove_core_patterns() {
}

/**
* Import patterns from wordpress.org/patterns.
* Register Core's official patterns from wordpress.org/patterns.
*
* @since 5.8.0
*/
function load_remote_patterns() {
// This is the core function that provides the same feature.
if ( function_exists( '_load_remote_block_patterns' ) ) {
return;
}
$patterns = get_transient( 'gutenberg_remote_block_patterns' );
if ( ! $patterns ) {
$request = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' );
$core_keyword_id = 11; // 11 is the ID for "core".
$request->set_param( 'keyword', $core_keyword_id );
$response = rest_do_request( $request );
if ( $response->is_error() ) {
return;
}
$patterns = $response->get_data();
set_transient( 'gutenberg_remote_block_patterns', $patterns, HOUR_IN_SECONDS );

$request = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' );
$core_keyword_id = 11; // 11 is the ID for "core".
$request->set_param( 'keyword', $core_keyword_id );
$response = rest_do_request( $request );
if ( $response->is_error() ) {
return;
}
$patterns = $response->get_data();

foreach ( $patterns as $settings ) {
$pattern_name = 'core/' . sanitize_title( $settings['title'] );
Expand Down
87 changes: 61 additions & 26 deletions lib/class-wp-rest-pattern-directory-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,38 +117,73 @@ public function get_items( $request ) {
$query_args['search'] = $search_term;
}

$api_url = add_query_arg(
array_map( 'rawurlencode', $query_args ),
'http://api.wordpress.org/patterns/1.0/'
);
/*
* Include a hash of the query args, so that different requests are stored in
* separate caches.
*
* MD5 is chosen for its speed, low-collision rate, universal availability, and to stay
* under the character limit for `_site_transient_timeout_{...}` keys.
*
* @link https://stackoverflow.com/questions/3665247/fastest-hash-for-non-cryptographic-uses
*/
$transient_key = 'wp_remote_block_patterns_' . md5( implode( '-', $query_args ) );

if ( wp_http_supports( array( 'ssl' ) ) ) {
$api_url = set_url_scheme( $api_url, 'https' );
}
/*
* Use network-wide transient to improve performance. The locale is the only site
* configuration that affects the response, and it's included in the transient key.
*/
$raw_patterns = get_site_transient( $transient_key );

if ( ! $raw_patterns ) {
$api_url = add_query_arg(
array_map( 'rawurlencode', $query_args ),
'http://api.wordpress.org/patterns/1.0/'
);

$wporg_response = wp_remote_get( $api_url );
$raw_patterns = json_decode( wp_remote_retrieve_body( $wporg_response ) );
if ( wp_http_supports( array( 'ssl' ) ) ) {
$api_url = set_url_scheme( $api_url, 'https' );
}

if ( is_wp_error( $wporg_response ) ) {
$wporg_response->add_data( array( 'status' => 500 ) );
/*
* Default to a short TTL, to mitigate cache stampedes on high-traffic sites.
* This assumes that most errors will be short-lived, e.g., packet loss that causes the
* first request to fail, but a follow-up one will succeed. The value should be high
* enough to avoid stampedes, but low enough to not interfere with users manually
* re-trying a failed request.
*/
$cache_ttl = 5;
$wporg_response = wp_remote_get( $api_url );
$raw_patterns = json_decode( wp_remote_retrieve_body( $wporg_response ) );

if ( is_wp_error( $wporg_response ) ) {
$raw_patterns = $wporg_response;

} elseif ( ! is_array( $raw_patterns ) ) {
// HTTP request succeeded, but response data is invalid.
$raw_patterns = new WP_Error(
'pattern_api_failed',
sprintf(
/* translators: %s: Support forums URL. */
__( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server&#8217;s configuration. If you continue to have problems, please try the <a href="%s">support forums</a>.', 'gutenberg' ),
__( 'https://wordpress.org/support/forums/', 'gutenberg' )
),
array(
'response' => wp_remote_retrieve_body( $wporg_response ),
)
);

} else {
// Response has valid data.
$cache_ttl = HOUR_IN_SECONDS;
}

return $wporg_response;
set_site_transient( $transient_key, $raw_patterns, $cache_ttl );
}

// Make sure w.org returned valid data.
if ( ! is_array( $raw_patterns ) ) {
return new WP_Error(
'pattern_api_failed',
sprintf(
/* translators: %s: Support forums URL. */
__( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server&#8217;s configuration. If you continue to have problems, please try the <a href="%s">support forums</a>.', 'gutenberg' ),
__( 'https://wordpress.org/support/forums/', 'gutenberg' )
),
array(
'status' => 500,
'response' => wp_remote_retrieve_body( $wporg_response ),
)
);
if ( is_wp_error( $raw_patterns ) ) {
$raw_patterns->add_data( array( 'status' => 500 ) );

return $raw_patterns;
}

$response = array();
Expand Down
40 changes: 40 additions & 0 deletions phpunit/class-wp-rest-pattern-directory-controller-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,46 @@ public function test_get_items_invalid_response_data() {
$this->assertWPError( $response->as_error() );
}

/**
* @covers WP_REST_Pattern_Directory_Controller::get_items
*
* @since 5.8.0
*/
public function test_get_items_prepare_filter() {
wp_set_current_user( self::$contributor_id );
self::mock_successful_response( 'browse-all', true );

// Test that filter changes uncached values.
add_filter(
'rest_prepare_block_pattern',
function() {
return 'initial value';
}
);

$request = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' );
$response = rest_do_request( $request );
$patterns = $response->get_data();

$this->assertSame( 'initial value', $patterns[0] );

// Test that filter changes cached values (the previous request primed the cache).
add_filter(
'rest_prepare_block_pattern',
function() {
return 'modified the cache';
},
11
);

// Test that the filter works against cached values.
$request = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' );
$response = rest_do_request( $request );
$patterns = $response->get_data();

$this->assertSame( 'modified the cache', $patterns[0] );
}

public function test_get_item() {
$this->markTestSkipped( 'Controller does not have get_item route.' );
}
Expand Down

0 comments on commit 4264cac

Please sign in to comment.