5

I have a loop within a loop. I figured out how to show all or X number of custom posts after Y number of normal posts. I also figured out how to show 1 custom post after every Y normal posts. How do I show X custom posts after EVERY Y number of normal posts.

In simple terms. How do I show 2 book (custom post type) posts after every 4 blog posts? How does one fetch more than 1 post at a time from the loop?

this is the logic of the x custom posts after y normal posts. how do i get x custom posts after EVERY y normal posts?

main query // normal query
$k=0 // setting iterator
while have posts, the post

---post markup---


if($k == 4 or $k % 4 == 0) { // whether after x number or after every x number
second query // using WP_Query
while second query has posts, the posts

---custom post type markup // I NEED TO SHOW MORE THAN ONE HERE IF IT IS AFTER EVERY X blogposts

endwhile; //second query ends

}

$k++;
endwhile // main query
5
  • Can you provide some additional detail of what happens/doesn't work when you run your code?
    – Stephen S.
    Apr 17, 2014 at 17:52
  • The logic I have posted above is for x posts AFTER y posts. I can't figure out the logic for x posts EVERY y posts. So currently I am making do with x posts AFTER y posts as I don't know how I can get more posts AT A TIME from the loop... Apr 17, 2014 at 18:00
  • @thedigitalmonk Any progress on this problem?
    – birgire
    May 7, 2014 at 19:51
  • @birgire I am so sorry for not following up on this. Yes I tried your code and it worked perfectly! But then the client added another requirement where things had to be loaded via Ajax and I lost it there. Another developer took over and rewrote the entire thing. However I am going to use this on new projects. Hopefully a case will come soon. I am very grateful for the help! May 8, 2014 at 8:56
  • 1
    @thedigitalmonk ok great, no problem, best of luck with your new projects.
    – birgire
    May 8, 2014 at 9:09

1 Answer 1

10

Here's an idea:

Definitions:

  • Posts per page: PPP
  • Custom post type: Y
  • Main query post type: X
  • How many Y posts to inject each time: y
  • How many X posts to display before injecting the Y posts: x

Formula:

We will use:

    PPP(Y) = y * floor( ( PPP(X) -1 ) / x )

where PPP(X), x and y are positive.

Examples:

Setup 1:

PPP(X)=1, x=3, y=2, PPP(Y) = 2 * floor( (1-1)/3 ) = 2 * 0 = 0
Loop: X 

Setup 2:

PPP(X)=3, x=3, y=2, PPP(Y) = 2 * floor( (3-1)/3 ) = 2 * 0  = 0
Loop: XXX

Setup 3:

PPP(X)=4, x=3, y=2, PPP(Y) = 2 * floor( (4-1)/3 ) = 2 * 1  = 2
Loop: XXX YY X      

Setup 4:

PPP(X)=11, x=3, y=2, PPP(Y) = 2 * floor( (11-1)/3 )  = 2 * 3 = 6
Loop: XXX YY XXX YY XXX YY XX        

Setup 5:

PPP(X)=12, x=3, y=2, PPP(Y) = 2 * floor( (12-1)/3 ) = 2 * 3 = 6
Loop: XXX YY XXX YY XXX YY XXX      

Setup 6:

PPP(X)=13, x=3, y=2, PPP(Y) = 2 * floor( (13-1)/3 ) = 2 * 4 = 8 
Loop: XXX YY XXX YY XXX YY XXX YY X     

Strategy:

Let's try to find a way to inject the Y posts into the loop of X posts, without modifying the template files directly.

We want to hook into the loop_start action for the main query of post type X, and fetch PPP(Y) posts of type Y.

Then we want to use the the_post hook to display:

get_template_part( 'content', 'y' );

We can use the for example the current_post and posts_per_page properties of the main $wp_query object to control the injection logic.

I think it's easier to call WP_Query() for each injection, but let's constrain ourselves to call it only once before the main loop.

Implementation:

Create a template part file in your active theme's directory, for example content-page.php, containing:

<article>
    <header class="entry-header">
        <h2 class="entry-title"><?php the_title(); ?></h2>
    </header>
    <div class="entry-content">
        <?php the_content(); ?>
    </div>
</article>

Then you can try the following:

add_action( 'wp', function(){

    // We want the injection only on the home page:
    if( ! is_home() ) return;

    // Start the injection:
    $inject = new WPSE_Inject( array( 
        'items_before_each_inject' => 3, 
        'cpt_items_per_inject'     => 2, 
        'cpt'                      => 'page',
        'template_part'            => 'content-page',
        'paged'                    => ( $pgd = get_query_var( 'paged' ) ) ? $pgd : 1,       
    ) );
    $inject->init();
});

and modify it to your needs.

The WPSE_Inject class is defined as:

/**
 * class WPSE_Inject
 *
 * Inject custom posts into the main loop, through hooks, 
 * with only a single WP_Query() call
 *
 * @link http://wordpress.stackexchange.com/a/141612/26350
 *
 * Definitions:
 *      Posts per page: PPP
 *      Custom post type: Y
 *      Main query post type: X
 *      How many Y posts to inject: y
 *      How many X posts to display before injecting the Y posts: x
 *
 * Formula:
 *      PPP(Y) = y * floor( ( PPP(X) -1 ) / x ) 
 *          where PPP(X), x and y are positive
 */

class WPSE_Inject
{   
    protected $results       = NULL;
    protected $query         = NULL;
    protected $nr            = 0;
    protected $inject_mode   = FALSE;
    protected $args          = array();

    public function __construct( $args = array() )
    {
        $defaults = array( 
            'items_before_each_inject' => 5, 
            'cpt_items_per_inject'     => 1, 
            'cpt'                      => 'post', 
            'paged'                    => 1, 
            'template_part'            => 'content-post'
        );
        $this->args = wp_parse_args( $args, $defaults );
    }

    public function init()
    {
        add_action( 'loop_start', array( $this, 'loop_start' ) );
        add_action( 'loop_end',   array( $this, 'loop_end'   ) );
    }

    public function cpt_items_on_this_page( WP_Query $query )
    {
        $count =  $this->args['cpt_items_per_inject'] * floor( ( $query->get( 'posts_per_page' ) -1 ) / $this->args['items_before_each_inject'] );
        return ( $count > 0 ) ? $count : 1;
    }

    public function loop_start( WP_Query $query )
    {
        $this->query = $query;

        if( $query->is_main_query() )
        {               
            $args = array( 
                'post_type'        => $this->args['cpt'], 
                'posts_per_page'   => $this->cpt_items_on_this_page( $query ), 
                'paged'            => $this->args['paged'], 
                'suppress_filters' => TRUE, 
            );
            $this->results = new WP_Query( $args );
            add_action( 'the_post', array( $this, 'the_post' ) );
        }
    }

    public function loop_end( WP_Query $query )
    {
        if( $query->is_main_query() )
            remove_action( 'the_post', array( $this, 'the_post' ) );
    }

    public function the_post()
    {           
        if( ! $this->inject_mode        
            && 0 < $this->nr 
            && 0 === $this->nr % $this->args['items_before_each_inject'] )
        {    
            $this->inject_mode = TRUE;          
            $this->results->rewind_posts();
            $this->results->current_post = ( absint( $this->nr / $this->args['items_before_each_inject'] ) -1 ) * $this->args['cpt_items_per_inject'] - 1;
            $j = 1;
            if ( $this->results->have_posts() ) :
                while ( $this->results->have_posts() ) :
                    $this->results->the_post();
                    get_template_part(  $this->args['template_part'] );
                    if( $this->args['cpt_items_per_inject'] < ++$j )
                        break;

                endwhile;
                wp_reset_postdata();
            endif;
            $this->inject_mode = FALSE;
        }

        if( ! $this->inject_mode )
            $this->nr++;

    }
}

I tested this on the default theme, where I injected page content into the main post loop. The pagination seemed to work as well.

This can be refined more, but hopefully this is a starting point for you.

14
  • I went through your thought process and it is perfectly what I have asked for. Just not sure how all of this(post_count, current_post and posts_per_page) will come together. Waiting till you expand the answer. :) Apr 18, 2014 at 5:51
  • @thedigitalmonk I updated the answer, hopefully this first version works for you?
    – birgire
    Apr 18, 2014 at 15:14
  • An outstanding answer. Sep 15, 2015 at 16:45
  • Is this still working with the current version of WordPress? I tried to exchange pages and posts with two custom post types and have it appear on the second post type’s archive, inserted both code snippets into my theme’s functions.php, and while the first query returns two posts, no posts are being put out for the second post type at all. May I just have overlooked something, @birgire? Calling the stuff through <?php if (have_posts()) : while (have_posts()) : the_post(); ?><?php endwhile; endif; ?> in my archive template.
    – physalis
    Mar 24, 2016 at 11:55
  • 1
    great, we should use the main query whenever we can ;-) @physalis
    – birgire
    May 17, 2017 at 10:57

Your Answer

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

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