Make WordPress Core

Opened 5 years ago

Last modified 8 days ago

#18106 reopened feature request

get_terms of posts in a specific post_type

Reported by: braydonf Owned by:
Milestone: Awaiting Review Priority: normal
Severity: normal Version:
Component: Taxonomy Keywords: close needs-patch
Focuses: Cc:


I have searched most everywhere to find how to use get_terms for a specific post type, and have only found this article: http://wordpress.stackexchange.com/questions/14331/get-terms-by-taxonomy-and-post-type However this is doing a query for posts and getting the terms for those posts rather than doing a straight SQL statement, so it's not as efficient or ideal.

Adding an additional argument for additional where statements or a argument to specific a post_type, are two solutions I can imagine.

Change History (12)

#1 @braydonf
5 years ago

Here is SQL to achieve something similar:

    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;


Perhaps better for another function all together.

#2 @Bjorn2404
5 years ago

I was looking to do the same thing - it would be nice if this was a built-in function. I couldn't get your function to work exactly as you have it above. Maybe when you copied it in some quotes were added or changed. The INs in the WHERE clause seemed to be causing issues. I think your back quotes might have turned into regular quotes. I think I have it working correctly.

#3 @braydonf
5 years ago

I've been working more on the above, and have included it as part of another plugin here: http://code.braydon.com/p/awe/source/tree/master/class-awe.php#L46

I think you're probably right about the quotes.

#4 @stephenh1988
4 years ago

  • Cc contact@… added

Someone asked how to do this on WordPress Stack Exchange http://wordpress.stackexchange.com/a/57449/9364. After implementing the above solution (with validation, cache etc). I realised a better way *might* be to use a wrapper around get_terms and make use of the filter.

This wrapper function takes the same arguments as get_terms, plus a 'post_types' key in the $args array. This takes a string|array of post types. It works for the defaults - but I haven't verified it for all arguments...

Not exactly future proof (neither solutions are) - but would be great to see this feature added at some point. I should image that count will cause problems as this is stored in the db. The workaround below replaces it with an SQL COUNT(*).

function wpse57444_get_terms($taxonomies, $args=array() ){

    if( !empty($args['post_types']) ){
        $args['post_types'] = (array) $args['post_types'];
        add_filter( 'terms_clauses','wpse_filter_terms_by_cpt',10,3);

        function wpse_filter_terms_by_cpt( $pieces, $tax, $args){
            global $wpdb;

            //Don't use db count
            $pieces['fields'] .=", COUNT(*) " ;

            //Join extra tables to restrict by post type.
            $pieces['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 ";

            //Restrict by post type and Group by term_id for COUNTing.
            $post_types_str = implode(',',$args['post_types']);
            $pieces['where'].= $wpdb->prepare(" AND p.post_type IN(%s) GROUP BY t.term_id", $post_types_str);

            return $pieces;
        $terms = get_terms($taxonomies, $args);
        remove_filter( 'terms_clauses','wpse_filter_terms_by_cpt',10);          
        return $terms;

#5 @wonderboymusic
3 years ago

  • Keywords close needs-patch added

There is probably going to be no unslow way to do this in core

#6 @wonderboymusic
2 years ago

#27720 was marked as a duplicate.

#7 @boonebgorges
23 months ago

  • Milestone Awaiting Review deleted
  • Resolution set to wontfix
  • Status changed from new to closed

get_terms() never makes reference to wp_term_relationships, so probably isn't the place for this kind of argument. It belongs somewhere like wp_get_post_terms().

The simplest way to implement this: in wp_get_post_terms(), if $post_id is empty and $args['post_type'] is not, then fetch the IDs of the posts in the post type(s) and pass them to wp_get_object_terms(). This is probably going to perform very slowly on large sites, and if your site is large enough you may even experience the joy of a seg fault. I don't think this is something we'd want to put into core.

It's hard to think of a more robust solution that won't look pretty hackish. wp_get_object_terms() is designed to be agnostic about the object type (post, user, etc). But filtering by post_type is very specific to posts. So, you can imagine passing a 'primary_id_column' and 'primary_table' column to wp_get_object_terms() to build the necessary JOIN clause, but it's hard to imagine the generic logic that would be necessary to build a WHERE clause like p.post_type IN ('post','page'). It'd be out of keeping with the rest of WP to have functions passing chunks of SQL between each other. At the same time, it doesn't make a lot of architectural sense to put logic directly into wp_get_object_terms() that is specific to posts (or, worse, post_type). And even if we did, we'd be looking at a query that'd be pretty slow for large numbers of posts.

On the basis of this, I'm closing as wontfix. Official recommendation for now is to query for post IDs on your own and pass to wp_get_object_terms(). If someone shows up here with a dynamite patch that proves me wrong, I'll eat my foot and dub you the champion of WordPress.

#8 @boonebgorges
14 months ago

#32919 was marked as a duplicate.

#9 follow-up: @atomicjack
14 months ago

Why not make a change so that tags are a property of the posts @boonebgorges?

I understand this would inevitably be a big adjustment.

Or is this completely silly?

#10 in reply to: ↑ 9 @boonebgorges
14 months ago

Replying to atomicjack:

Why not make a change so that tags are a property of the posts @boonebgorges?

Tags *are* a property of posts. That's why it's problematic to add a 'post_type' argument to get_terms(), a function that is designed to get information about terms, not posts. Fetching terms based on object relationships means two additional table joins, which will scale poorly on many installations. So there are both performance problems and conceptual problems (scope creep of get_terms()).

Pending a rearchitecture, if you need the terms belonging to a set of posts, use wp_get_object_terms().

#11 @wordpresssites
10 days ago

  • Resolution wontfix deleted
  • Status changed from closed to reopened

How about adding a 'post_type' and 'category' argument to get_tags() or get _terms() or get_categories so tags can be listed for different post types or categories? The only way to do that now is to use the include or exclude parameters.

Last edited 8 days ago by wordpresssites (previous) (diff)

#12 @SergeyBiryukov
10 days ago

  • Milestone set to Awaiting Review
Note: See TracTickets for help on using tickets.