If you don't mind the cost of calling COUNT
to first get the total number of posts (you have a dynamic first page in terms of numbers of posts so it doesn't work with a simple offset) here is something that I've wrote (it's late and I am tired so it might have some bugs / might not be the right solution):
// This code can be placed in the 'functions.php' file.
function wt_posts_clauses_hook( $clauses, $query ) {
if ( $query->is_main_query() && $query->is_home() ) {
global $wpdb;
extract( $clauses );
if ( ! empty( $groupby ) ) $groupby = 'GROUP BY ' . $groupby;
$sql = "SELECT COUNT(ID) FROM $wpdb->posts $join WHERE 1=1 $where $groupby";
$no_posts = $wpdb->get_var( $sql );
$posts_per_page = get_option( 'posts_per_page' );
$extra = $no_posts % $posts_per_page;
$paged = absint( $query->get( 'paged' ) );
if ( ! $paged ) $paged = 1;
$from = ( $paged - 1 ) * $posts_per_page;
$to = $posts_per_page;
if ( $extra ) {
if ( $paged < 2 ) {
$from = 0;
$to = $extra;
} else {
$from = $posts_per_page * ( $paged - 2 ) + $extra;
}
}
$clauses['limits'] = "LIMIT {$from}, {$to}";
}
return $clauses;
}
add_filter( 'posts_clauses_request', 'wt_posts_clauses_hook', 99, 2 );
You can probably implement a caching mechanism if you don't want to run this on every listing page call by reading the found_posts
property. If it doesn't correspond to the cache entry you simply delete the cache. Of course, you first have to modify this function above to initially try and read the $no_posts
value from cache. If there is none then run the $sql
query and create the cache with the corresponding $no_posts
value.