WordPress: Display the posts of all categories, at most 5 posts per category, and ordered by date?

I am not sure if this has been answered before, but vigorous googling has not led me anywhere so far.

I have a wordpress site where I would like to display all posts as usual, ordered by date. However, instead of setting a limit on the total number of posts displayed, I would like to set a limit on each category.

Read More

For instance, if I have two categories FOO and BAR, I want WordPress to display all, but at most 5, posts from both FOO and BAR. The posts should still be ordered by date, and I mean that any post from FOO that was posted before one from BAR appears first and vice versa.

Specific problem: There is one post in the category FOO from last year, then 20 entries in BAR since then and now I am adding another post in the category FOO. Usually, WordPress with a post limit of 10 would display this most recent FOO entry and 9 more BAR entries. However, I would like it to display both my FOO entries and the 5 most recent BAR entries. Only after I add 4 more FOO entries, the FOO entry from last year will no longer be displayed.

What is the best, most clean and maintainable way to achieve this?

I would be very grateful for any help.

Related posts

Leave a Reply

3 comments

  1. Given that there seems no other solution that doing an insane amount of queries, I devised the following, added to functions.php.

    function alter_query( $query ) {
        if ( $query->is_home() && $query->is_main_query() ) {
            $post_limit = floor(get_settings('posts_per_page')/2);      
            $the_posts = array();
            foreach (get_terms('category',array('hide_empty'=>1,'fields'=>'ids')) as $id) 
                $the_posts = array_merge( $the_posts, 
                    get_posts( array(
                        'fields'      => 'ids',
                        'numberposts' => $post_limit,
                        'category'    => $id, 
                        'orderby'     => 'post_date')));
            $query->set('post__in',    $the_posts);
            $query->set('numberposts', -1);
            $query->set('orderby',     'post_date');
        }
    }
    add_action( 'pre_get_posts', 'alter_query' );
    

    In this solution, I only fetch the ids of all relevant posts first, instead of all the contents and then perform a query to retrieve the actual data. I prefer this solution so far because I can use it without even touching my main loop, which is modified via the hook pre_get_posts.

    I would still prefer a solution that is a little faster, i.e. one that only performs a single query.

  2. Untested, but this should get you started:

    $terms = get_terms('category');
    $results = array(); //Initialize results array
    foreach($terms as $term){
        $args = array(
            'posts_per_page'=>5,
            'tax_query' => array(
                array(
                    'taxonomy' => 'category',
                    'terms'    => $term->term_id,
                ),
            )
        );
        $q = new WP_Query($args);
        if($q->have_posts()) : while($q->have_posts()) : the_post();
            $results[] = $post->ID; //Append IDs to results
        endwhile;endif;
    }
    if($all_posts = array_unique($results)){ //Filter out the duplicates
        $q = new WP_Query(array('post__in'=>$all_posts)); //Query all results
        while($q->have_posts()) : the_post();
            //Loop markup goes here
        endwhile;
    }
    else{
        //No posts found
    }
    
  3. You just need to use get_categories, get_posts with order by date and use foreach to show the results.

    Here is:

    <?php
    // Setting blank array
    $posts_category = array();
    
    // Getting categories
    $args = array('type' => 'post', 'orderby' => 'name', 'hide_empty' => 1);
    $categories = get_categories( $args ); // Getting all categories
    
    foreach($categories as $category) {
              $counter = 1;
    
              // Getting posts
              $args = array('posts_per_page'=> 5, 'offset'=> 1, 'category' => $category->term_id, 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'ASC');
              $posts = get_posts( $args ); // Getting all posts
    
              if(count($posts) >= 1) {
                        foreach($posts as $post) { // Register every post found
                                  if(!array_key_exists($post->ID, $posts_category)) {
                                    $posts_category[$post->ID] = array('title' =>$post->post_title, 'permalink' => get_permalink($post->ID), 'date' => $post->post_date);
                                    $counter = $counter+1;
                                  }
                        }
              }
    }
    
    // Sorting all posts by date
    function sortByDate($a, $b) {
              return strtotime($a["date"]) - strtotime($b["date"]);
    }
    usort($posts_category, "sortByDate");
    $posts = array_reverse($posts_category);
    
    //Showing results
    foreach($posts_category as $post) { ?>
    <li>
              <a href="<?php echo $post['permalink']; ?>"><?php echo $post['title']; ?></a>
    </li>
    <?php }