Posts with at least 3 tags of a list of tags

For example, there are the tags {foo, bar, chocolate, mango, hammock, leaf}

I would like to find all posts with at least 3 of these tags.

Read More

A post with tags {foo, mango, vannilla, nuts, leaf} will match it because it has {foo, mango, leaf} – so at least 3 tags from the needed set of tags.

Therefore it would be in the list of the matched posts.

Is there a simple way to do that, without doing multiple loops through all the posts ?

Related posts

3 comments

  1. The answer below is simplified, and could be extended to check if any posts have 3 matching tags before outputting the list. Using one query and assuming you have at least one post with 3 matching tags:

    //List of tag slugs
    $tags = array('foo', 'bar', 'chocolate', 'mango', 'hammock', 'leaf');
    
    $args = array(
        'tag_slug__in' => $tags
        //Add other arguments here
    );
    
    // This query contains posts with at least one matching tag
    $tagged_posts = new WP_Query($args);
    
    echo '<ul>';
    while ( $tagged_posts->have_posts() ) : $tagged_posts->the_post();
       // Check each single post for up to 3 matching tags and output <li>
       $tag_count = 0;
       $tag_min_match = 3;
       foreach ( $tags as $tag ) {
          if ( has_tag( $tag ) && $tag_count < $tag_min_match ) {
             $tag_count ++;
          }
       }
       if ($tag_count == $tag_min_match) {
          //Echo list style here
          echo '<li><a href="'. get_permalink() .'" title="'. get_the_title() .'">'. get_the_title() .'</a></li>';
       }
    endwhile;
    wp_reset_query();
    echo '</ul>';
    

    EDIT: Adjusting the variable $tag_min_match will set the number of matches.

  2. Here’s one way to do it:

    Given a set of 5 tags, {a, b, c, d, e}:

    1) In PHP, generate all the possible subsets containing 3 elements, without repetition:

    {a, b, c}
    {a, b, d}
    {a, b, e}
    {a, c, d}
    {a, c, e}
    {b, c, d}
    {b, c, e}
    {c, d, e}
    

    2) Convert those subsets into a massive taxonomy query:

    $q = new WP_Query( array(
      'tax_query' => array(
        'relation' => 'OR',
        array(
          'terms' => array( 'a', 'b', 'c' ),
          'field' => 'slug',
          'operator' => 'AND'
        ),
        array(
          'terms' => array( 'a', 'b', 'd' ),
          'field' => 'slug',
          'operator' => 'AND'
        ),
        ...
      )
    ) );
    
  3. The approach of sprclldr is the one I used. As for the while loop, here is what I used instead :

    $relatedPosts = $tagged_posts->posts;
    $indexForSort = array();
    
    for ($i = count($relatedPosts) - 1; $i >= 0; $i--) {
      $relatedPostTags = get_tags($relatedPosts[$i]->ID);
      //get the ids of each related post
      $relatedPostTags = $this->my_array_column($relatedPostTags, 'term_id');
      $relatedPostTagsInPostTag = array_intersect($tags, $relatedPostTags);
      $indexForSort[$i] = count($relatedPostTagsInPostTag);
    }
    
    //sort by popularity, using indexForSort
    array_multisort($indexForSort, $relatedPosts, SORT_DESC);
    

    I then take the top posts :

    $a_relatedPosts = array_slice($relatedPosts, 0, $this->numberRelatedPosts);
    

    my_array_column is a similar function than the PHP 5,5’s array_column :

      protected function my_array_column($array, $column) {
        if (is_array($array) && !empty($array)) {
          foreach ($array as &$value) {
            //it also get the object's attribute, not only array's values
            $value = is_object($value) ? $value->$column : $value[$column];
          }
          return $array;
        }
        else
          return array();
      }
    

    It doesn’t answer the initial question (but it resolves my root problem), as
    : if there is no related posts with 3 common tags, then this will all the same give some posts.

Comments are closed.