0

I have a Custom Post Type that uses the default category, the same as the posts, and some other post types in my site. How can I list all non-empty categories from the custom post type?

For instance, if I have categories “Cat 1”, “Cat 2" and “Cat 3” and I have a default post with a category of “Cat 1", and a custom post with categories “Cat 2” and “Cat 3". How would I write a function that would return “Cat 2” and “Cat 3"?

It seems like the get_terms() functions don’t support a post_type argument. Is there a way I can add some other query to a get_terms() function?

I know that it is possible to hide unused terms:

$terms = get_terms( array(
    'taxonomy' => 'category',
    'hide_empty' => true, <-----
) );

However this hides empty for all post types, I'd like to hide empty for just a custom post type.

1
  • 1
    hide_empty is based on the count column of wp_terms. This count is based on the total number of posts of any post type for that term. The number of posts of each post type is not stored separately for each post type, so you can't do what you're asking for with get_terms(). You would need to write your own query, but it would be very inefficient since you'd essentially need to check every post. You're probably better of registering a second separate taxonomy for your post type. Commented Apr 23, 2019 at 9:34

2 Answers 2

1

You can modify the term query to count for a specfic post type.

Set a unique argument parameter to your term query.

$terms = get_terms( array(
    'taxonomy' => 'category',
    'hide_empty' => true,
    'my_param' => 'book', // Name of the custom post type you want to filter.
) );

And then in function.php add this filter.

add_filter('terms_clauses', function($clauses, $taxonomy, $args) {
    global $wpdb;

    $post_type = $args['my_param'] ?? '';

    // Proceed only if your custom parameter is set.
    if (empty($post_type)) {
        return $clauses;
    }

    // Modify the query to only include terms that have posts in the specified post type.
    $clauses['join'] .= " LEFT JOIN {$wpdb->term_relationships} AS tr ON tt.term_taxonomy_id = tr.term_taxonomy_id LEFT JOIN {$wpdb->posts} AS p ON tr.object_id = p.ID AND p.post_type = '{$post_type}'";
    $clauses['where'] .= " GROUP BY t.term_id HAVING COUNT(DISTINCT p.ID) > 0";

    return $clauses;
}, 10, 3);
1
  • 1
    I'm not able to test this right now, but that looks really clever. Thanks for answering.
    – Djave
    Commented Aug 10, 2023 at 10:39
0

This will end up being a little convoluted since the taxonomy just describes a relationship that may belong to any of the post types, so there's not a direct data link for what you're trying to fetch.

But you could do something like this (untested), which could be a fairly heavy query if you're doing on every page load or something, so maybe consider running it on a cron job and storing it somewhere or something.

<?php

    $args = array(
        'post_type' => 'my-post-type',
        'posts_per_page' => -1,
    );

    $fetch = get_posts( $args );
    $cat_r = array();

    // Iterate the posts and get the categories
    foreach( $fetch as $item ) {
        $cats = get_the_terms( $item->ID, 'category' );
        $cat_r[] = $cats;
    }

    // Outputs an array of term objects
    echo '<pre>';
    print_r( $cat_r );
    echo '</pre>';
1
  • Thanks for the response – I think I might try to create a custom query instead as the overhead for this would be really big. I really appreciate you taking the time to answer though. Thanks.
    – Djave
    Commented Apr 23, 2019 at 9:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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