diff --git a/core-data/resolvers.js b/core-data/resolvers.js index 39c2a5abb9e9a..a717f9f94b296 100644 --- a/core-data/resolvers.js +++ b/core-data/resolvers.js @@ -21,7 +21,7 @@ import { getEntity } from './entities'; */ export async function* getCategories() { yield setRequested( 'terms', 'categories' ); - const categories = await apiRequest( { path: '/wp/v2/categories' } ); + const categories = await apiRequest( { path: '/wp/v2/categories?per_page=-1' } ); yield receiveTerms( 'categories', categories ); } diff --git a/core-data/test/resolvers.js b/core-data/test/resolvers.js index 8a88aba9610a5..3c4bcca1a7d19 100644 --- a/core-data/test/resolvers.js +++ b/core-data/test/resolvers.js @@ -16,7 +16,7 @@ describe( 'getCategories', () => { beforeAll( () => { apiRequest.mockImplementation( ( options ) => { - if ( options.path === '/wp/v2/categories' ) { + if ( options.path === '/wp/v2/categories?per_page=-1' ) { return Promise.resolve( CATEGORIES ); } } ); diff --git a/editor/components/post-taxonomies/flat-term-selector.js b/editor/components/post-taxonomies/flat-term-selector.js index 5a89da360e1a9..2c066474b9161 100644 --- a/editor/components/post-taxonomies/flat-term-selector.js +++ b/editor/components/post-taxonomies/flat-term-selector.js @@ -16,7 +16,7 @@ import { withSelect, withDispatch } from '@wordpress/data'; * Module constants */ const DEFAULT_QUERY = { - per_page: 100, + per_page: -1, orderby: 'count', order: 'desc', _fields: 'id,name', diff --git a/editor/components/post-taxonomies/hierarchical-term-selector.js b/editor/components/post-taxonomies/hierarchical-term-selector.js index 3142ea77ab998..fdd79b25ec173 100644 --- a/editor/components/post-taxonomies/hierarchical-term-selector.js +++ b/editor/components/post-taxonomies/hierarchical-term-selector.js @@ -17,7 +17,7 @@ import { withSelect, withDispatch } from '@wordpress/data'; * Module Constants */ const DEFAULT_QUERY = { - per_page: 100, + per_page: -1, orderby: 'count', order: 'desc', _fields: 'id,name,parent', diff --git a/lib/rest-api.php b/lib/rest-api.php index 31fa318ad8f5e..3907bd8445af5 100644 --- a/lib/rest-api.php +++ b/lib/rest-api.php @@ -353,6 +353,17 @@ function gutenberg_register_post_prepare_functions( $post_type ) { } add_filter( 'registered_post_type', 'gutenberg_register_post_prepare_functions' ); +/** + * Whenever a taxonomy is registered, ensure we're hooked into its WP REST API response. + * + * @param string $taxonomy The newly registered taxonomy. + */ +function gutenberg_register_taxonomy_prepare_functions( $taxonomy ) { + add_filter( "rest_{$taxonomy}_collection_params", 'gutenberg_filter_term_collection_parameters', 10, 2 ); + add_filter( "rest_{$taxonomy}_query", 'gutenberg_filter_term_query_arguments', 10, 2 ); +} +add_filter( 'registered_taxonomy', 'gutenberg_register_taxonomy_prepare_functions' ); + /** * Includes the value for the 'viewable' attribute of a post type resource. * @@ -463,12 +474,7 @@ function gutenberg_ensure_wp_json_has_theme_supports( $response ) { * @param WP_REST_Request $request Request used to generate the response. */ function gutenberg_handle_early_callback_checks( $response, $handler, $request ) { - $routes = array( - '/wp/v2/blocks', - '/wp/v2/pages', - '/wp/v2/users', - ); - if ( in_array( $request->get_route(), $routes, true ) ) { + if ( 0 === strpos( $request->get_route(), '/wp/v2/' ) ) { $can_view_authors = false; $can_unbounded_query = false; $types = get_post_types( array( 'show_in_rest' => true ), 'objects' ); @@ -538,6 +544,52 @@ function gutenberg_filter_post_query_arguments( $prepared_args, $request ) { return $prepared_args; } +/** + * Include additional query parameters on the terms query endpoint. + * + * @see https://core.trac.wordpress.org/ticket/43998 + * + * @param array $query_params JSON Schema-formatted collection parameters. + * @param object $taxonomy Taxonomy being accessed. + * @return array + */ +function gutenberg_filter_term_collection_parameters( $query_params, $taxonomy ) { + if ( $taxonomy->show_in_rest + && ( false === $taxonomy->rest_controller_class + || 'WP_REST_Terms_Controller' === $taxonomy->rest_controller_class ) + && isset( $query_params['per_page'] ) ) { + // Change from '1' to '-1', which means unlimited. + $query_params['per_page']['minimum'] = -1; + // Default sanitize callback is 'absint', which won't work in our case. + $query_params['per_page']['sanitize_callback'] = 'rest_sanitize_request_arg'; + } + return $query_params; +} + +/** + * Filter term collection query parameters to include specific behavior. + * + * @see https://core.trac.wordpress.org/ticket/43998 + * + * @param array $prepared_args Array of arguments for WP_Term_Query. + * @param WP_REST_Request $request The current request. + * @return array + */ +function gutenberg_filter_term_query_arguments( $prepared_args, $request ) { + // Can't check the actual taxonomy here because it's not + // passed through in $prepared_args (or the filter generally). + if ( 0 === strpos( $request->get_route(), '/wp/v2/' ) ) { + if ( -1 === $prepared_args['number'] ) { + // This should be unset( $prepared_args['number'] ) + // but WP_REST_Terms Controller needs to be updated to support + // unbounded queries. + // Will be addressed in https://core.trac.wordpress.org/ticket/43998. + $prepared_args['number'] = 100000; + } + } + return $prepared_args; +} + /** * Include additional query parameters on the user query endpoint. * diff --git a/phpunit/class-gutenberg-rest-api-test.php b/phpunit/class-gutenberg-rest-api-test.php index 242b638a3bb92..a8fe8fdb61856 100644 --- a/phpunit/class-gutenberg-rest-api-test.php +++ b/phpunit/class-gutenberg-rest-api-test.php @@ -280,6 +280,26 @@ public function test_get_items_unbounded_per_page_unauthorized() { $this->assertEquals( 'rest_forbidden_per_page', $data['code'] ); } + public function test_get_categories_unbounded_per_page() { + wp_set_current_user( $this->author ); + $this->factory->category->create(); + $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); + $request->set_param( 'per_page', '-1' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + } + + public function test_get_categories_unbounded_per_page_unauthorized() { + wp_set_current_user( $this->subscriber ); + $this->factory->category->create(); + $request = new WP_REST_Request( 'GET', '/wp/v2/categories' ); + $request->set_param( 'per_page', '-1' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 403, $response->get_status() ); + $data = $response->get_data(); + $this->assertEquals( 'rest_forbidden_per_page', $data['code'] ); + } + public function test_get_pages_unbounded_per_page() { wp_set_current_user( $this->author ); $this->factory->post->create( array( 'post_type' => 'page' ) );