From e44ca0593629f13f794c023874a32f2750b1278a Mon Sep 17 00:00:00 2001
From: Chris Skene <chris@xtfer.com>
Date: Mon, 9 Jan 2012 10:49:45 +1100
Subject: [PATCH] Issue #424070 Allow multiple vocabularies to be used in
 autocompletes

---
 content_taxonomy.module              |   15 +++-
 content_taxonomy_autocomplete.module |  118 +++++++++++++++++++++++-----------
 2 files changed, 90 insertions(+), 43 deletions(-)

diff --git a/content_taxonomy.module b/content_taxonomy.module
index 6832b72..30f7f42 100644
--- a/content_taxonomy.module
+++ b/content_taxonomy.module
@@ -77,13 +77,20 @@ function content_taxonomy_field_settings($op, $field) {
       }
       
       $form['vid'] = array(
-        '#title' => t('Vocabulary'),
+        '#title' => t('Primary vocabulary'),
         '#type' => 'select',
         '#default_value' => is_numeric($field['vid']) ? $field['vid'] : 0,
         '#options' => $options_voc,
-        '#description' => t('Terms of the selected vocabulary get exposed to the field'),
+        '#description' => t('Primary vocabulary. Terms of the selected vocabulary get exposed to the field, and any new terms are added to this vocabulary.'),
+      );
+      $form['vids'] = array(
+        '#title' => t('Other vocabularies'),
+        '#type' => 'select',
+        '#default_value' => count($field['vids']) > 0 ? $field['vids'] : array(),
+        '#options' => $options_voc,
+        '#description' => t('Terms of the selected vocabularies get exposed to the field. The Primary vocabulary is also selected, regardless of the setting in this field.'),
+        '#multiple' => TRUE,
       );
-      
       $form['hierarchical_vocabulary'] = array(
         '#type' => 'fieldset',
         '#title' => t('Advanced settings for hierarchical vocabularies'),
@@ -122,7 +129,7 @@ function content_taxonomy_field_settings($op, $field) {
       return $form;   
     
     case 'save':
-      return array('save_term_node', 'vid', 'parent', 'parent_php_code', 'depth');
+      return array('save_term_node', 'vid', 'vids', 'parent', 'parent_php_code', 'depth');
     
     case 'database columns':
       return array(
diff --git a/content_taxonomy_autocomplete.module b/content_taxonomy_autocomplete.module
index 5639bbe..a3fb186 100644
--- a/content_taxonomy_autocomplete.module
+++ b/content_taxonomy_autocomplete.module
@@ -138,6 +138,7 @@ function content_taxonomy_autocomplete_widget(&$form, &$form_state, $field, $ite
     '#default_value' => isset($items) ? $items : NULL,
     '#value_callback' => 'content_taxonomy_autocomplete_value',
     '#vid' => $field['vid'],
+    '#vids' => $field['vids'],
   );
   return $element;
 }
@@ -155,7 +156,10 @@ function content_taxonomy_autocomplete_value($element, $edit = FALSE) {
       $terms[] = taxonomy_get_term($entry[$field_key]);
     }
   }
-  $value = content_taxonomy_autocomplete_merge_tags($terms, $element['#vid']);
+  if (!$element['#vids']) {
+    $element['#vids'] = array($element['#vid'] => $element['#vid']);
+  }
+  $value = content_taxonomy_autocomplete_merge_tags($terms, $element['#vids']);
   $value = !empty($value) ? $value : NULL;
   return array($field_key => $value);
 }
@@ -218,7 +222,10 @@ function content_taxonomy_autocomplete_validate($element, &$form_state) {
   
   $value = $element['#value'];
  
-  $extracted_ids = content_taxonomy_autocomplete_tags_get_tids($value, $field['vid'], content_taxonomy_field_get_parent($field), $field['widget']['extra_parent']);
+  if (!$field['vids']) {
+    $field['vids'] = array($field['vid'] => $field['vid']);
+  }
+  $extracted_ids = content_taxonomy_autocomplete_tags_get_tids($value, $field['vid'], $field['vids'], content_taxonomy_field_get_parent($field), $field['widget']['extra_parent']);
 
   if (!$field['multiple'] && count(content_taxonomy_autocomplete_split_tags($value, $field['vid'])) > 1) {
     form_set_error($field['field_name'] .'][value', t('You can provide only one value'));
@@ -267,11 +274,20 @@ function content_taxonomy_autocomplete_form2data($extracted_ids, $field, $elemen
  * @param STRING typed input
  */
 function content_taxonomy_autocomplete_load($field_name, $string = '') {
-   // The user enters a comma-separated list of tags. We only autocomplete the last tag.
-  // This regexp allows the following types of user input:
-  // this, "somecmpany, llc", "and ""this"" w,o.rks", foo bar
+  $single_vocab = FALSE;
+
+  // Combine all vocabulary selections into one array
   $content_type_info = _content_type_info();
   $vid = $content_type_info['fields'][$field_name]['vid'];
+  $others = $content_type_info['fields'][$field_name]['vids'];
+  $vids = array($vid => $vid);
+  if (empty($others)) {
+    $single_vocab = TRUE;
+  }
+  else {
+    $vids = $vids + $others;
+  }
+
   $tid = content_taxonomy_field_get_parent($content_type_info['fields'][$field_name]);
   
   // If the menu system has splitted the search text because of slashes, glue it back.
@@ -288,19 +304,21 @@ function content_taxonomy_autocomplete_load($field_name, $string = '') {
   $matches = array();
   if ($last_string != '') {
     if ($tid) {
-      $result = db_query_range(db_rewrite_sql("SELECT t.name FROM {term_data} t 
+      $result = db_query_range(db_rewrite_sql("SELECT t.name, t.tid, t.vid, v.name as vocab FROM {term_data} t 
         LEFT JOIN {term_synonym} s ON t.tid = s.tid
+        LEFT JOIN {vocabulary} v ON t.vid = v.vid
         INNER JOIN {term_hierarchy} h ON  t.tid = h.tid
-        WHERE h.parent = %d 
+        WHERE h.parent = %d
         AND (LOWER(t.name) LIKE LOWER('%%%s%%') OR LOWER(s.name) LIKE LOWER('%%%s%%'))", 't', 'tid'),
         $tid,$last_string,$last_string,0,10);
     }
     else {
-      $result = db_query_range(db_rewrite_sql("SELECT t.name FROM {term_data} t 
+      $result = db_query_range(db_rewrite_sql("SELECT t.name, t.tid, t.vid, v.name as vocab FROM {term_data} t 
         LEFT JOIN {term_synonym} s ON t.tid = s.tid
-        WHERE t.vid = %d 
+        JOIN {vocabulary} v ON t.vid = v.vid
+        WHERE t.vid IN (%s)
         AND (LOWER(t.name) LIKE LOWER('%%%s%%') OR LOWER(s.name) LIKE LOWER('%%%s%%'))", 't', 'tid'),
-        $vid, $last_string, $last_string, 0, 10);
+        implode(',', $vids), $last_string, $last_string, 0, 10);
     }
     $prefix = count($array) ? '"'. implode('", "', $array) .'", ' : '';
 
@@ -310,7 +328,16 @@ function content_taxonomy_autocomplete_load($field_name, $string = '') {
       if (strpos($tag->name, ',') !== FALSE || strpos($tag->name, '"') !== FALSE) {
         $n = '"'. str_replace('"', '""', $tag->name) .'"';
       }
-      $matches[$prefix . $n] = check_plain($tag->name);
+      $name = htmlspecialchars_decode($tag->name);
+      $suffix = ' [tid:'. $tag->tid .']';
+
+      // Selects on multiple vocabularies return terms prefixed with their Vocabulary
+      if ($single_vocab == TRUE) {
+        $matches[$prefix . $n . $suffix] = check_plain($name);
+      }
+      else {
+        $matches[$prefix . $n . $suffix] = '<em>'. check_plain($tag->vocab) .':</em> '. check_plain($name);
+      }
     }
   }
 
@@ -319,11 +346,15 @@ function content_taxonomy_autocomplete_load($field_name, $string = '') {
 
 /**
  * Get TIDs for freetagging tags
- *  Free tagging vocabularies do not send their tids in the form,
- *  so we'll detect them here and process them independently.
+ *
+ * Free tagging vocabularies may not send their tids in the form, so we'll 
+ * detect them here and process them independently.
+ *
  * @param $typed_input A string containing all comma separated tags. As the user typed it.
+ * @param $vid primary vocabulary id (where to write to)
+ * @param $vids secondary vocabulary ids (where to search for tags)
  */
-function content_taxonomy_autocomplete_tags_get_tids($typed_input, $vid, $parent = 0, $extra_parent = 0) {
+function content_taxonomy_autocomplete_tags_get_tids($typed_input, $vid, $vids, $parent = 0, $extra_parent = 0) {
   $extra_parent_vid = 0;
   if ($extra_parent) {
     $extra_parent_term = taxonomy_get_term($extra_parent);
@@ -338,37 +369,46 @@ function content_taxonomy_autocomplete_tags_get_tids($typed_input, $vid, $parent
     // If a user has escaped a term (to demonstrate that it is a group,
     // or includes a comma or quote character), we remove the escape
     // formatting so to save the term into the DB as the user intends.
-    $typed_term = trim(str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $typed_term)));
+    $typed_term = trim(str_replace('""', '"', preg_replace("/^\"(.*)\"$/u", "$1", $typed_term)));
     if ($typed_term == "") { continue; }
-    
-    // See if the term exists in the chosen vocabulary
-    // and return the tid, otherwise, add a new record.
-    $possibilities = taxonomy_get_term_by_name($typed_term);
-
-    $typed_term_tid = NULL; // tid match if any.
-    foreach ($possibilities as $possibility) {
-      if ($possibility->vid == $vid || ($extra_parent_vid != 0 && $possibility->vid == $extra_parent_vid)) {
-        if ($parent) {
-          $parents = array();
-          $parents = taxonomy_get_parents($possibility->tid);
-          if (in_array($parent, array_keys($parents)) || in_array($extra_parent, array_keys($parents))) {
+
+    // Check if the term has a corresponding ID in brackets, and pull this out
+    // before trying to match on all terms.
+    preg_match('/^(?:\s*|(.*) )?\[\s*tid\s*:\s*(\d+)\s*\]$/', $typed_term, $matches);
+    if (isset($matches[2])) {
+      // A term in [brackets] will be returned as $matches[2]
+      // The term itself is in $matches[1]
+      $result['existing_tids'][$matches[2]] = $matches[2];
+    }
+    else {
+      // See if the term exists in the chosen vocabularies
+      // and return the tids, otherwise, add a new record.
+      $possibilities = taxonomy_get_term_by_name($typed_term);
+      $typed_term_tid = NULL; // tid match if any.
+      foreach ($possibilities as $possibility) {
+        if (in_array($possibility->vid, $vids) || ($extra_parent_vid != 0 && $possibility->vid == $extra_parent_vid)) {
+          if ($parent) {
+            $parents = array();
+            $parents = taxonomy_get_parents($possibility->tid);
+            if (in_array($parent, array_keys($parents)) || in_array($extra_parent, array_keys($parents))) {
+              $result['existing_tids'][$possibility->tid] = $possibility->tid;
+              $typed_term_tid = $possibility->tid;  
+            }
+          }
+          else {
             $result['existing_tids'][$possibility->tid] = $possibility->tid;
-            $typed_term_tid = $possibility->tid;  
+            $typed_term_tid = $possibility->tid;
           }
         }
-        else {
-          $result['existing_tids'][$possibility->tid] = $possibility->tid;
-          $typed_term_tid = $possibility->tid;
-        }
+      }
+      if (!$typed_term_tid) {
+        $result['non_existing_terms'][] = array(
+          'name' => $typed_term,
+          'vid' => $vid,
+        );
       }
     }
 
-    if (!$typed_term_tid) {
-      $result['non_existing_terms'][] = array(
-        'name' => $typed_term,
-        'vid' => $vid,
-      );
-    }
   }
 
   return $result;
@@ -419,7 +459,7 @@ function content_taxonomy_autocomplete_merge_tags($terms, $vid) {
       if (preg_match('/,/', $term->name) || preg_match('/"/', $term->name)) {
         $name = '"'. preg_replace('/"/', '""', $name) .'"';
       }
-      $typed_terms[] = $name;
+      $typed_terms[] = $name .' [tid:'. $term->tid .']';
     }
   }
   return implode(', ', $typed_terms);
-- 
1.7.5.4

