When to check for max_num_pages using Wordpress REST API?

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?

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.


  • 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.org/wp-json/wp/v2/posts
    

    from your command line.


Related Questions