1

For a specific section on my website I loop through some categories and get the three latest posts in that categorie and list them. Like so:

<?php $categories = get_categories(array('exclude' => '1, 4, 9, 10, 2899')); ?>

<?php foreach ($categories as $category) : ?>
    <div class="subject">
        <h3><?php echo $category->cat_name; ?></h3>
        <ul>
            <?php $args = array(
                'cat' => $category->cat_ID,
                'posts_per_page' => 3
            ); ?>
            <?php if (false === ( $category_posts_query = get_transient( 'category_posts' ) ) ) {
                $category_posts_query = new WP_Query($args);
                set_transient( 'category_posts', $category_posts_query, 36 * HOUR_IN_SECONDS );
            }
            ?>
            <?php while($category_posts_query->have_posts()) : ?>
                <?php $category_posts_query->the_post(); ?>
                <li><a href="<?php the_permalink(); ?>" title="<?php the_title(); ?>"><?php the_title(); ?></a></li>
            <?php endwhile; ?>
        </ul>
    </div>
<?php wp_reset_postdata(); ?>
<?php endforeach; ?>

However, the result is not what is expected: all posts are the same across categories even though the posts don't belong to the different categories: enter image description here

When I remove the transient for caching, everything works as expted.

<?php $categories = get_categories(array('exclude' => '1, 4, 9, 10, 2899')); ?>

<?php foreach ($categories as $category) : ?>
    <div class="subject">
        <h3><?php echo $category->cat_name; ?></h3>
        <ul>
            <?php $args = array(
                'cat' => $category->cat_ID,
                'posts_per_page' => 3
            ); ?>
            <?php $category_posts_query = new WP_Query($args); ?>
            <?php while($category_posts_query->have_posts()) : ?>
                <?php $category_posts_query->the_post(); ?>
                <li><a href="<?php the_permalink(); ?>" title="<?php the_title(); ?>"><?php the_title(); ?></a></li>
            <?php endwhile; ?>
        </ul>
    </div>
<?php wp_reset_postdata(); ?>
<?php endforeach; ?>

How do I add chacing to this last snippet?

  • I have added extra important info to my question. Please check it out – Pieter Goosen Apr 23 '15 at 7:51
2

I don't think you need so many transients set, one for each category. You can only use one, that is enough. Transients are quite expensive to set as it requires additional db queries, so you would want to cut down on the use.

As always, I like to keep my templates simple, short and sweet, so I tend to write custom functions to move the bulk of the code outside my templates. You can put everything in one function with one transient

You can try something like this (Requires PHP 5.4+)

function get_term_post_list( $taxonomy = 'category', $args = [], $query_args = [] )  
{
    /*
     * Check if we have a transient set
     */
    if ( false === ( $output = get_transient( 'term_list_' . md5( $taxonomy . json_encode( $args ) . json_encode( $query_args ) ) ) ) ) {

        /*
         * Use get_terms to get an array of terms
         */
        $terms = get_terms( $taxonomy, $args );

       if ( is_wp_error( $terms ) || empty( $terms ) )
            return null;

        /*
         * We will create a string with our output
         */
        $output = ''; 
        foreach ( $terms as $term ) {

            $output .= '<div class="subject">';
            $output .= '<h3>' . $term->name . '</h3>';
            $output .= '<ul>';

            /*
             * Use a tax_query to make this dynamic for all taxonomies
             */
            $default_args = [
                'no_found_rows' => true,
                'suppress_filters' => true,
                'tax_query' => [
                    [
                        'taxonomy' => $taxonomy,
                        'terms' => $term->term_id,
                        'include_children' => false
                    ]
                ]
            ];
            /*
             * Merge the tax_query with the user set arguments
             */
            $merged_args = array_merge( $default_args, $query_args );

            $q = new WP_Query( $merged_args );

            while($q->have_posts()) {
                $q->the_post();

                $output .= '<li><a href="' . get_permalink() . '" title="' . apply_filters( 'the_title', get_the_title() ) . '">' . apply_filters( 'the_title', get_the_title() ) . '</a></li>';

            }
            wp_reset_postdata();

            $output .= '</ul>';
            $output .= '</div>';

        }
        /*
         * Set our transient, use all arguments to create a unique key for the transient name
         */
        set_transient( 'term_list_' . md5( $taxonomy . json_encode( $args ) . json_encode( $query_args ) ), $output, 36 * HOUR_IN_SECONDS );
    }
    /*
     * $output will be atring, treat as such
     */
    return $output;
}

FEW NOTES

  • The first parameter, $taxonomy is the taxonomy to get terms and posts from. It defaults to 'category'

  • The second parameter is $args which is the arguments which should be passed to get_terms(). For more info, check get_terms()

  • The third parameter, $query_args is the arguments that should be passed to the custom query. Remember to avoid using any taxonomy related parameters. For more info, see WP_Query

  • If the second parameter is not set and the third parameter is set, pass an empty array to the second parameter

  • Modify and abuse the code as you see fit

USAGE

You can now use the function as follow in your templates

$post_list = get_term_post_list( 'category', ['exclude' => '1, 4, 9, 10, 2899'], ['posts_per_page' => 3] );
if ( $post_list !== null ) {
    echo $post_list;
}

If you do not pass anything to the second parameter but to the third, you should do the following (pass empty array)

$post_list = get_term_post_list( 'category', [], ['posts_per_page' => 3] );
if ( $post_list !== null ) {
    echo $post_list;
}

EDIT

In a rush, I totally forgot to add a function to flush the transient when a new post is published, updated, deleted or undeleted. As your code stands, the list will only be updated when the transient expires.

To flush the transient on the above post conditions, simply use the transition_post_status hook. Add the following to your functions.php

add_action( 'transition_post_status', function ()
{
        global $wpdb;
        $wpdb->query( "DELETE FROM $wpdb->options WHERE `option_name` LIKE ('_transient%_term_list_%')" );
        $wpdb->query( "DELETE FROM $wpdb->options WHERE `option_name` LIKE ('_transient_timeout%_term_list_%')" );
});

EDIT 2

From your comment to this answer

As you might have guessed I'd liked to use this in combination with my other question. How would I go about doing that? Should the exclusion of the terms happen in get_term_post_list()? Or should the two functions be merged somehow? (As I don't need them seperately.)

The best way to accomplish is to merge the two functions into one. That would make the most sense. I have merged the two functions and modified the functionality with the exclude parameters. What I have done is, you can now add an array of term id's to excludse via the get_terms() exclude parameter and you can also within the same function set an array of term slugs to exclude. The results will be merged into one single exclude parameter before being passed to get_terms()

Here is the function, again I have commented it well to make it easy to follow (This goes into functions.php)

function get_term_post_list( $taxonomy = 'category', $args = [], $query_args = [], $exclude_by_slug = [] )  
{
    /*
     * Check if we have a transient set
     */
    if ( false === ( $output = get_transient( 'term_list_' . md5( $taxonomy . json_encode( $args ) . json_encode( $query_args ) ) ) ) ) {

        /*
         * Check if any array of slugs is passed and if it is a valid array
         */
        if ( is_array( $exclude_by_slug ) && !empty( $exclude_by_slug ) ) {

            foreach ( $exclude_by_slug as $value ) {

                /*
                 * Use get_term_by to get the term ID and add ID's to an array
                 */
                $term_objects = get_term_by( 'slug', $value, $taxonomy );
                $term_ids[] = (int) $term_objects->term_id;

            }

        }

        /*
         * Merge $args['exclude'] and $term_ids 
         */
        if ( isset( $args['exclude'] ) && isset( $term_ids ) ) {

            $excluded_args = (array) $args['exclude'];
            unset( $args['exclude'] );
            $args['exclude'] = array_merge( $excluded_args, $term_ids );

        } elseif ( !isset( $args['exclude'] ) && isset( $term_ids ) ) {

            $args['exclude'] = $term_ids;

        } 

        /*
         * Use get_terms to get an array of terms
         */
        $terms = get_terms( $taxonomy, $args );

       if ( is_wp_error( $terms ) || empty( $terms ) )
            return null;

        /*
         * We will create a string with our output
         */
        $output = ''; 
        foreach ( $terms as $term ) {

            $output .= '<div class="subject">';
            $output .= '<h3>' . $term->name . '</h3>';
            $output .= '<ul>';

            /*
             * Use a tax_query to make this dynamic for all taxonomies
             */
            $default_args = [
                'no_found_rows' => true,
                'suppress_filters' => true,
                'tax_query' => [
                    [
                        'taxonomy' => $taxonomy,
                        'terms' => $term->term_id,
                        'include_children' => false
                    ]
                ]
            ];
            /*
             * Merge the tax_query with the user set arguments
             */
            $merged_args = array_merge( $default_args, $query_args );

            $q = new WP_Query( $merged_args );

            while($q->have_posts()) {
                $q->the_post();

                $output .= '<li><a href="' . get_permalink() . '" title="' . apply_filters( 'the_title', get_the_title() ) . '">' . apply_filters( 'the_title', get_the_title() ) . '</a></li>';

            }
            wp_reset_postdata();

            $output .= '</ul>';
            $output .= '</div>';

        }
        /*
         * Set our transient, use all arguments to create a unique key for the transient name
         */
        set_transient( 'term_list_' . md5( $taxonomy . json_encode( $args ) . json_encode( $query_args ) ), $output, 36 * HOUR_IN_SECONDS );
    }
    /*
     * $output will be string, treat as such
     */
    return $output;
}

As far as usage, you first need to look at the parameters that you can pass

  • Parameter 1 - $taxonomy -> taxonomy to get terms from. Default category

  • Parameter 2 - $args -> The arguments that should be passed to get_terms(). Please see get_terms() for a full list of parameters that can be passed as an array. Default empty array []

  • Parameter 3 - $query_args -> Custom argument to be passed to WP_Query. You should not pass taxonomy related parameters here as it cause issues with the deafult build in tax_query. For a full list of valid arguments which can be passed in an array, see WP_Query. Default empty array []

  • Parameter 4 - $exclude_by_slug -> An array of slugs to exclude. Please note, this has to be a valid array for this to work. Strings will be ignored. Default empty array []

You can now call the function in any of your template files as

$a = get_term_post_list( 'category', ['exclude' => [1, 13, 42]], ['posts_per_page' => 3], ['term-slug-1', 'term-slug-2'] );
echo $a;

OR

$taxonomy = 'category';
$args = [
    'exclude' => [1, 13, 42]
];
$query_args = [
    'posts_per_page' => 3
];
$exclude_by_slug = [
    '0' => ['term-slug-1', 'term-slug-2']
];
$a = get_term_post_list( $taxonomy, $args, $query_args, $exclude_by_slug );
echo $a;

Final note, if you don't need to pass a specific parameter, remember to just pass an empty array like

$a = get_term_post_list( 'category', [], [], ['term-slug-1'] );
echo $a;

All the above replaces all your code in your question

  • What if user adds a new term ? They have to wait 3 days to get visible on page ? – Sumit Apr 23 '15 at 7:35
  • That is easy, flush your transient once a new post is published, will update my answer :-) – Pieter Goosen Apr 23 '15 at 7:39
  • Great! Now this is perfect. – Sumit Apr 23 '15 at 7:41
  • 1
    @Sumit updated with the specific info. Thank for poiting that out. Totally forgot about that. Got really caught up last night between answers. ;-) – Pieter Goosen Apr 23 '15 at 7:51
  • I really appreciate your efforts! Especially your way of commenting everything thoroughly. It really helps a beginner like myself! I am a little confused on how to use this though. As you might have guessed I'd liked to use this in combination with my other question. How would I go about doing that? Should the exclusion of the terms happen in get_term_post_list()? Or should the two functions be merged somehow? (As I don't need them seperately.) – Bram Vanroy Apr 23 '15 at 10:33
0

Your category is ID being changed at every loop iteration and you are setting a common transient, which gives the same result every time even if category ID has been changed.

So you need to save transient for every category.

set_transient( 'category_posts_' . $category->cat_ID, $category_posts_query, 36 * HOUR_IN_SECONDS );

Try this solution

<?php $categories = get_categories(array('exclude' => '1, 4, 9, 10, 2899')); ?>

<?php foreach ($categories as $category) : ?>
    <div class="subject">
        <h3><?php echo $category->cat_name; ?></h3>
        <ul>
            <?php $args = array(
                'cat' => $category->cat_ID,
                'posts_per_page' => 3
            ); ?>
            <?php if (false === ( $category_posts_query = get_transient( 'category_posts_' . $category->cat_ID ) ) ) {
                $category_posts_query = new WP_Query($args);
                set_transient( 'category_posts_' . $category->cat_ID, $category_posts_query, 36 * HOUR_IN_SECONDS );
            }
            ?>
            <?php while($category_posts_query->have_posts()) : ?>
                <?php $category_posts_query->the_post(); ?>
                <li><a href="<?php the_permalink(); ?>" title="<?php the_title(); ?>"><?php the_title(); ?></a></li>
            <?php endwhile; ?>
        </ul>
    </div>
<?php wp_reset_postdata(); ?>
<?php endforeach; ?>
  • Add the whole list in one transient. Setting transients on every iteration is very expensive – Pieter Goosen Apr 22 '15 at 14:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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