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
2

pre_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.

Leave a Reply

Your email address will not be published. Required fields are marked *