0

I've got a 'downloads' custom post, with his custom taxonomy. The taxonomy has 2 main terms. One of the 2, has 4 sub-terms. I'm currently using the wp templating system to get a standard wpquery of the parent term into a taxonomy-taxonomyname-termname.php page. Till here, all fine.

Now what I'd like to do is to show all the posts under the parent term, without pagination (this is not a problem). But I would like show them splitted/grouped by sub-terms (with the sub-term name above each of the 4 sub-terms groups).

Normally, I would perform a custom query. But since this time I'm trying to take advantage of the default wp query system, I'd like to use just the loop. Is there a way to have the posts grouped by sub-term?

  • Are posts assigned to the parent and child, or just the child term? – Pieter Goosen Jul 27 '15 at 15:29
  • Hi Pieter, they can be both – Luca Reghellin Jul 29 '15 at 6:29
  • Ok, then my answer will not work 100%. Will need to change my approach slightly – Pieter Goosen Jul 29 '15 at 6:34
3

Always great if someone wants to use better, more correct and more efficient ways to get things done. To achieve what you need is not difficult.

WORKSFLOW:

  • Use the usort() to create your custom sort order

  • Use the the_posts filter to sort and return the sorted array of posts just before the loop is executed

  • Inside the loop, all you need to do is to compare the previous post and current post terms and act on that

THE CODE:

Lets look at the code:

First, get the sorting done using usort() and the_posts filter: (Requires at least PHP 5.4+. If you don't have at least PHP 5.4, then you are sure to run into security issues. Also note, all the code is untested and might be buggy)

add_filter( 'the_posts', function ( $posts, $q )
{
    if ( $q->is_main_query() // Target the main query only
         && $q->is_tax() // Change to target only specific term or taxonomy
    ) {
        /**
         * We will now sort the $posts array before the loop executes. We will use usort()
         *
         * There is a bug in usort causing the following error:
         * usort(): Array was modified by the user comparison function
         * @see https://bugs.php.net/bug.php?id=50688
         * This bug has yet to be fixed, when, no one knows. The only workaround is to suppress the error reporting
         * by using the @ sign before usort
         */
        @usort( $posts, function ( $a, $b )
        {
            /**
             * Get the respective terms from the posts 
             * We will use the first term's name
             */
            $array_a = get_the_terms( $a->ID, 'TAXONOMY_NAME_HERE' )[0]->name;
            $array_b = get_the_terms( $b->ID, 'TAXONOMY_NAME_HERE' )[0]->name;

            // Sort by term name, if they are the same, sort by post date
            if (  $array_a != $array_b ) {
                return strcasecmp( $array_a, $array_b ); // Sort terms alphabetically, ascending
                // return strcasecmp( $array_b, $array_a ); // Sort terms alphabetically, descending
            } else {
                return $a < $b; // Sort by date if terms are the same. Change < to > if the post date order is incorrect
            }
        }
    }
}, 10, 2 );

This should take care of the sorting. As I have stated, just make sure the date sorting is correct within the terms. If not, just change < to >

Now we can display the term names inside the loop. Adjust and modify as needed

if ( have_posts() ) {
    // Define variable to hold previous post term name
    $term_string = '';
    while ( have_posts() ) {
    the_post();
        global $post;
        // Get the post terms. Use the first term's name
        $term_name = get_the_terms( $post->ID, 'TAXONOMY_NAME_HERE' )[0]->name;
        // Display the taxonomy name if previous and current post term name don't match
        if ( $term_string != $term_name )
            echo '<h2>' . $term_name . '</h2>'; // Add styling and tags to suite your needs

        // Update the $term_string variable
        $term_string = $term_name;

        // REST OF YOUR LOOP

    }
}
  • Frankly, I'm glad there's a more efficient way than my suggestion. Thanks for this. I think this will address a similar issue I've been having with one of my own projects. – Mike Jul 27 '15 at 17:36
  • My pleasure, glad this makes sense. I have recently done a huge shortcode similar to this to another question with a lot of options. Feel free to check it out here – Pieter Goosen Jul 27 '15 at 17:41
  • @Mike and Stratboy, there was a small bug in my sorting function, have repaired it. Please see my update :-) – Pieter Goosen Jul 29 '15 at 7:12
  • I see you have unaccepted this answer, do you have any issue or question related to this ;-). Please let me know so we can get this solved – Pieter Goosen Jul 29 '15 at 14:04
2

If I understand the question correctly, you want to produce something like this:

Sub-Item 1

Post 1

Post 2

[...]

Sub Item 2

Post 1

Post 2

[...]

There may be a more efficient way than what I am about to tell you, but I would try something like this:

First create an array to hold your sorted posts.

$sortedPosts = array();

Next, create an array for each of your sub-terms.

foreach ($subTerms as $subTerm) {
    $sortedPosts[$subTerm] = array();
}

Loop through the posts in your query, and sort them based on the assigned term.

if (have_posts()) {
    while (have_posts()) {
        the_post();
        $postTerms = get_terms('taxonomyName', array('fields' => 'name'));

        if (is_array($postTerms)) {
            foreach ($subTerms as $subTerm) {
                if (in_array($subTerm, $postTerms)) {
                    global $post;

                    $sortedPosts[$subTerm][] = $post;
                    /* This grabs the array stored at $sortedPosts[$subTerm]
                       and then inserts the current post at the end of it. */
                }
            }
        }
    }
}

Once all the posts have been sorted, loop through your sorted array to print the display.

foreach ($subTerms as $subTerm) {
    $myPosts = $sortedPosts[$subTerm];

    echo '<h1>' . $subTerm . '</h1>';

    foreach ($myPosts as $currentPost) {
        global $post;
        $post = $currentPost;

        // Enter post display stuff here
    }

    echo '<hr>'
}
  • I'm not sure about performance, but this is a way to get the job done. You can sort the loop before executing it ;-) – Pieter Goosen Jul 27 '15 at 15:39
0

Ok, thank you all for your good answers. Anyway, I need more flexibility, so I ended up not using the wp template auto-sugars...

This answer is therefore not strictly related to my own original question, but I think it can help people too.

I built a custom function that retrieves a set of custom posts form a specific custom taxonomy and an optional parent term. If parent is passed, returned posts will belong to direct parent term children. This is the code:

// $post_type: string
    // $taxonomy: string
    // $parent_term: string (slug) || int (term_id)
    // retrieves posts of a specific post_type and taxonomy, sorted by term
    function get_taxonomy_posts($post_type, $taxonomy, $parent_term = ''){

        if(!$post_type || !$taxonomy) return false;

        global $wpdb;

        //if $parent_term is defined and is not an integer then should be a slug, so let's get its id
        if(!empty($parent_term) && !is_int($parent_term)){
            $parent_data = get_term_by('slug',$parent_term,$taxonomy);
            $parent_term = $parent_data->term_id;
        }

        $term_ids = get_terms($taxonomy, array( 'parent' => $parent_term, 'fields' => 'ids' ));
        if(is_wp_error($term_ids)) return false;

        $term_ids_str = implode(',',$term_ids);

      // NOTE: terms.term_order gets added to tables by this plugin:
      // http://wordpress.org/plugins/taxonomy-terms-order/
      $query = $wpdb->prepare(
        "select p.ID, p.post_title, p.post_content, p.post_excerpt, t.term_id, t.term_order, t.name as term_name, t.slug as term_slug, tt.count
        from " . $wpdb->prefix . "posts p
        inner join " . $wpdb->prefix . "term_relationships rel on p.ID = rel.object_id
        inner join " . $wpdb->prefix . "term_taxonomy tt on tt.term_taxonomy_id = rel.term_taxonomy_id
        inner join " . $wpdb->prefix . "terms t on tt.term_id = t.term_id
        where p.post_type = %s and p.post_status = %s and t.term_id in (" . $term_ids_str . ")
        order by t.term_order, p.menu_order"
        ,$post_type
        ,"publish"
      );

      $records = $wpdb->get_results($query);
      $posts_array = array();

      foreach($records as $record){
        $term_name = $record->term_name;
        if(!key_exists($term_name, $posts_array)) $posts_array[$term_name] = array();
        $posts_array[$term_name][] = $record;
      }

      return $posts_array;

    }//end get taxonomy_posts 

It will give you an array where keys are term names and values are arrays of posts (or more precisely, post data of posts) belonging to that term. It can be easily modified to have for example keys as term_id instead of term_name. The single post data returned is like this:

    object(stdClass)#2308 (9) {
      ["ID"]=>
      string(3) "269"
      ["post_title"]=>
      string(26) "title"
      ["post_content"]=>
      string(0) "content"
      ["post_excerpt"]=>
      string(0) "excerpt"
      ["term_id"]=>
      string(3) "803"
      ["term_order"]=>
      string(1) "2"
      ["term_name"]=>
      string(27) "term name"
      ["term_slug"]=>
      string(26) "term-slug"
      ["count"]=>
      string(1) "3"
    }

SIDE NOTE 1: currently, I'm using the taxonomy-terms-order plugins and so in the query I'm ordering terms by the custom term_order field. Again, if you don't use the plugin, just change the fields retrieved and order by clause as you prefer.

SIDE NOTE 2: Instead of a taxonomy-taxonomyname-termname.php, now I basically use a dummy page and apply a custom template (I mean /* Template Name: [name] */): this is not mandatory, but I do it to have the wp's main query just do a simple 1 post query instead a sort of duplicated term query.

Hope it helps!

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.