I'm implementing infinite loading using the WP API, so I pull in the new posts via an API request. Because the default API response is fine with me and I only really need to filter the posts returned, I'm not creating a custom route but rather customizing the default query:
add_filter( 'rest_post_query', array( $this, 'get_posts' ), 10, 2 );
public function get_posts( $args, $req ) {
/*
This function is used to retrieve posts of the 'post' and 'news' type
*/
$cat = $req[ 'category' ];
$page = $req[ 'page' ];
$tag = $req[ 'tag' ];
$type = $req[ 'type' ];
$args[ 'paged' ] = isset( $page ) ? $page : 1;
$args[ 'posts_per_page' ] = 8;
$args[ 'category_name' ] = isset( $cat ) && ! empty( $cat ) ? $cat : null;
$args[ 'tag' ] = isset( $tag ) && ! empty( $tag ) ? $tag : null;
$args[ 'post_type' ] =
isset( $type ) && ! empty( $type ) ? array( $type ) : array( 'post', 'news' );
return $args;
}
I have a problem with pagination, though. Imagine that I have 10 pages of results and I request page 20: the API's default behavior is to throw the following error:
{
"code": "rest_post_invalid_page_number",
"message": "The page number requested is larger than the number of pages available.",
"data": {
"status": 400
}
}
What I would like to do is return an empty array instead, because it would be easier and more intuitive to deal with in the frontend. So I thought I'd check the max_num_pages
property of the query, but I don't know where to do that.
I tried doing this:
add_action( 'pre_get_posts', array( $this, 'check_pagination_limit' ) );
public function check_pagination_limit( $query ) {
if( ! is_admin() ) {
$currentPage = $query->get('paged');
$lastPage = $query->max_num_pages;
if( $currentPage > $lastPage ) {
$query->set('post__in', array(0));
}
}
}
But pre_get_posts
doesn't seem to work well when rest_post_query
is being used... Is there any rest_
filter or hook that I can use to access the query before the response is sent?
I'm implementing infinite loading using the WP API, so I pull in the new posts via an API request. Because the default API response is fine with me and I only really need to filter the posts returned, I'm not creating a custom route but rather customizing the default query:
add_filter( 'rest_post_query', array( $this, 'get_posts' ), 10, 2 );
public function get_posts( $args, $req ) {
/*
This function is used to retrieve posts of the 'post' and 'news' type
*/
$cat = $req[ 'category' ];
$page = $req[ 'page' ];
$tag = $req[ 'tag' ];
$type = $req[ 'type' ];
$args[ 'paged' ] = isset( $page ) ? $page : 1;
$args[ 'posts_per_page' ] = 8;
$args[ 'category_name' ] = isset( $cat ) && ! empty( $cat ) ? $cat : null;
$args[ 'tag' ] = isset( $tag ) && ! empty( $tag ) ? $tag : null;
$args[ 'post_type' ] =
isset( $type ) && ! empty( $type ) ? array( $type ) : array( 'post', 'news' );
return $args;
}
I have a problem with pagination, though. Imagine that I have 10 pages of results and I request page 20: the API's default behavior is to throw the following error:
{
"code": "rest_post_invalid_page_number",
"message": "The page number requested is larger than the number of pages available.",
"data": {
"status": 400
}
}
What I would like to do is return an empty array instead, because it would be easier and more intuitive to deal with in the frontend. So I thought I'd check the max_num_pages
property of the query, but I don't know where to do that.
I tried doing this:
add_action( 'pre_get_posts', array( $this, 'check_pagination_limit' ) );
public function check_pagination_limit( $query ) {
if( ! is_admin() ) {
$currentPage = $query->get('paged');
$lastPage = $query->max_num_pages;
if( $currentPage > $lastPage ) {
$query->set('post__in', array(0));
}
}
}
But pre_get_posts
doesn't seem to work well when rest_post_query
is being used... Is there any rest_
filter or hook that I can use to access the query before the response is sent?
2 Answers
Reset to default 2pre_get_posts
does fire for REST requests.
After copying your function and stepping through it, your code is actually working -- the first time. However the WP REST controller has the following bit:
if ( $total_posts < 1 ) {
// Out-of-bounds, run the query again without LIMIT for total count.
unset( $query_args['paged'] );
$count_query = new WP_Query();
$count_query->query( $query_args );
$total_posts = $count_query->found_posts;
}
This re-runs the query, which in turn re-runs your function. However as you can see, it has intentionally unset the 'paged' argument, so this time when you compare $currentPage > $lastPage
you are comparing 0 > 0
which is false, so your post__in
argument is not set, and posts are returned. You know the rest of the story -- WordPress then catches that you can't have that page because there aren't enough posts.
You could get that parameter more directly since it is part of your GET request, like:
if( ! is_admin()
&& isset($_GET['page']) ) {
$currentPage = $_GET['page'];
$lastPage = $query->max_num_pages;
if( $currentPage > $lastPage ) {
$query->set('post__in', array(0));
}
}
This seemed to work, but I didn't test it thoroughly.
To be honest, you are really swimming upstream here, and I think a better solution might be to take a hint from WordPress and just build in handling for that error response. It's nicely packaged as JSON, and you will probably want to watch for other errors too anyways.
Also, I think your function should check and make sure it is only firing on REST requests. Right now as it is written it would fire on others as well.
Note that the rest response header for naitve endpoints contains information about the number of found items and total number of pages:
X-WP-Total: 10
X-WP-TotalPages: 1
This would be available via javascript and can help with the pagination on the front-end.
Check it with e.g.
curl -I https://example/wp-json/wp/v2/posts
from your command line.