1

I would like to create a paginated result using a maximum number of posts. I have been trying to achieve this using \WP_Query class e.g:

function getQuery(int $page, int $posts_per_page = 2, int $max_posts = 7): \WP_Query
{
  $args = [
    'posts_per_page' => $posts_per_page,
    'numberposts' => $max_posts,
    'offset' => $page - 1,
    'post_status' => [
      'publish',
    ],
  ];
  return new \WP_Query($args);
}

// Get the last page query which I would assume only would contain a single post.
$query = getQuery(3);

This almost works, the problem is the maximum number of posts when using a paginated result set. I would assume that the last page only would get a single post taking the numberposts argument in to consideration, but this does not seem to work.

One solution is to check if we have reached the end and then slice of the extra returned posts from the array e.g:

if (($page + 1) * $posts_per_page > $max_posts) {
  $count = $max_posts - ($page * $posts_per_page);
  $query->posts = array_slice($query->posts, 0, $count);
  $query->found_posts = $count;
  $query->post_count = $count;
}

but this not seems like a very good solution.

1 Answer 1

1

Hope I understand this correctly. Below is an approach using few hooks to modify the query before fetching the data. Might need some adjustments and testing.

Note that numberposts is just an alias for posts_per_page in get_posts() wrapper of WP_Query.

We could try to use paged instead of offset, but it will not work if we plan to modify posts_per_page on the last page calculated by ceil($max_posts/$posts_per_page ). So we set offset directly as:

$args = array(
  'posts_per_page' => $posts_per_page,
  'offset'         => ($page - 1) * $posts_per_page,
  'post_status'    => array( 'publish' ),
);

We can adjust the number of found posts if it exceeds $max_posts:

$fn_found_posts = function( $found_posts, $q ) use ( $max_posts, $page ) { 
    return $found_posts > $max_posts ? $max_posts : $found_posts;
};

add_action( 'pre_get_posts', $fn_pre_get_posts );

but this filtering will not adjust the returned posts array.

We can therefore adjust the posts_per_page to $max_posts - ($page - 1) * $posts_per_page for the last page (according to $max_posts) because that will adjust the previously set offset:

$fn_pre_get_posts = function( $q ) use ($page, $posts_per_page, $max_posts ) {  
    if ( $page == ceil($max_posts/$posts_per_page ) ) {
        $q->set( 'posts_per_page', $max_posts - ($page - 1) * $posts_per_page );
    }
};

add_filter( 'found_posts', $fn_found_posts );

We can then return empty array for pages after the last page (according to $max_posts):

$fn_posts_pre_query = function( $posts ) use ($page, $posts_per_page, $max_posts ) { 
    return $page > ceil( $max_posts/$posts_per_page) ? array() : $posts; 
};

add_filter( 'posts_pre_query', $fn_posts_pre_query );

Here the modified getQuery() function:

function getQuery(int $page, int $posts_per_page = 2, int $max_posts = 7): \WP_Query {
    $fn_found_posts = function( $found_posts ) use ( $max_posts ) { 
        return $found_posts > $max_posts ? $max_posts : $found_posts;
    };

    $fn_posts_pre_query = function( $posts ) use ($page, $posts_per_page, $max_posts ) { 
        return $page > ceil($max_posts/$posts_per_page) ? array() : $posts; 
    };
    
    $fn_pre_get_posts = function( $q ) use ($page, $posts_per_page, $max_posts ) {  
        if ( $page == ceil($max_posts/$posts_per_page ) ) {
            $q->set( 'posts_per_page', $max_posts - ($page - 1) * $posts_per_page );
        }
    };
    
    add_filter( 'found_posts', $fn_found_posts );
    add_filter( 'posts_pre_query', $fn_posts_pre_query );
    add_action( 'pre_get_posts', $fn_pre_get_posts );

    $args = array(
        'posts_per_page' => $posts_per_page,
        'offset'         => ($page - 1) * $posts_per_page,
        'post_status'    => array( 'publish' ),
    );
    $q = new \WP_Query($args);

    remove_action( 'pre_get_posts', $fn_pre_get_posts );
    remove_filter( 'posts_pre_query', $fn_empty_array );
    remove_filter( 'found_posts', $fn_found_posts );

    return $q;
}

ps: one might add some input range checks etc.

2
  • 1
    I will test around with the above as soon as I have a little more time, thank you. Commented Jan 31, 2023 at 19:07
  • @Cyclonecode hopefully it worked out for you?
    – birgire
    Commented May 11 at 18:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.