Wordpress - Get terms by taxonomy AND post_type
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;
}
}
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('%s') AND tt.taxonomy IN('%s')
GROUP BY t.term_id",
join( "', '", $post_types ),
join( "', '", $taxonomies )
);
$results = $wpdb->get_results( $query );
return $results;
}
Great question and solid answers.
I really liked the approach by @jessica using the terms_clauses filter, because it extends the get_terms function in a very reasonable way.
My code is a continuation of her idea, with some sql from @braydon to reduce duplicates. It also allows for an array of post_types:
/**
* my_terms_clauses
*
* filter the terms clauses
*
* @param $clauses array
* @param $taxonomy string
* @param $args array
* @return array
**/
function my_terms_clauses($clauses, $taxonomy, $args)
{
global $wpdb;
if ($args['post_types'])
{
$post_types = implode("','", array_map('esc_sql', (array) $args['post_types']));
// allow for arrays
if ( is_array($args['post_types']) ) {
$post_types = implode( "','", $args['post_types'] );
}
$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 IN ('". $post_types. "') GROUP BY t.term_id";
}
return $clauses;
}
add_filter('terms_clauses', 'my_terms_clauses', 99999, 3);
Because get_terms doesn't have a clause for GROUPY BY, I had to add it to the end of the WHERE clause. Notice that I have the filter priority set very-high, in hopes it will always go last.