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.