Skip to content

Commit

Permalink
Pattern Directory API: Add support for pagination parameters (#45293)
Browse files Browse the repository at this point in the history
* Pattern Directory API: Add support for pagination parameters

* Fix linter & php compat issues

* Remove the 6.0 filter

* Mirror GB 6.0 to also pass through the gutenberg version

* Add 'per_page', 'page', 'offset', 'order', and 'orderby' to collection params

* Add initial tests for new query parameters

* Bump the default per_page to 100 to match w.org API

* Update function name

* Fix linter issues

* remove obsolete `get_items`

Co-authored-by: ntsekouras <[email protected]>
  • Loading branch information
ryelle and ntsekouras authored Nov 8, 2022
1 parent 1386ec3 commit b14837c
Show file tree
Hide file tree
Showing 6 changed files with 375 additions and 79 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
/**
* REST API: Gutenberg_REST_Pattern_Directory_Controller_6_0 class
*
* @package Gutenberg
* @subpackage REST_API
*/

/**
* Controller which provides REST endpoint for block patterns from wordpress.org/patterns.
*/
class Gutenberg_REST_Pattern_Directory_Controller_6_0 extends WP_REST_Pattern_Directory_Controller {
/**
* 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
*
* @since 6.0.0
* @todo This should be removed when the minimum required WordPress version is >= 6.0.
*
* @param array $query_args Query arguments to generate a transient key from.
* @return string Transient key.
*/
protected function get_transient_key( $query_args ) {
if ( method_exists( get_parent_class( $this ), __FUNCTION__ ) ) {
return parent::get_transient_key( $query_args );
}

if ( isset( $query_args['slug'] ) ) {
// This is an additional precaution because the "sort" function expects an array.
$query_args['slug'] = wp_parse_list( $query_args['slug'] );

// Empty arrays should not affect the transient key.
if ( empty( $query_args['slug'] ) ) {
unset( $query_args['slug'] );
} else {
// Sort the array so that the transient key doesn't depend on the order of slugs.
sort( $query_args['slug'] );
}
}

return 'wp_remote_block_patterns_' . md5( serialize( $query_args ) );
}
}
10 changes: 0 additions & 10 deletions lib/compat/wordpress-6.0/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,6 @@ function gutenberg_register_global_styles_endpoints() {
}
add_action( 'rest_api_init', 'gutenberg_register_global_styles_endpoints' );


/**
* Registers the block pattern directory.
*/
function gutenberg_register_rest_pattern_directory() {
$pattern_directory_controller = new Gutenberg_REST_Pattern_Directory_Controller();
$pattern_directory_controller->register_routes();
}
add_action( 'rest_api_init', 'gutenberg_register_rest_pattern_directory' );

/**
* Registers the Edit Site's Export REST API routes.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
<?php
/**
* REST API: Gutenberg_REST_Global_Styles_Controller class
* REST API: Gutenberg_REST_Pattern_Directory_Controller_6_2 class
*
* @package Gutenberg
* @subpackage REST_API
*/

/**
* Controller which provides REST endpoint for block patterns.
* Controller which provides REST endpoint for block patterns from wordpress.org/patterns.
*/
class Gutenberg_REST_Pattern_Directory_Controller extends WP_REST_Pattern_Directory_Controller {
class Gutenberg_REST_Pattern_Directory_Controller_6_2 extends Gutenberg_REST_Pattern_Directory_Controller_6_0 {
/**
* Search and retrieve block patterns metadata
*
* @since 6.0.0
* @since 5.8.0
* @since 6.0.0 Added 'slug' to request.
* @since 6.2.0 Added 'per_page', 'page', 'offset', 'order', and 'orderby' to request.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_items( $request ) {
/**
Expand All @@ -30,36 +31,24 @@ public function get_items( $request ) {

$gutenberg_data = get_plugin_data( dirname( dirname( dirname( __DIR__ ) ) ) . '/gutenberg.php', false );

$query_args = array(
'locale' => get_user_locale(),
'wp-version' => $wp_version, // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- it's defined in `version.php` above.
'gutenberg-version' => $gutenberg_data['Version'],
$valid_query_args = array( 'offset', 'order', 'orderby', 'page', 'per_page', 'search', 'slug' );
$query_args = array_merge(
array_intersect_key( $request->get_params(), array_flip( $valid_query_args ) ),
array(
'locale' => get_user_locale(),
'wp-version' => $wp_version, // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- it's defined in `version.php` above.
'gutenberg-version' => $gutenberg_data['Version'],
)
);

$category_id = $request['category'];
$keyword_id = $request['keyword'];
$search_term = $request['search'];
$slug = $request['slug'];

if ( $category_id ) {
$query_args['pattern-categories'] = $category_id;
}

if ( $keyword_id ) {
$query_args['pattern-keywords'] = $keyword_id;
}

if ( $search_term ) {
$query_args['search'] = $search_term;
}
$query_args['pattern-categories'] = isset( $request['category'] ) ? $request['category'] : false;
$query_args['pattern-keywords'] = isset( $request['keyword'] ) ? $request['keyword'] : false;

if ( $slug ) {
$query_args['slug'] = $slug;
}
$query_args = array_filter( $query_args );

$transient_key = $this->get_transient_key( $query_args );

/**
/*
* 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.
*/
Expand All @@ -71,7 +60,7 @@ public function get_items( $request ) {
$api_url = set_url_scheme( $api_url, 'https' );
}

/**
/*
* 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
Expand All @@ -90,7 +79,7 @@ public function get_items( $request ) {
$raw_patterns = new WP_Error(
'pattern_api_failed',
sprintf(
/* translators: %s: Support forums URL. */
/* 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' )
),
Expand Down Expand Up @@ -125,40 +114,4 @@ public function get_items( $request ) {

return new WP_REST_Response( $response );
}

/**
* 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
*
* @since 6.0.0
* @todo This should be removed when the minimum required WordPress version is >= 6.0.
*
* @param array $query_args Query arguments to generate a transient key from.
* @return string Transient key.
*/
protected function get_transient_key( $query_args ) {
if ( method_exists( get_parent_class( $this ), __FUNCTION__ ) ) {
return parent::get_transient_key( $query_args );
}

if ( isset( $query_args['slug'] ) ) {
// This is an additional precaution because the "sort" function expects an array.
$query_args['slug'] = wp_parse_list( $query_args['slug'] );

// Empty arrays should not affect the transient key.
if ( empty( $query_args['slug'] ) ) {
unset( $query_args['slug'] );
} else {
// Sort the array so that the transient key doesn't depend on the order of slugs.
sort( $query_args['slug'] );
}
}

return 'wp_remote_block_patterns_' . md5( serialize( $query_args ) );
}
}
70 changes: 70 additions & 0 deletions lib/compat/wordpress-6.2/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,73 @@ function gutenberg_register_rest_block_pattern_categories() {
$block_patterns->register_routes();
}
add_action( 'rest_api_init', 'gutenberg_register_rest_block_pattern_categories' );

/**
* Registers the block pattern directory.
*/
function gutenberg_register_rest_pattern_directory() {
$pattern_directory_controller = new Gutenberg_REST_Pattern_Directory_Controller_6_2();
$pattern_directory_controller->register_routes();
}
add_action( 'rest_api_init', 'gutenberg_register_rest_pattern_directory' );

/**
* Add extra collection params to pattern directory requests.
*
* @param array $query_params JSON Schema-formatted collection parameters.
* @return array Updated parameters.
*/
function gutenberg_pattern_directory_collection_params_6_2( $query_params ) {
$query_params['page'] = array(
'description' => __( 'Current page of the collection.', 'gutenberg' ),
'type' => 'integer',
'default' => 1,
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
'minimum' => 1,
);

$query_params['per_page'] = array(
'description' => __( 'Maximum number of items to be returned in result set.', 'gutenberg' ),
'type' => 'integer',
'default' => 100,
'minimum' => 1,
'maximum' => 100,
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
);

$query_params['offset'] = array(
'description' => __( 'Offset the result set by a specific number of items.', 'gutenberg' ),
'type' => 'integer',
);

$query_params['order'] = array(
'description' => __( 'Order sort attribute ascending or descending.', 'gutenberg' ),
'type' => 'string',
'default' => 'desc',
'enum' => array( 'asc', 'desc' ),
);

$query_params['orderby'] = array(
'description' => __( 'Sort collection by post attribute.', 'gutenberg' ),
'type' => 'string',
'default' => 'date',
'enum' => array(
'author',
'date',
'id',
'include',
'modified',
'parent',
'relevance',
'slug',
'include_slugs',
'title',
'favorite_count',
),
);

return $query_params;
}
add_filter( 'rest_pattern_directory_collection_params', 'gutenberg_pattern_directory_collection_params_6_2' );
3 changes: 2 additions & 1 deletion lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function gutenberg_is_experiment_enabled( $name ) {

// WordPress 6.0 compat.
require_once __DIR__ . '/compat/wordpress-6.0/class-gutenberg-rest-global-styles-controller.php';
require_once __DIR__ . '/compat/wordpress-6.0/class-gutenberg-rest-pattern-directory-controller.php';
require_once __DIR__ . '/compat/wordpress-6.0/class-gutenberg-rest-pattern-directory-controller-6-0.php';
require_once __DIR__ . '/compat/wordpress-6.0/class-gutenberg-rest-edit-site-export-controller.php';
if ( ! class_exists( 'WP_REST_Block_Pattern_Categories_Controller' ) ) {
require_once __DIR__ . '/compat/wordpress-6.0/class-wp-rest-block-pattern-categories-controller.php';
Expand All @@ -51,6 +51,7 @@ function gutenberg_is_experiment_enabled( $name ) {

// WordPress 6.2 compat.
require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-block-pattern-categories-controller.php';
require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-pattern-directory-controller-6-2.php';
require_once __DIR__ . '/compat/wordpress-6.2/rest-api.php';
require_once __DIR__ . '/compat/wordpress-6.2/block-patterns.php';

Expand Down
Loading

0 comments on commit b14837c

Please sign in to comment.