Enhancing Drupal's Multiple Term Filters

Enhancing Drupal's Multiple Term Filters

The Pathauto module for Drupal makes your links look pretty by automatically creating URL aliases when content is created. For example, when dealing with vocabularies, “example.com/taxonomy/term/1” can be gussied up as “example.com/tag/apple”.

I ran into a problem, though, when I wanted to specify multiple terms in the URL, like “example.com/tag/apple,keynote” to display content with both of those tags. Since that’s an alias that hasn’t been defined yet, Drupal just presented me with a 404.

So I decided to add some functions to a custom module to handle these paths:

/*
 * Implements hook_menu.
 */
function MYMODULE_menu() {
  $items['tags/%'] = array(
    'title'           => 'Multiple tags filter',
    'page callback'   => 'MYMODULE_parse_tags',
    'page arguments'  => array(1),
    'access callback' => TRUE,
    'type'            => MENU_CALLBACK,
  );
  return $items;
}

function MYMODULE_parse_tags($input = '') {
  if (empty($input)) drupal_not_found();

  $output = '';

  if (strpos($input, ' ') == TRUE) {
    $tags = explode(' ', $input);
    $delim = '+';
  }
  elseif (strpos($input, ',') == TRUE) {
    $tags = explode(',', $input);
    $delim = ',';
  }
  else {
    // Single term.
    $tags = $input;
    $delim = '';
  }

  foreach ($tags as $tag) {
    // Change 'Tags' to your vocabulary.
    $tid = _get_term_from_name($tag, 'Tags');
    $output .= $tid == 0 ? '' : $tid . ',';
  }
  if (!empty($output)) {
    drupal_goto('taxonomy/term/' . rtrim($output,','));
  }
  else {
    drupal_not_found();
  }
}

function _get_term_from_name($term_name, $vocabulary_name) {
  if ($vocabulary = taxonomy_vocabulary_machine_name_load($vocabulary_name)) {
    // Can't use EntityFieldQuery because it does a case-sensitive search.    
    $tree = taxonomy_get_tree($vocabulary->vid);
    foreach ($tree as $term) {
      if (strtolower($term->name) == strtolower($term_name)) {
        return $term->tid;
      }
    }
  }
  return 0;
}

mymodule_menu() implements hook_menu as a callback for the “tags/%” path. It calls mymodule_parse_tags(), which–if an argument exists–breaks the argument into pieces, and calls _get_term_from_name() to translate each term name into a term ID. Finally, each of those term IDs is concatenated to produce an OR search (‘+’) or an AND (‘,’) search.

Still not working. An OR search like “example.com/tags/apple+keynote” gave me the correct results, but an AND search like “example.com/tag/apple,keynote” would give me the exact same results.

Turns out that multiple term ID handling was removed in Drupal 7. (Where was the outcry?) Fortunately, an intrepid developer stepped up and released Taxonomy Filter, a module to expose multiple term filtering to your visitors, and Multi-term Views, a module that modifies the behavior of the default “taxonomy_term” view.

Since I was using a clone of the “taxonomy_term” view, Multi-term Views didn’t solve my problem right away, but I submitted a patch that extended the OR behavior to any view based on the default. This restored Drupal’s behavior from previous versions.

My final goal was to change the title of the view from “Content tagged Apple, Keynote” to “Content tagged Apple and Keynote”. Here’s the final piece of the puzzle:

function MYMODULE_views_pre_render(&$view) {
  if ($view->name == 'tags' && $view->current_display == 'page') {
    $arg = $view->argument;
    $old_title = $arg['term_node_tid_depth']->validated_title;
    $new_title = str_replace(', ', ' and ', $old_title);
    $new_title = str_replace('+ ', ' or ', $new_title);
    $view->build_info['title'] = 'Content tagged ' . $new_title;
  }
}

If implementing in your module, make sure $view->name and $view->current_display are updated appropriately, as well as the vocabulary name as noted above.