7

I am looking for a solution that allows me to print the following:

Cat 1        Cat 2        Cat 3
 Post 1       Post 1       Post 1
 Post 2       Post 2       Post 2
 Post 3                    Post 3
                           Post 4

EDIT

I am looking for something that will only require one database query! So if you have a foreach in your code followed by a new WP_Query then that is not what I am looking for (I am planning to put this onto my website's homepage).

3
  • downvoted as your requirement turns this from a WordPress question into a php sorting problem.
    – Michael
    Feb 25, 2012 at 11:29
  • 3
    Please see my answer. Extremely quick and efficient. See the stats I've added in my answer Sep 7, 2014 at 13:51
  • How about by this? <?php $erer = wp_list_categories('orderby=name&title_li=&show_count=1'); var_dump($erer); ?> You can get how to use HTML and css then.
    – Vishwa
    Oct 16, 2018 at 4:14

8 Answers 8

20

EDIT REVISIT NO 2

I have never touched the Transient API, until today when I saw @MikeSchinkel answer in this post. This inspired me to revisit this post once again. After some testing, I came up with the following:

  • Time to execute went down from ~0.07 seconds to ~0.002seconds

  • Database query time went down by about halve

  • With the transient, only 2 db queries are executed

How the code works (Just going to discuss changes from the original code from REVISIT):

STEP 1

We need to save the value of $q to a transient, this is the value that holds the category list with post titles.

STEP 2

We first need to check if a transient exists, and if none exists, create the transient. If the transient exists, retrieve its info

STEP 3

This info is now be passed through a foreach loop to print the list with category names and post titles.

STEP 4

As it stands, the transient will be updated every twelve hours. This can be set to suite your needs. However, the transient will need to be deleted and recreated every time a post's status changes. This might be from draft to publish, a new post being published or a post that's being trashed. To do this, you need to make use of delete_transient which will be hooked to transition_post_status which will be triggered each time a post's status changes

Here is the complete code:

In your functions.php

add_action( 'transition_post_status', 'publish_new_post', 10, 3 );

function publish_new_post() {
   delete_transient( 'category_list' );
}

In your template where you need to display your list

<?php
if ( false === ( $q = get_transient( 'category_list' ) ) ) {

    $args = array( 
        'posts_per_page' => -1
    );

    $query = new WP_Query($args); 

    $q = array();

    while ( $query->have_posts() ) { 

        $query->the_post(); 

        $a = '<a href="'. get_permalink() .'">' . get_the_title() .'</a>';

        $categories = get_the_category();

        foreach ( $categories as $key=>$category ) {

            $b = '<a href="' . get_category_link( $category ) . '">' . $category->name . '</a>';    

        }

        $q[$b][] = $a; // Create an array with the category names and post titles
    }


    /* Restore original Post Data */
    wp_reset_postdata();

set_transient( 'category_list', $q, 12 * HOUR_IN_SECONDS );
}

foreach ($q as $key=>$values) {
        echo $key;

        echo '<ul>';
            foreach ($values as $value){
                echo '<li>' . $value . '</li>';
            }
        echo '</ul>';
    }


?>

REVISIT

I recently came up with a very lightweight solution that is way much faster than the other possible solutions given. On my test site I get a total generation time of only ~0.07 seconds and only 6 db queries according to Query Monitor while the other methods give me a generation time of ~0.35 seconds and 50 extra db queries.

Here is a breakdown of my method

STEP 1

You first need to create a custom query with WP_Query to retrieve all published posts

$args = array( 
        'posts_per_page' => -1
    );

    $query = new WP_Query($args);   
    $q = array();

    while ( $query->have_posts() ) { 

    }

    /* Restore original Post Data */
    wp_reset_postdata();

Step 2

Using get_the_category, retrieve a list of all categories a post belongs to.

$categories = get_the_category();

        foreach ( $categories as $key=>$category ) {

            $b = '<a href="' . get_category_link( $category ) . '">' . $category->name . '</a>';    

        }

STEP 3

Assign variables to the post title and the categories of the post

$a = '<a href="'. get_permalink() .'">' . get_the_title() .'</a>';

and

$b = '<a href="' . get_category_link( $category ) . '">' . $category->name . '</a>';    

STEP 4

Combine these two variables to form a multidimensional array

$q[$b][] = $a;

To see what is happening in the array, simply do a var_dump

?><pre><?php var_dump($q); ?></pre><?php

STEP 5

Using foreach loops, create your post list sorted by category

foreach ($q as $key=>$values) {
    echo $key;

    echo '<ul>';
        foreach ($values as $value){
            echo '<li>' . $value . '</li>';
        }
    echo '</ul>';
}

ALL TOGETHER NOW!

Here is the complete code

<?php

    $args = array( 
        'posts_per_page' => -1
    );

    $query = new WP_Query($args);   
    $q = array();

    while ( $query->have_posts() ) { 

        $query->the_post(); 

        $a = '<a href="'. get_permalink() .'">' . get_the_title() .'</a>';

        $categories = get_the_category();

        foreach ( $categories as $key=>$category ) {

            $b = '<a href="' . get_category_link( $category ) . '">' . $category->name . '</a>';    

        }

        $q[$b][] = $a; // Create an array with the category names and post titles
    }

    /* Restore original Post Data */
    wp_reset_postdata();

    foreach ($q as $key=>$values) {
        echo $key;

        echo '<ul>';
            foreach ($values as $value){
                echo '<li>' . $value . '</li>';
            }
        echo '</ul>';
    }

?>
3
  • 2
    why other categories are duplicated?
    – winresh24
    Mar 15, 2020 at 3:57
  • How can I do it with custom post type?
    – winresh24
    Mar 15, 2020 at 4:55
  • @Pieter-Goosen Excellent code! Instead of using a <ul>, I want to use a template from a file. I tried including the file in the `foreach ($values as $value) {include(template.php);} but then it sets the post names as the page title, instead of the post titles. Any idea how I can fix this?
    – harvey
    Apr 19, 2020 at 7:06
1

I just wanted to add my solution that derived from this question I had. This caches the query for the categories and also caches the posts, including the post content, in each category. The first time the cache is filled there are normal database queries, however once filled the cached categories and posts are served, so no more database queries.

// Transients API all categories and all posts
$query_categories = get_transient('cached_categories');
if ( false === $query_categories){
    $args_cat = array(
        // order by category name ascending
        'orderby' => 'name',
        'order' => 'ASC',
        // get only top level categories
        'parent' => 0
    );
    // Instead of caching a WP Query I cache 'get_categories()'.
    $query_categories = get_categories($args_cat);
    // var_dump($query_categories);
    set_transient('cached_categories', $query_categories, DAY_IN_SECONDS );
}

// Full posts query
// if there are categories filled with posts
if (!empty ($query_categories) && !is_wp_error( $query_categories )) {

    foreach ($query_categories as $category) {

        // var_dump($category);
        $query_category_posts = get_transient('cached_posts_' . $category->slug );
        if ( false === $query_category_posts ){

            // Query all posts by slug inside each category
            $args_category_posts = array(
                'post_type' => 'post',
                // The category slug and category name we get from the foreach over all categories
                'category_name' => $category->slug
            );

            // Here I cache the WP_Query, though this runs for all categories.
            // Because of that the '$category->slug' is used to serve a string and not an object.
            $query_category_posts = new WP_Query($args_category_posts);         
            set_transient( 'cached_posts_' . $category->slug , $query_category_posts, DAY_IN_SECONDS );
        }

        if ($query_category_posts->have_posts()) {
            while ($query_category_posts->have_posts()) {
                $query_category_posts->the_post(); ?>
                <article class="<?php echo $category->slug ?>-article">
                    <h2 class="<?php echo $category->slug ?>-article-title">
                        <a href="<?php echo get_permalink() ?>"><?php echo get_the_title() ?></a>
                    </h2>
                    <p class="<?php echo $category->slug ?>-post-info">
                        <?php the_time('d. m. Y') ?>
                    </p>
                    <div <?php post_class() ?> >
                        <?php the_content(); ?>
                    </div>
                </article> <?php
            }
        } // end loop
    } // end foreach
wp_reset_postdata() ;
} // end if there are categories filled with posts
1

Here is my solution for getting the categories and the posts within those categories as one result.

My example is based on custom post type taxonomy category document_category and the custom post type documents. But i'm sure you will get the idea.

$result = [];

$categories = get_terms( [
    'taxonomy' => 'document_category'
] );

foreach ( $categories as $index => $category ) {
    $args      = [
        'post_type'      => 'documents',
        'posts_per_page' => - 1,
        'public'         => true,
        'tax_query'      => [
            [
                'taxonomy'         => 'document_category',
                'field'            => 'term_id',
                'terms'            => $category->term_id,
                'include_children' => true
            ]
        ]
    ];
    $documents = get_posts( $args );

    $result[ $index ]['category']  = $category;
    $result[ $index ]['documents'] = $documents;
}

return $result;
0

try now this code

$cat_ids=array();
foreach (get_categories() as $cat)
{
    array_push($cat_ids, $cat->cat_ID);
}
$the_query = new WP_Query(array('post_type'=>'post', array('category__and' => $cat_ids) ) );
if( $the_query->have_posts() ): 
    while ( $the_query->have_posts() ) : $the_query->the_post(); 
        echo the_title();
    endwhile;
endif; 
wp_reset_query();
-1

I've build something for me that I use quite alot, here is the code, you can use slugs, ids or the term object in the $categories array, if you want to get all the categories you can use get_terms(), and feed with a bunch of term objects, but be careful, there is no treatment for hierarchy on this code.

$categories = array( 1, 'slug', 3 );
echo '<ul>';
foreach($categories as $category) {
    $term = ( is_numeric($category) || is_object($category) ? get_term( $category, 'category' ) : get_term_by( 'slug', $category, 'category' ) );
    $args = array(
        'cat' => $term->term_id
        // Add any other arguments to fit your needs
    );
    $q = new WP_Query( $args );
    if( $q->have_posts() ) {
        echo '<li><a href="' . get_term_link( $term->term_id, 'category' ) . '">' . $term->name . '</a><ul>';
        while( $q->have_posts() ) {
            $q->the_post();
            echo '<li><a href="' . get_permalink() . '">' . get_the_title() . '</a></li>';
        }
        echo '</ul></li>';
    } else {

    }
}
echo '</ul>';
1
  • O(n2) inefficient
    – S..
    Apr 29, 2018 at 17:47
-1

Untested, yet one of the simplest approaches I would try is the following:

<?php
    $category_ids = get_all_category_ids();
    foreach ($category_ids as $values) {
        $args = array('category' => $value);
        $posts_array = get_posts( $args );
        foreach ($posts_array as $post) : setup_postdata($post);
?> 
    <li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
<?php
        endforeach;
        wp_reset_query();
    }
?>
4
  • Same problem as Sagive's code
    – Ben
    Feb 24, 2012 at 23:13
  • ok. There is no way to get a single query done. Loading any page for Wordpress has multiple queries as it is, before adding custom code. All built-in queries use the WPDB class. The only way to do it in a single query is with your own transact sql or by making WPDB a parent class to another class for this purpose and calling it that way. Mar 4, 2012 at 0:14
  • Nota that get_all_category_ids(); is now depreciated Jan 6, 2015 at 8:16
  • O(n) inefficient
    – S..
    Apr 29, 2018 at 17:47
-1

You can use this... Set the amount of posts you need...

Also I have put it all inside a div, so you can do the structure and design your looking for.

<?php  
    $allcats = get_categories('child_of=0'); 

    foreach ($allcats as $cat) :
        $args = array(
            'posts_per_page' => 3, // set number of post per category here
            'category__in' => array($cat->term_id)
        );

        $customInCatQuery = new WP_Query($args);

        if ($customInCatQuery->have_posts()) :
            echo '<div>';
            echo '<h3>'.$cat->name.'</h3>';
            echo '<ul>';
            while ($customInCatQuery->have_posts()) : $customInCatQuery->the_post(); ?>

            <li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
        <?php
            endwhile; 
            echo '</ul></div>'; 
        ?>

<?php
        else :
            echo 'No post published in:'.$cat->name;                
        endif; 
        wp_reset_query();
    endforeach; 
?>

Hope this helps.

7
  • 1
    Thx, but take the example of say 10 categories. In that case your code causes 11 SQL queries. I am looking for 1 SQL query. The best thing would probably be to get all posts, ordered by category, but I don't know how to do that (I didn't find order_by = cat in the codex) !?
    – Ben
    Feb 24, 2012 at 23:12
  • i dont think it matters one bit if its 10 categories or 20... (tried it with 20) unless you are trying to load a huge number posts which in any case might slow the page load.. try it - you'll find it real breezy - trying to rearrange after loaded via categories would mean building a huge and mostly useless code (to my humble opinion :) )
    – Sagive
    Feb 24, 2012 at 23:33
  • 1
    No it doesn't, you just need to change the output slightly (i.e. end list for cat n, and start list for cat n+1) everytime a post has a category that is different from the previous post's category... A lot simpler than bombarding your poor database with 20+ queries per pageload... (to my humble opinion :)
    – Ben
    Feb 25, 2012 at 0:04
  • i think it would we asking the database the same question but just ordering stuff differently on the output end.. i dont see the difference... besides - its a logical query.. "give me the first * from this category"... give me the first * from that category" instead of give me that post, again, again, again.. its the same way but i am puling the data in the way i need it... unless i am mistaken..
    – Sagive
    Feb 25, 2012 at 0:37
  • 1
    The difference is the number of db queries. One big vs. many small... ;)
    – Ben
    Feb 25, 2012 at 1:11
-2

if you are looking for a plugin, List Category Posts might work for you.

Fore a query, take a look at get_posts

2

Your Answer

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

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