Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

I have 2 custom post types 'bookmarks' and 'snippets' and a shared taxonomy 'tag'. I can generate a list of all terms in the taxonomy with get_terms(), but I can't figure out how to limit the list to the post type. What I'm basically looking for is something like this:

get_terms(array('taxonomy' => 'tag', 'post_type' => 'snippet'));

Is there a way to achieve this? Ideas are greatly appreciated!!

Oh, I'm on WP 3.1.1

share|improve this question

6 Answers 6

up vote 5 down vote accepted

Here is another way to do something similar, with one SQL query:

static public function get_terms_by_post_type( $taxonomies, $post_types ) {

    global $wpdb;

    $query = $wpdb->prepare( "SELECT t.*, COUNT(*) from $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id INNER JOIN $wpdb->term_relationships AS r ON r.term_taxonomy_id = tt.term_taxonomy_id INNER JOIN $wpdb->posts AS p ON p.ID = r.object_id WHERE p.post_type IN('".join( "', '", $post_types )."') AND tt.taxonomy IN('".join( "', '", $taxonomies )."') GROUP BY t.term_id");

    $results = $wpdb->get_results( $query );

    return $results;

}
share|improve this answer
    
Yes! This does exactly what I want. –  Gavin Hewitt Nov 7 '11 at 1:33
1  
Thank you for the question and answer! –  dSquared Dec 6 '11 at 16:39
    
print_r(get_terms_by_post_type(array('category') , array('event') )); shows Warning: Missing argument 2 for wpdb::prepare() –  devo Jun 20 at 2:20

So it just happens that I needed something like that for a project I'm working on. I simply wrote a query to select all posts of a custom type, then I check what are the actual terms of my taxonomy they are using.

Then I got all terms of that taxonomy using get_terms() and then I only used those that were in both of the lists, wrapped it up in a function and I was done.

But then I needed more then just the ID's: I needed the names so I added a new argument named $fields so I could tell the function what to return. Then I figured that get_terms accepts many arguments and my function was limited to simply terms that are being used by a post type so I added one more if statement and there you go:

The Function:

/* get terms limited to post type 
 @ $taxonomies - (string|array) (required) The taxonomies to retrieve terms from. 
 @ $args  -  (string|array) all Possible Arguments of get_terms http://codex.wordpress.org/Function_Reference/get_terms
 @ $post_type - (string|array) of post types to limit the terms to
 @ $fields - (string) What to return (default all) accepts ID,name,all,get_terms. 
 if you want to use get_terms arguments then $fields must be set to 'get_terms'
*/
function get_terms_by_post_type($taxonomies,$args,$post_type,$fields = 'all'){
    $args = array(
        'post_type' => (array)$post_type,
        'posts_per_page' => -1
    );
    $the_query = new WP_Query( $args );
    $terms = array();
    while ($the_query->have_posts()){
        $the_query->the_post();
        $curent_terms = wp_get_object_terms( $post->ID, $taxonomy);
        foreach ($curent_terms as $t){
          //avoid duplicates
            if (!in_array($t,$terms)){
                $terms[] = $c;
            }
        }
    }
    wp_reset_query();
    //return array of term objects
    if ($fields == "all")
        return $terms;
    //return array of term ID's
    if ($fields == "ID"){
        foreach ($terms as $t){
            $re[] = $t->term_id;
        }
        return $re;
    }
    //return array of term names
    if ($fields == "name"){
        foreach ($terms as $t){
            $re[] = $t->name;
        }
        return $re;
    }
    // get terms with get_terms arguments
    if ($fields == "get_terms"){
        $terms2 = get_terms( $taxonomies, $args );
        foreach ($terms as $t){
            if (in_array($t,$terms2)){
                $re[] = $t;
            }
        }
        return $re;
    }
}

Usage:

If you only need a list of term id's then:

$terms = get_terms_by_post_type('tag','','snippet','ID');

If you only need a list of term names then:

$terms = get_terms_by_post_type('tag','','snippet','name');

If you only need a list of term objects then:

$terms = get_terms_by_post_type('tag','','snippet');

And if you need to use extra arguments of get_terms like: orderby, order, hierarchical ...

$args = array('orderby' => 'count', 'order' => 'DESC',  'hide_empty' => 1);
$terms = get_terms_by_post_type('tag',$args,'snippet','get_terms');

Enjoy!

Update:

To fix the term count to specific post type change:

foreach ($current_terms as $t){
          //avoid duplicates
            if (!in_array($t,$terms)){
                $terms[] = $t;
            }
        }

to:

foreach ($current_terms as $t){
    //avoid duplicates
    if (!in_array($t,$terms)){
        $t->count = 1;
        $terms[] = $t;
    }else{
        $key = array_search($t, $terms);
        $terms[$key]->count = $terms[$key]->count + 1;
    }
}
share|improve this answer
    
wouldn't it be better if you use (array) $args instead of a list of 4 $vars? This would allow you to not care about order you throw in the arguments, so something like get_terms_by_post_type( $args = array( 'taxonomies', 'args', 'post_type', 'fields' => 'all') ) and then call them inside the function with $args['taxonomies']. This would help you staying away from adding empty values and having to remember the order of your arguments. I'd also suggest to use single quotes instead of double. I saw them beeing up to five times faster. –  kaiser Apr 9 '11 at 10:48
1  
@kaiser - Double quoted strings have to be parsed, where as single quoted values are always treated as literal. When you're using variables in a string it makes sense and it's perfectly fine to use double quotes, but for non-variable string values single quotes are more ideal(because they won't need to be parsed) and slightly faster(we're talking about milliseconds in most cases). –  t31os Apr 9 '11 at 11:35
    
@t31os - Absolutely correct. I still prefer 'this is my mood: '.$value over "this is my mood: $value", because of readability. When it comes to speed: It's not slightly - i meassured up to five times. And when you use double quotes in your whole theme everywhere, they'll quickly sum up when you got a lot of requests. Anyway good you made that clear. –  kaiser Apr 9 '11 at 12:25
    
@t31os Out of a discussion I re-meassured the speed of " vs. ' and I was wrong. The difference is far outside anything anybody would notice. –  kaiser Jul 24 '11 at 15:24
    
+1 nice function! 2 typos: $taxonomies is used in the function $taxonomy and $terms[] = $c; has to be $terms[] = $t; –  Rob Vermeer Feb 5 '12 at 17:45

I wrote a function that allows you to pass post_type in the $args array to the get_terms() function:

HT to @braydon for writing the SQL.

 /**
 * terms_clauses
 *
 * filter the terms clauses
 *
 * @param $clauses array
 * @param $taxonomy string
 * @param $args array
 * @return array
**/
function terms_clauses($clauses, $taxonomy, $args)
{
    global $wpdb;

    if ($args['post_type'])
    {
        $clauses['join'] .= " INNER JOIN $wpdb->term_relationships AS r ON r.term_taxonomy_id = tt.term_taxonomy_id INNER JOIN $wpdb->posts AS p ON p.ID = r.object_id";
        $clauses['where'] .= " AND p.post_type='{$args['post_type']}'"; 
    }
    return $clauses;
}
add_filter('terms_clauses', 'terms_clauses', 10, 3);
share|improve this answer

I was unable to make the get_terms arguments to work with Gavin's version of the code above, but finally did by changing

$terms2 = get_terms( $taxonomy );

to

$terms2 = get_terms( $taxonomy, $args );

as it was in the original function from Bainternet.

share|improve this answer
    
Fixed it in the current version –  Gavin Hewitt Apr 10 '11 at 11:06

Avoid Duplicates:

//avoid duplicates
    $mivalor=$t->term_id;
    $arr=array_filter($terms, function ($item) use ($mivalor) {return isset($item->term_id) && $item->term_id == $mivalor;});

    if (empty($arr)){
    $t->count=1;
            $terms[] = $t;
        }else{
            $key = array_search($t, $terms);
            $terms[$key]->count = $terms[$key]->count + 1;
        }
share|improve this answer
1  
Can you explain why this solves the problem? See How to Answer. –  brasofilo Feb 26 '13 at 17:38

@Bainternet: Thanks! I had to alter the function slightly because it wasn't working (some typos). The only problem now is that the term count is off. The count isn't taking the post type into consideration so I don't think you can use get_terms() in this.

function get_terms_by_post_type($post_type,$taxonomy,$fields='all',$args){
    $q_args = array(
        'post_type' => (array)$post_type,
        'posts_per_page' => -1
    );
    $the_query = new WP_Query( $q_args );

    $terms = array();

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

        global $post;

        $current_terms = get_the_terms( $post->ID, $taxonomy);

        foreach ($current_terms as $t){
            //avoid duplicates
            if (!in_array($t,$terms)){
                $t->count = 1;
                $terms[] = $t;
            }else{
                $key = array_search($t, $terms);
                $terms[$key]->count = $terms[$key]->count + 1;
            }
        }
    }
    wp_reset_query();

    //return array of term objects
    if ($fields == "all")
        return $terms;
    //return array of term ID's
    if ($fields == "ID"){
        foreach ($terms as $t){
            $re[] = $t->term_id;
        }
        return $re;
    }
    //return array of term names
    if ($fields == "name"){
        foreach ($terms as $t){
            $re[] = $t->name;
        }
        return $re;
    }
    // get terms with get_terms arguments
    if ($fields == "get_terms"){
        $terms2 = get_terms( $taxonomy, $args );

        foreach ($terms as $t){
            if (in_array($t,$terms2)){
                $re[] = $t;
            }
        }
        return $re;
    }
}

EDIT: Added the fix(es). But somehow it's still not working for me. The count still shows the incorrect value.

share|improve this answer
    
That's a different story, but you can count when avoiding duplicates in the while loop. –  Bainternet Apr 9 '11 at 13:34
    
I updated my answer with a term count fix. –  Bainternet Apr 9 '11 at 14:25
1  
Please don't add follow-ups as answers, unless you are specifically answering your own question, additions should instead be made to the original question. –  t31os Apr 10 '11 at 10:06
1  
@t31os: Ah yes, I was wondering how to add an addition. Didn't think of editing my question. Thanks! –  Gavin Hewitt Apr 10 '11 at 10:58
    
How can I call this? print_r(get_terms_by_post_typea(array('event','category','',array())); this one gives Warning: Invalid argument supplied for foreach() for the line foreach ($current_terms as $t){ –  devo Jun 20 at 2:32

protected by Community Feb 26 '13 at 17:20

Thank you for your interest in this question. Because it has attracted low-quality answers, posting an answer now requires 10 reputation on this site.

Would you like to answer one of these unanswered questions instead?

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