Get all tags based on specific category (including all tags from child categories and posts) wordpress

StackOverflow https://stackoverflow.com/questions/22787341

  •  25-06-2023
  •  | 
  •  

سؤال

I wanted to use wp_tag_cloud() on single.php using the argument that gets all tags from specific category including all tags from its child categories and posts.

هل كانت مفيدة؟

المحلول 3

I am getting closer on this.

<div class="tag_cloud_on_single">

    <h2>Popular Topics</h2>

    <?php

    $category = get_the_category();
    $root_cat_of_curr =  $category[0]->category_parent;

    function get_cat_slug($cat_id) {
        $cat_id = (int) $cat_id;
        $category = &get_category($cat_id);
        return $category->slug;
    }

    $my_cat = get_cat_slug($root_cat_of_curr);

    $custom_query = new WP_Query('posts_per_page=-1&category_name='.$my_cat.'');
    if ($custom_query->have_posts()) :
        while ($custom_query->have_posts()) : $custom_query->the_post();
            $posttags = get_the_tags();
            if ($posttags) {
                foreach($posttags as $tag) {
                    $all_tags[] = $tag->term_id;
                }
            }
        endwhile;
    endif;

    $tags_arr = array_unique($all_tags);
    $tags_str = implode(",", $tags_arr);

    $args = array(
        'smallest'                  => 12, 
        'largest'                   => 24,
        'unit'                      => 'pt', 
        'number'                    => 0,  
        'format'                    => 'flat',
        'separator'                 => "&nbsp;&nbsp;&nbsp;",
        'orderby'                   => 'name', 
        'order'                     => 'RAND',
        'exclude'                   => null,  
        'topic_count_text_callback' => default_topic_count_text,
        'link'                      => 'view', 
        'echo'                      => true,
        'include'                   => $tags_str
    );

    wp_tag_cloud($args);

    ?>

</div>

Thanks all for your contribution. Appreciate your help.

نصائح أخرى

There is no native way in Wordpress to do this. The reason being that tags are not tied to categories, they are separate. That being said, the only way to get all tags IN USE by a specific category is to cycle through every post of the category and grab the tags of each post.

I've written up a quick function to do just that.

Place this function into your functions.php file.

function get_tags_in_use($category_ID, $type = 'name'){
    // Set up the query for our posts
    $my_posts = new WP_Query(array(
      'cat' => $category_ID, // Your category id
      'posts_per_page' => -1 // All posts from that category
    ));

    // Initialize our tag arrays
    $tags_by_id = array();
    $tags_by_name = array();
    $tags_by_slug = array();

    // If there are posts in this category, loop through them
    if ($my_posts->have_posts()): while ($my_posts->have_posts()): $my_posts->the_post();

      // Get all tags of current post
      $post_tags = wp_get_post_tags($my_posts->post->ID);

      // Loop through each tag
      foreach ($post_tags as $tag):

        // Set up our tags by id, name, and/or slug
        $tag_id = $tag->term_id;
        $tag_name = $tag->name;
        $tag_slug = $tag->slug;

        // Push each tag into our main array if not already in it
        if (!in_array($tag_id, $tags_by_id))
          array_push($tags_by_id, $tag_id);

        if (!in_array($tag_name, $tags_by_name))
          array_push($tags_by_name, $tag_name);

        if (!in_array($tag_slug, $tags_by_slug))
          array_push($tags_by_slug, $tag_slug);

      endforeach;
    endwhile; endif;

    // Return value specified
    if ($type == 'id')
        return $tags_by_id;

    if ($type == 'name')
        return $tags_by_name;

    if ($type == 'slug')
        return $tags_by_slug;
}

Then when you want to grab the tags of a specific category, call this function like so:

// First paramater is the category and the second paramater is how to return the tag (by name, by id, or by slug)
// Leave second paramater blank to default to name

$tags = get_tags_in_use(59, 'name');

Hope this helps.

EDIT:

This is the function you will need to use in conjunction with the other:

function tag_cloud_by_category($category_ID){
    // Get our tag array
    $tags = get_tags_in_use($category_ID, 'id');

    // Start our output variable
    echo '<div class="tag-cloud">';

    // Cycle through each tag and set it up
    foreach ($tags as $tag):
        // Get our count
        $term = get_term_by('id', $tag, 'post_tag');
        $count = $term->count;

        // Get tag name
        $tag_info = get_tag($tag);
        $tag_name = $tag_info->name;

        // Get tag link
        $tag_link = get_tag_link($tag);

        // Set up our font size based on count
        $size = 8 + $count;

        echo '<span style="font-size:'.$size.'px;">';
        echo '<a href="'.$tag_link.'">'.$tag_name.'</a>';
        echo ' </span>';

    endforeach;

    echo '</div>';
}

So you can use this function simply like so:

tag_cloud_by_category($cat_id);

In your theme's functions.php insert the following function:

function get_category_tags($args) {
    global $wpdb;
    $tags = $wpdb->get_results
    ("
        SELECT DISTINCT terms2.term_id as tag_id, terms2.name as tag_name, null as tag_link
        FROM
            wp_posts as p1
            LEFT JOIN wp_term_relationships as r1 ON p1.ID = r1.object_ID
            LEFT JOIN wp_term_taxonomy as t1 ON r1.term_taxonomy_id = t1.term_taxonomy_id
            LEFT JOIN wp_terms as terms1 ON t1.term_id = terms1.term_id,

            wp_posts as p2
            LEFT JOIN wp_term_relationships as r2 ON p2.ID = r2.object_ID
            LEFT JOIN wp_term_taxonomy as t2 ON r2.term_taxonomy_id = t2.term_taxonomy_id
            LEFT JOIN wp_terms as terms2 ON t2.term_id = terms2.term_id
        WHERE
            t1.taxonomy = 'category' AND p1.post_status = 'publish' AND terms1.term_id IN (".$args['categories'].") AND
            t2.taxonomy = 'post_tag' AND p2.post_status = 'publish'
            AND p1.ID = p2.ID
        ORDER by tag_name
    ");
    $count = 0;
    foreach ($tags as $tag) {
        $tags[$count]->tag_link = get_tag_link($tag->tag_id);
        $count++;
    }
    return $tags;
}

In your theme document call the function as follows. Notice it accepts multiple category id's:

 $args = array(
        'categories'                => '12,13,14'
    );

$tags = get_category_tags($args);

This will return an array that you could do the following with:

$content .= "<ul>";
foreach ($tags as $tag) {
    $content .= "<li><a href=\"$tag->tag_link\">$tag->tag_name</a></li>";
}
$content .= "</ul>";
echo $content;

Pure SQL approach

Recently, I did the similar query for WooCommerce category (including it children's) for Tags using pure SQL query.

The structure of the query is identical in both cases. The only thing that change is the value of post_type and value of taxonomy that we query for.

Therefore, you can use the below example to also query tags for posts.

Recursion - Get categories IDs (parent and children's)

To select all categories and its subcategories, we could use recursive CTA (MySQL 8.0, MariaDB 10.2 and UP). Like in the example below:

SET @parentCategoryId := 17;

WITH recursive cteCategory(term_id) AS
(   
    SELECT t.term_id FROM wp_terms t
    LEFT JOIN wp_term_taxonomy tt 
    ON tt.term_taxonomy_id = t.term_id
    WHERE tt.term_taxonomy_id = @parentCategoryId 
    AND tt.taxonomy = 'product_cat'
    UNION ALL
    SELECT t2.term_id FROM wp_terms t2
    LEFT JOIN wp_term_taxonomy tt2 
    ON tt2.term_taxonomy_id = t2.term_id
    INNER JOIN cteCategory
    ON tt2.parent = cteCategory.term_id
)
SELECT * FROM cteCategory;

The above query will recursively push child categories IDs to SELECT clause;

+---------+
| term_id |
+---------+
|      17 |
|      56 |
|      57 |
+---------+
3 rows in set (0.001 sec)

You can reed more about recurion here: https://mariadb.com/kb/en/recursive-common-table-expressions-overview/ and here How to create a MySQL hierarchical recursive query?

Building a full query

Tags and Categories are not related to each other. They both tied to posts, so that we need to query for all the posts/products assigned to the above categories.

Finally, we need to query all the tags that are related to the above posts. We do that with use of IN directive.

Now, we need to combine all of the above in to a single query. The solution looks like this:

-- define (parent/starting) category
SET @parentCategoryId := 17;

-- define recursive function here
WITH recursive cteCategory(term_id) AS
(   
    SELECT t.term_id FROM wp_terms t
    LEFT JOIN wp_term_taxonomy tt 
    ON tt.term_taxonomy_id = t.term_id
    WHERE tt.term_taxonomy_id = @parentCategoryId 
    AND tt.taxonomy = 'product_cat' -- <<< category
    UNION ALL
    SELECT t2.term_id FROM wp_terms t2
    LEFT JOIN wp_term_taxonomy tt2 
    ON tt2.term_taxonomy_id = t2.term_id
    INNER JOIN cteCategory
    ON tt2.parent = cteCategory.term_id
)

-- start query for tags used in collection of post/products 
SELECT DISTINCT t.* FROM wp_posts AS p
LEFT JOIN wp_term_relationships tr ON p.ID = tr.object_id
LEFT JOIN wp_term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
LEFT JOIN wp_terms t ON t.term_id = tt.term_id
WHERE p.post_type="product" -- <<< post
AND p.post_status = 'publish'
AND tt.taxonomy = "product_tag" -- <<< post_tag
AND p.ID IN
    -- build list of posts/products listed under initlial category and their childrens 
    (SELECT p.ID FROM wp_posts AS p
    LEFT JOIN wp_term_relationships tr ON p.ID = tr.object_id
    LEFT JOIN wp_term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
    LEFT JOIN wp_terms t ON t.term_id = tt.term_id
    WHERE p.post_type="product" -- <<< post
    AND p.post_status = 'publish'
    AND tt.taxonomy = "product_cat" -- <<< category
    AND tt.term_taxonomy_id IN (SELECT * FROM cteCategory) -- call recursive function cteCategory
    -- AND FIND_IN_SET(t.name, @CategoryByName)  -- <<< search category by name
    ORDER BY p.ID)
ORDER BY p.ID;

Output a list of tags for given category and its childrens:

+---------+-------------------------+---------------------+------------+
| term_id | name                    | slug                | term_group |
+---------+-------------------------+---------------------+------------+
|      40 | tag ziemniaki           | ziemniaki           |          0 |
|      46 | tag pomidory koktajlowe | pomidory-koktajlowe |          0 |
|      42 | tag pomidory            | pomidory            |          0 |
|      44 | tag ogórki              | ogorki              |          0 |
|      39 | tag cebula              | cebula              |          0 |
|      41 | tag kapusta             | kapusta             |          0 |
+---------+-------------------------+---------------------+------------+
6 rows in set (0.002 sec)

The above query will always return a list of non-empty tags sice we build that list, looping on the list of products in a given category group.

Even that this might look like a very complex query (which it is), it was able to complete the task in only 0.002 sec. It was blazing fast, but we still should to cache its result and run it only if we add, modifie or remove any tags on our site. Then we can provide result instantly without any delay.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top