FB Pixel

Inserting custom posts into the loop

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:

  1. $posts – the results of the query as an array of post objects
  2. $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;  }

2 thoughts on “Inserting custom posts into the loop”

  1. 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.

Comments are closed.