Make WordPress Core

Opened 13 years ago

Closed 8 years ago

Last modified 8 years ago

#18106 closed feature request (wontfix)

get_terms of posts in a specific post_type

Reported by: braydonf's profile braydonf Owned by:
Milestone: Priority: normal
Severity: normal Version:
Component: Taxonomy Keywords: close needs-patch
Focuses: Cc:

Description

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 (14)

#1 @braydonf
13 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
13 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
13 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
12 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
11 years ago

  • Keywords close needs-patch added

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

#6 @wonderboymusic
10 years ago

#27720 was marked as a duplicate.

#7 @boonebgorges
10 years 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
9 years ago

#32919 was marked as a duplicate.

#9 follow-up: @atomicjack
9 years 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
9 years 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 follow-up: @wordpresssites
8 years ago

  • Resolution wontfix deleted
  • Status changed from closed to reopened

How about adding a 'post_type' argument to get_tags() so tags can be listed for different post types?

Version 0, edited 8 years ago by wordpresssites (next)

#12 @SergeyBiryukov
8 years ago

  • Milestone set to Awaiting Review

#13 in reply to: ↑ 11 @boonebgorges
8 years ago

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

Replying to wordpresssites:

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.

The technical explanation for 'post_type' can be found in my comment above. I'm not sure what 'category' would mean in this context: show all tags that belong to posts that are also in a given category? That kind of query would be even *more* resource-intensive than the 'post_type' query - I think that about 7 database tables would be involved. That kind of use case is specific enough that I think that a custom implementation using 'include' is probably the most appropriate.

I'm going to close this ticket again, as I think that the reasoning from comment 7 still stands. If anyone is able to work up a way of handling this so that it has a reasonable chance of scaling, please feel free to reopen with those details.

#14 @SergeyBiryukov
8 years ago

#39940 was marked as a duplicate.

Note: See TracTickets for help on using tickets.