2

I am using a custom post type that uses two taxonomies "colour" and "style". I have a script (from this thread) which groups them together so when you are on a colour taxonomy page they are grouped by style like this:

Blue > Style 1

  • Product 1
  • Product 2
  • Product 3
  • Product 4
  • Product 5

Blue > Style 2

  • Product 1
  • Product 2
  • Product 3
  • Product 4
  • Product 5

Now this all works great but it returns EVERY post. I want to limit it to the first three posts for each style. I know I can do something like the following but it limits posts for the whole page not per style. Any help? :)

//limit to three per style
add_action('pre_get_posts', 'change_tax_num_of_posts' );

function change_tax_num_of_posts( $wp_query ) {

    if( is_tax('colour') && is_main_query() ) {

      $wp_query->set('posts_per_page', 3);

    }

}
  • This is going to take some work, you will need to create and run a couple of custom queries, collect all relevant post ID's and then use pre_get_posts to alter the alter the main query. You have to smart here with your custom queries as they can become expensive, and you will also need to alter the current tax_query. You will most probably then also need to scrap the the_posts filter from my answer in the link – Pieter Goosen Aug 11 '15 at 11:05
  • Thank you @PieterGoosen I am just thinking about this again now and wondering if an easier way may be to add another taxonony to the custom post, called "featured" and then only return the ones tagged with "featured" on the colour taxonomy page. – caffeinehigh Aug 11 '15 at 11:12
  • This will require that will need to remove terms everytime a new post is added. I will try to look into this tonight when I'm home. It is quite a interesting question that others can benefit from as well ;-) – Pieter Goosen Aug 11 '15 at 12:42
  • Thank you that would be great! I will also keep digging in to this and if I find a solution will post it up here for others to see. – caffeinehigh Aug 11 '15 at 12:53
1

As I have stated in comments, this will take some work. In short, you will need the following:

  • Get all the terms from the style taxonomy. Here we would only want the term ids

  • Get the current tax_query from the colour taxonomy. Use each term from the style taxonomy and create a new tax_query to get posts from the current page's term and the term being looped over from the style taxonomy

  • Run a custom query on each term from the style taxonomy to get an array of post ids from each term. Here you would want to add all your custom parameters. We will use the order from this custom queries as post order

  • Pass the array of post ids to the main query. Here you would not want to add custom sorting etc. We will use the sequence of the post ids in the post id array for sorting

FEW NOTES BEFORE THE CODE:

  • The code requires PHP 5.4+ due to the use of short array syntax ([]). You should also not be using any version older than PHP5.4. If you however do, just change the short array syntax to the old array syntax (array()). For example, get_terms( $taxonomy, ['fields' => 'ids'] ) should become get_terms( $taxonomy, array( 'fields' => 'ids' ) )

  • I have build in a transient system to reduce the extra strain coming from all the extra work that we need to do. This transient is set to expire in a week, you can adjust this to be longer or shorter. The transient will be automatically deleted when a new post is published, deleted, undeleted or updated

  • I have commented the code very well so you can follow and make some kind of sense from what I was doing as I went along

  • You can extend and modify as needed. Make sure to check out my comments

  • With this new code, you can scrap my complete idea of sorting as described in my linked answer on SO. The code given in this answer should take care of this sorting. Any adjustments that you would want to make should be done in the custom queries and get_terms calls. Just remember to flush your transients after each modification, or better, uncomment the transient calls and return to normal once you are happy with all your modifications

THE CODE:

As always, we will be using pre_get_posts to alter the main query as needed

add_action( 'pre_get_posts', function ( $q )
{       
    if (    !is_admin() // Targets only front end queries
         && $q->is_main_query() // Targets only main query
         && $q->is_tax( 'colour' ) // Targets only taxonomy pages
    ) {

        /** 
         * To save on the extra work that we need to do to get our results,
         * lets save everything in a transient. We will save the string of post ids
         * in the transient. 
         *
         * We will only delete and recreate this transient when a new post is published,
         * deleted, undeleted or updated. This will save us a lot of extra hard work
         * on every page load
         *
         * @link https://codex.wordpress.org/Transients_API
         */
        $queried_object = get_queried_object(); // Get the current queried object to build a unique transient name
        /**
         * Use md5 to build a unique transient name to avoid any conflicts
         * The code below will create a unique transient name which will look like this
         * "colourtax_1ac37e97ee207e952dfc2b8f7eef110f"
         *
         * Note, this should NOT be longer that 45 characters else the transient will be regenerated
         * on each page load. Transients are expensive to create, so don't want to recreate this on every
         * page load due to a name being to long. As a quick guide the md5 part generate a 32 character string,
         * so the "colourtax_" part should be a maximum of 13 characters
         */
        $unique_transient_name = 'colourtax_' . md5( $queried_object->taxonomy . $queried_object->slug . $queried_object->term_id );
        if ( false === ( $post_ids_array = get_transient( $unique_transient_name ) ) ) {

            // Gets the current tax_query
            $tax_query = $q->tax_query->queries; 
            // Choose the taxonomy to sort by
            $taxonomy = 'style'; 
            // Set the variable to hold the sorted post ids according to terms
            $post_ids_array = [];

            /**
             * Get all the terms from the selected taxonomy to sort by. Just get term ids
             * Add additional arguments here as needed
             * 
             * @link https://codex.wordpress.org/Function_Reference/get_terms
             */
            $terms = get_terms( $taxonomy, ['fields' => 'ids'] ); 
            if (    $terms // Check if the array has valid terms, not empty
                 && !is_wp_error( $terms ) // Check that we do not have any error
            ) { 
                // Define a variable to hold all post ID
                $posts_ids = '';
                foreach ( $terms as $term ) {
                    /**
                     * NOTE: You would want to do everything here
                     *
                     * Build our query args, add all your relevant arguments here
                     * You should extend this to include your custom parameter values
                     * like meta_queries en sorting order.
                     * Do a var_dump( $wp_query ) and use the relevant arguments from
                     * there to make this dynamic
                     */
                    $args = [
                        'post_type' => 'any',
                        'posts_per_page' => 3, // Get only 3 posts per term
                        'fields' => 'ids', // Only get post ids to make query faster and more lean
                        // Build a tax_query to add additional terms from selected taxonomy to sort by  
                        'tax_query' => [ 
                            $tax_query, // Our default tax_query from the taxonomy page
                            [
                                'taxonomy' => $taxonomy,
                                'terms' => $term,
                                'include_children' => false,
                            ],
                        ],
                    ];
                    // Return an array of post ids only
                    $posts_array = get_posts( $args );
                    // First check if we have posts to avoid bugs in our code
                    if ( $posts_array ) {
                        // Break the ids array up into a string for later processing
                        foreach ( $posts_array as $v )
                            $posts_ids .= ' ' . $v;
                        unset( $v );    
                    } //endif $posts_array

                } //endforeach $terms
                unset( $term );

                // ADDITIONAL, CAN DELETE THE FOLLOWING SECTION IF YOU WANT TO. READ COMMENTS BELOW

                /**
                 * You can remove the following section. The idea here is as follow:
                 * Any post without a term in the style taxonomy will not be displayed on
                 * a colour taxonomy term page. To avoid this, we will need to get all posts
                 * that does not have a post in style taxonomy. This posts will be displayed last
                 * on the page
                 *
                 * If you are very sure that all posts are tagged in a colour AND style taxonomy
                 * term, then you can remove this section, this is really just a fall back
                 */
                $args_additional = [
                    'post_type' => 'any',
                    'posts_per_page' => 3, // Get only 3 posts without style taxonomy term, adjust as needed
                    'fields' => 'ids', // Only get post ids to make query faster and more lean
                    // Build a tax_query to get posts that is not tagged in style taxonomy  
                    'tax_query' => [ 
                        $tax_query, // Our default tax_query from the taxonomy page
                        [
                            'taxonomy' => $taxonomy,
                            'terms' => $terms,
                            'include_children' => false,
                            'operator' => 'NOT IN', // Posts should not have these terms from style taxonomy
                        ],
                    ],
                ];
                // Return an array of post ids only
                $posts_array_2 = get_posts( $args_additional );
                // First check if we have posts to avoid bugs in our code
                if ( $posts_array_2 ) {
                    // Break the ids array up into a string for later processing
                    foreach ( $posts_array_2 as $v )
                        $posts_ids .= ' ' . $v;
                    unset( $v );    
                } //endif $posts_array

                // STOP DELETING HERE!!

                // Create an array of post ids from the $posts_ids string
                $post_ids_array = explode( ' ', ltrim( $posts_ids ) );

            } //endif $terms

            /**
             * Set the transient if it does not exist. 
             * NOTE: We will choose a week for expiry date, set as needed
             *
             * @link https://codex.wordpress.org/Transients_API#Using_Time_Constants
             */     
            set_transient( $unique_transient_name, $post_ids_array, 7 * DAY_IN_SECONDS );   
        } // endif transient check

        /**
         * Check if we have an array of post ID's before changing anything on the tax page
         *
         * Here we will alter the main query. You do not want to add or remove anything
         * here. Any custom parameters like sorting should be done in the custom queries
         * above
         *
         * DO NOT CHANGE ANYTHING IN THE CODE BELOW EXCEPT posts_per_page
         */
        if ( !empty( $post_ids_array ) ) {
            $q->set( 'post__in', $post_ids_array ); // Posts to get as set in our array, max of 3 posts per term
            $q->set( 'orderby', 'post_in' ); // Sort our posts in the order it is passed in the post__in array
            $q->set( 'order', 'ASC' );
            $q->set( 'posts_per_page', -1 ); // You can change this, if I remember, you need all posts on one page
        }

    } //endif conditional checks for query
});

This will take care of everything except the flushing of the transients on post publish, delete, etc

The following code will take care of this. Everytime a post is updated, published, trashed or untrashed, the transition_post_status hook fires, so we will use that logic to delete all our transients which contains part of the colourtax_ name

add_action( 'transition_post_status', function ()
{
    global $wpdb;
    $wpdb->query( "DELETE FROM $wpdb->options WHERE `option_name` LIKE ('_transient%_colourtax_%')" );
    $wpdb->query( "DELETE FROM $wpdb->options WHERE `option_name` LIKE ('_transient_timeout%_colourtax_%')" );
});
  • Wow excellent work and very informative, much appreciated! I'm going to take some time to read through your comments see if I can learn the details of what is going on. :) Thank you again! – caffeinehigh Aug 12 '15 at 20:31
0

Ok, I think I have found a way of doing this. Although I must say I haven't tested it out yet and it's probably not the best way. So happy to see any other solutions.

What I have done is created a meta box in my custom post with a radio button that has options "featured" and "not featured". I set the three custom posts of each group I want to show and then filter the main query with the meta key that matches. Like so:

function colour_tax_featured($query) {

if( is_tax('colour') && $query->is_main_query() ) {

    $query->set('meta_key', 'meta_box_featured_colour');
        $query->set('meta_value', 'featured_colour');
    return $query;
}

 }

    add_action('pre_get_posts', 'colour_tax_featured' );
  • I'm off to bed now, had a long day, I'm busy with the approach as stated in comments to make it dynamic, it is a bit more tricky though than what I had originally thought. As said, the issue with your approach is that you need to constantly change fields when you publish new posts, and that can be frustrating ;-). Should have something working by tomorrow night. Enjoy – Pieter Goosen Aug 11 '15 at 20:00
  • Excellent thank you for your work and I look forward to seeing your solution! A dynamic method would be much better than what I have come up with. – caffeinehigh Aug 11 '15 at 20: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.