diff --git a/editor/components/page-attributes/parent.js b/editor/components/page-attributes/parent.js index 0b45d8e01747c1..09b1a3e0963b32 100644 --- a/editor/components/page-attributes/parent.js +++ b/editor/components/page-attributes/parent.js @@ -63,7 +63,7 @@ const applyWithAPIDataItems = withAPIData( ( props, { type } ) => { const isHierarchical = get( props, [ 'postType', 'hierarchical' ], false ); const queryString = stringify( { context: 'edit', - per_page: 100, + per_page: -1, exclude: postId, parent_exclude: postId, _fields: [ 'id', 'parent', 'title' ], diff --git a/editor/store/effects.js b/editor/store/effects.js index ad08123a54118d..ca7f57f15bb434 100644 --- a/editor/store/effects.js +++ b/editor/store/effects.js @@ -398,7 +398,7 @@ export default { if ( id ) { result = wp.apiRequest( { path: `/wp/v2/${ basePath }/${ id }` } ); } else { - result = wp.apiRequest( { path: `/wp/v2/${ basePath }` } ); + result = wp.apiRequest( { path: `/wp/v2/${ basePath }?per_page=-1` } ); } result.then( diff --git a/lib/rest-api.php b/lib/rest-api.php index e69e2933e86d0b..0ba2ea7649f22f 100644 --- a/lib/rest-api.php +++ b/lib/rest-api.php @@ -309,6 +309,8 @@ function gutenberg_register_post_prepare_functions( $post_type ) { add_filter( "rest_prepare_{$post_type}", 'gutenberg_add_permalink_template_to_posts', 10, 3 ); add_filter( "rest_prepare_{$post_type}", 'gutenberg_add_block_format_to_post_content', 10, 3 ); add_filter( "rest_prepare_{$post_type}", 'gutenberg_add_target_schema_to_links', 10, 3 ); + add_filter( "rest_{$post_type}_collection_params", 'gutenberg_filter_post_collection_parameters', 10, 2 ); + add_filter( "rest_{$post_type}_query", 'gutenberg_filter_post_query_arguments', 10, 2 ); return $post_type; } add_filter( 'registered_post_type', 'gutenberg_register_post_prepare_functions' ); @@ -423,7 +425,12 @@ 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 ) { - if ( '/wp/v2/users' === $request->get_route() ) { + $routes = array( + '/wp/v2/blocks', + '/wp/v2/pages', + '/wp/v2/users', + ); + if ( in_array( $request->get_route(), $routes, true ) ) { $can_view_authors = false; $can_unbounded_query = false; $types = get_post_types( array( 'show_in_rest' => true ), 'objects' ); @@ -440,7 +447,8 @@ function gutenberg_handle_early_callback_checks( $response, $handler, $request ) return new WP_Error( 'rest_forbidden_per_page', __( 'Sorry, you are not allowed make unbounded queries.', 'gutenberg' ), array( 'status' => rest_authorization_required_code() ) ); } } - if ( ! empty( $request['who'] ) && 'authors' === $request['who'] ) { + if ( '/wp/v2/users' === $request->get_route() + && ! empty( $request['who'] ) && 'authors' === $request['who'] ) { if ( ! $can_view_authors ) { return new WP_Error( 'rest_forbidden_who', __( 'Sorry, you are not allowed to query users by this parameter.', 'gutenberg' ), array( 'status' => rest_authorization_required_code() ) ); } @@ -450,6 +458,48 @@ function gutenberg_handle_early_callback_checks( $response, $handler, $request ) } add_filter( 'rest_request_before_callbacks', 'gutenberg_handle_early_callback_checks', 10, 3 ); +/** + * Include additional query parameters on the posts query endpoint. + * + * @see https://core.trac.wordpress.org/ticket/43998 + * + * @param array $query_params JSON Schema-formatted collection parameters. + * @param string $post_type Post type being accessed. + * @return array + */ +function gutenberg_filter_post_collection_parameters( $query_params, $post_type ) { + $post_types = array( 'page', 'wp_block' ); + if ( in_array( $post_type->name, $post_types, true ) + && 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 post collection query parameters to include specific behavior. + * + * @see https://core.trac.wordpress.org/ticket/43998 + * + * @param array $prepared_args Array of arguments for WP_Query. + * @param WP_REST_Request $request The current request. + * @return array + */ +function gutenberg_filter_post_query_arguments( $prepared_args, $request ) { + $post_types = array( 'page', 'wp_block' ); + if ( in_array( $prepared_args['post_type'], $post_types, true ) ) { + // Avoid triggering 'rest_post_invalid_page_number' error + // which will need to be addressed in https://core.trac.wordpress.org/ticket/43998. + if ( -1 === $prepared_args['posts_per_page'] ) { + $prepared_args['posts_per_page'] = 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 2d6f142702e582..70d19d66570802 100644 --- a/phpunit/class-gutenberg-rest-api-test.php +++ b/phpunit/class-gutenberg-rest-api-test.php @@ -216,4 +216,24 @@ public function test_get_items_unbounded_per_page_unauthorized() { $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' ) ); + $request = new WP_REST_Request( 'GET', '/wp/v2/pages' ); + $request->set_param( 'per_page', '-1' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + } + + public function test_get_pages_unbounded_per_page_unauthorized() { + wp_set_current_user( $this->subscriber ); + $this->factory->post->create( array( 'post_type' => 'page' ) ); + $request = new WP_REST_Request( 'GET', '/wp/v2/pages' ); + $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'] ); + } }