Looping through posts per category gives same posts for each category

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

Leave a Comment