diff --git modules/taxonomy/taxonomy.admin.inc modules/taxonomy/taxonomy.admin.inc index 62b1c66..bdf28a4 100644 --- modules/taxonomy/taxonomy.admin.inc +++ modules/taxonomy/taxonomy.admin.inc @@ -675,28 +675,45 @@ function taxonomy_form_term($form, &$form_state, $edit = array(), $vocabulary = // full vocabulary. Contrib modules can then intercept before // hook_form_alter to provide scalable alternatives. if (!variable_get('taxonomy_override_selector', FALSE)) { - $parent = array_keys(taxonomy_get_parents($edit['tid'])); - $children = taxonomy_get_tree($vocabulary->vid, $edit['tid']); + $thresholds = variable_get('taxonomy_vocabulary_term_counts', array()); - // A term can't be the child of itself, nor of its children. - foreach ($children as $child) { - $exclude[] = $child->tid; - } - $exclude[] = $edit['tid']; + if (empty($thresholds[$vocabulary->machine_name])) { + $parent = array_keys(taxonomy_get_parents($edit['tid'])); + $children = taxonomy_get_tree($vocabulary->vid, $edit['tid']); + + // A term can't be the child of itself, nor of its children. + foreach ($children as $child) { + $exclude[] = $child->tid; + } + $exclude[] = $edit['tid']; - $tree = taxonomy_get_tree($vocabulary->vid); - $options = array('<' . t('root') . '>'); - foreach ($tree as $term) { - if (!in_array($term->tid, $exclude)) { - $options[$term->tid] = str_repeat('-', $term->depth) . $term->name; + $tree = taxonomy_get_tree($vocabulary->vid); + $options = array('<' . t('root') . '>'); + foreach ($tree as $term) { + if (!in_array($term->tid, $exclude)) { + $options[$term->tid] = str_repeat('-', $term->depth) . $term->name; + } } + $form['relations']['parent'] = array( + '#type' => 'select', + '#options' => $options, + '#default_value' => $parent, + '#multiple' => TRUE, + ); + } + else { + $parent = taxonomy_get_parents($edit['tid']); + $typed_string = taxonomy_implode_tags($parent, $vocabulary->vid); + + $form['relations']['parent'] = array( + '#type' => 'textfield', + '#autocomplete_path' => 'taxonomy/autocomplete/parent/' . $vocabulary->vid, + '#default_value' => $typed_string, + '#element_validate' => array('taxonomy_autocomplete_parent_validate'), + ); } - $form['relations']['parent'] = array( - '#type' => 'select', + $form['relations']['parent'] += array( '#title' => t('Parent terms'), - '#options' => $options, - '#default_value' => $parent, - '#multiple' => TRUE, ); } diff --git modules/taxonomy/taxonomy.module modules/taxonomy/taxonomy.module index 42a8f62..5ed91df 100644 --- modules/taxonomy/taxonomy.module +++ modules/taxonomy/taxonomy.module @@ -301,6 +301,13 @@ function taxonomy_menu() { 'type' => MENU_CALLBACK, 'file' => 'taxonomy.pages.inc', ); + $items['taxonomy/autocomplete/parent'] = array( + 'title' => 'Autocomplete taxonomy', + 'page callback' => 'taxonomy_autocomplete_legacy', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + 'file' => 'taxonomy.pages.inc', + ); $items['admin/structure/taxonomy/%taxonomy_vocabulary'] = array( 'title callback' => 'taxonomy_admin_vocabulary_title_callback', @@ -1364,6 +1371,45 @@ function taxonomy_autocomplete_validate($element, &$form_state) { } /** + * Form element validate handler for taxonomy term autocomplete element. + */ +function taxonomy_autocomplete_parent_validate($element, &$form_state) { + // Autocomplete widgets do not send their tids in the form, so we must detect + // them here and process them independently. + if ($tags = $element['#value']) { + // Collect candidate vocabularies. + $vid = $form_state['values']['vid']; + + // Translate term names into actual terms. + $typed_terms = drupal_explode_tags($tags); + $values = array(); + foreach ($typed_terms as $typed_term) { + // See if the term exists in the chosen vocabulary and return the tid; + // otherwise, create a new term. + if ($possibilities = taxonomy_term_load_multiple(array(), array('name' => trim($typed_term), 'vid' => $vid))) { + $term = array_pop($possibilities); + } + else { + $vocabulary = taxonomy_vocabulary_load($vid); + $term = (object) array( + 'vid' => $vid, + 'name' => $typed_term, + 'vocabulary_machine_name' => $vocabulary->machine_name, + ); + taxonomy_term_save($term); + } + $values[] = $term->tid; + } + $value = options_array_transpose(array('tid' => $values)); + } + else { + $value = array(); + } + + form_set_value($element, $value, $form_state); +} + +/** * Implements hook_field_widget_error(). */ function taxonomy_field_widget_error($element, $error, $form, &$form_state) { @@ -1540,3 +1586,27 @@ function taxonomy_taxonomy_term_delete($term) { /** * @} End of "defgroup taxonomy indexing" */ + +/** + * Implements hook_taxonomy_term_insert(). + * + * For large vocabularies or taxonomy term reference fields with many allowed + * values, Drupal core options widgets become unusable. + */ +function taxonomy_taxonomy_term_insert($term) { + $counts = variable_get('taxonomy_vocabulary_term_counts', array()); + + // Counts array stores vocabulary machine name when the vocabulary has + // exceeded the autocomplete threshold. + if (empty($counts[$term->vocabulary_machine_name])) { + $count = db_select('taxonomy_term_data', 't') + ->condition('t.vid', $term->vid) + ->countQuery() + ->execute() + ->fetchField(); + if ($count > variable_get('taxonomy_autocomplete_threshold', 150)) { + $counts[$term->vocabulary_machine_name] = $term->vocabulary_machine_name; + variable_set('taxonomy_vocabulary_term_counts', $counts); + } + } +} diff --git modules/taxonomy/taxonomy.pages.inc modules/taxonomy/taxonomy.pages.inc index dd6e8a0..0305e1c 100644 --- modules/taxonomy/taxonomy.pages.inc +++ modules/taxonomy/taxonomy.pages.inc @@ -123,3 +123,49 @@ function taxonomy_autocomplete($field_name, $tags_typed = '') { drupal_json_output($term_matches); } + +/** + * Helper function for autocompletion + */ +function taxonomy_autocomplete_legacy($vid = 0, $tags_typed = '') { + // The user enters a comma-separated list of tags. We only autocomplete the last tag. + $tags_typed = drupal_explode_tags($tags_typed); + $tag_last = drupal_strtolower(array_pop($tags_typed)); + + $matches = array(); + if ($tag_last != '') { + + $query = db_select('taxonomy_term_data', 't'); + $query->addTag('translatable'); + $query->addTag('term_access'); + + // Do not select already entered terms. + if (!empty($tags_typed)) { + $query->condition('t.name', $tags_typed, 'NOT IN'); + } + // Select rows that match by term name. + $tags_return = $query + ->fields('t', array('tid', 'name')) + ->condition('t.vid', $vid) + ->condition('t.name', '%' . db_like($tag_last) . '%', 'LIKE') + ->range(0, 10) + ->execute() + ->fetchAllKeyed(); + + $prefix = count($tags_typed) ? implode(', ', $tags_typed) . ', ' : ''; + + $term_matches = array(); + foreach ($tags_return as $tid => $name) { + $n = $name; + // Term names containing commas or quotes must be wrapped in quotes. + if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) { + $n = '"' . str_replace('"', '""', $name) . '"'; + } + else { + $term_matches[$prefix . $n] = check_plain($name); + } + } + } + + drupal_json_output($term_matches); +}