Today on the WordPress subreddit, someone asked if it was possible to have posts from a custom post type (recipes, in their example) show up in the main blog at a set frequency (ie. every 5th post). My initial thought was, probably not. But I decided to do a little digging anyway. Turns out there’s a handy filter called the_posts
that I didn’t know existed.
The the_posts
filter is run at the end of WP_Query->get_posts()
[trac] – after the results of the query have been retrieved from the database, but before The Loop begins. We can use this filter to modify the results, including adding our own. Our filter function will receive two arguments:
$posts
– the results of the query as an array of post objects$query
– the WP_Query instance
add_filter( 'the_posts', 'gowp_insert_recipes', 10, 2 ); function gowp_insert_recipes( $posts, $query ) { return $posts; }
We don’t want our filter to affect post lists in the admin dashboard, so we’ll start with:
if ( is_admin() ) return $posts;
We also only want our filter to affect the main query, so we’ll add this:
if ( ! is_main_query() ) return $posts;
We’ll set a variable for the frequency of the insertion which we’ll use in some math later on (if we’re developing this as a public plugin we could set/get this value as an option/setting):
$freq = 5;
Now we’ll retrieve our recipes. We’ll calculate our posts_per_page
on the fly, based on the value from the main query and our frequency, and use the same paged
value as the main query – this ensures that the inserted posts stay in sync regardless of pagination settings:
$args = array( 'post_type' => 'gowp_recipe', 'posts_per_page' => floor( $query->query_vars['posts_per_page'] / $freq ), 'paged' => ( $query->query_vars['paged'] ) ? $query->query_vars['paged'] : 1, );
We’ll then loop through the recipes and insert them into the $posts
array using array_slice()
[documentation]. We’ll do a little math to ensure that our recipes are inserted correctly (I’m sure there’s a more elegant way to do this – if you know one, please let me know in the comments):
if ( $recipes = get_posts( $args ) ) { $insert = -1; foreach ( $recipes as $recipe ) { $insert = $insert + ( $freq + 1 ); array_splice( $posts, $insert, 0, array( $recipe ) ); } }
Here is the final code all together:
add_filter( 'the_posts', 'gowp_insert_recipes', 10, 2 ); function gowp_insert_recipes( $posts, $query ) { if ( is_admin() ) return $posts; if ( ! is_main_query() ) return $posts; $freq = 5; $args = array( 'post_type' => 'gowp_recipe', 'posts_per_page' => floor( $query->query_vars['posts_per_page'] / $freq ), 'paged' => ( $query->query_vars['paged'] ) ? $query->query_vars['paged'] : 1, ); if ( $recipes = get_posts( $args ) ) { $insert = -1; foreach ( $recipes as $recipe ) { $insert = $insert + ( $freq + 1 ); array_splice( $posts, $insert, 0, array( $recipe ) ); } } return $posts; }
Interesting take. The problem is that you’ll miss out on a post when using the paging functionality. I’ve yet to come up with a solution to that problem.
To get back to this, you can count the amount of posts that are inserted and alter the posts_per_page based on that. The drawback to this, is that you have to hook on pre_get_posts and do an additional query there as well. So a little query overhead in that. Alternative is to just ignore the posts_per_page limit of course.