array( 'label' => t('Autocomplete Deluxe'), 'field types' => array('taxonomy_term_reference'), 'settings' => array( 'size' => 60, 'autocomplete_deluxe_path' => 'autocomplete_deluxe/taxonomy', ), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_CUSTOM, ), ), ); } /** * Custom taxonomy callback, which also accepts an empty string search. */ function autocomplete_deluxe_taxonomy_callback($field_name, $tags_typed = '', $limit = 10) { $field = field_info_field($field_name); $use_synonyms = !empty($_GET['synonyms']); // 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(); // Part of the criteria for the query come from the field's own settings. $vids = array(); $vocabularies = taxonomy_vocabulary_get_names(); foreach ($field['settings']['allowed_values'] as $tree) { // If the content taxonomy setting content_taxonomy_ignore_in_suggestions // is set, then the vocabulary is ignored. if (empty($tree['content_taxonomy_ignore_in_suggestions'])) { $vids[] = $vocabularies[$tree['vocabulary']]->vid; } } $query = db_select('taxonomy_term_data', 't'); $query->addTag('translatable'); $query->addTag('term_access'); if (module_exists('synonyms') && !empty($use_synonyms)) { $query->leftJoin('field_data_synonyms_synonym', 'fdss', 'fdss.entity_id = t.tid'); } if ($tag_last != '') { // 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. $query ->fields('t', array('tid', 'name')) ->condition('t.vid', $vids); if (module_exists('synonyms') && !empty($use_synonyms)) { $or = db_or(); $or->condition('fdss.synonyms_synonym_value', '%' . db_like($tag_last) . '%', 'LIKE'); $or->condition('t.name', '%' . db_like($tag_last) . '%', 'LIKE'); $query->condition($or); } else { $query->condition('t.name', '%' . db_like($tag_last) . '%', 'LIKE'); } if (isset($limit) && $limit > 0) { $query->range(0, $limit); } $tags_return = $query->execute() ->fetchAllKeyed(); } else { $query ->fields('t', array('tid', 'name')) ->condition('t.vid', $vids); if (isset($limit) && $limit > 0) { $query->range(0, $limit); } $tags_return = $query->execute() ->fetchAllKeyed(); } $prefix = count($tags_typed) ? drupal_implode_tags($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) . '"'; } $term_matches[$prefix . $n] = check_plain($name); } drupal_json_output($term_matches); } /** * Returns all allowed terms for a field without any prefix. */ function autocomplete_deluxe_allowed_terms($field) { $options = array(); foreach ($field['settings']['allowed_values'] as $tree) { if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) { if ($terms = taxonomy_get_tree($vocabulary->vid, $tree['parent'])) { foreach ($terms as $term) { $options[$term->name] = $term->name; } } } } return $options; } /** * Implements hook_field_widget_settings_form(). */ function autocomplete_deluxe_field_widget_settings_form($field, $instance) { $widget = $instance['widget']; $settings = $widget['settings']; $form['size'] = array( '#type' => 'textfield', '#title' => t('Size of textfield'), '#default_value' => isset($settings['size']) ? $settings['size'] : 6, '#element_validate' => array('_element_validate_integer_positive'), '#required' => TRUE, ); $form['limit'] = array( '#type' => 'textfield', '#title' => t('Limit of the output.'), '#description' => t('If set to zero no limit will be used'), '#default_value' => isset($settings['limit']) ? $settings['limit'] : 10, '#element_validate' => array('_element_validate_integer'), ); $form['min_length'] = array( '#type' => 'textfield', '#title' => t('Mininum length.'), '#description' => t('The minimum length of characters to enter to open the suggestion list.'), '#default_value' => isset($settings['min_length']) ? $settings['min_length'] : 0, '#element_validate' => array('_element_validate_integer'), ); $form['delimiter'] = array( '#type' => 'textfield', '#title' => t('Delimiter.'), '#description' => t('A character which should be used beside the enter key, to seperate terms.'), '#default_value' => isset($settings['delimiter']) ? $settings['delimiter'] : '', '#size' => 1, ); $form['not_found_message'] = array( '#type' => 'textfield', '#title' => t('Term not found message.'), '#description' => t('A message text which will be displayed, if the entered term was not found.'), '#default_value' => isset($settings['not_found_message']) ? $settings['not_found_message'] : "The term '@term' will be added.", ); if (module_exists('synonyms')) { $form['use_synonyms'] = array( '#type' => 'checkbox', '#title' => t('Allow synonyms'), '#description' => t('Should users be able to search for synonyms of terms?'), '#default_value' => isset($settings['use_synonyms']) ? $settings['use_synonyms'] : FALSE, ); } return $form; } /** * Implodes the tags from the taxonomy module. * * This function is essentially the same as axonomy_implode_tags, with the * difference, that it uses only a comma instead of a comma and a space to * implode the tags. It will help keep problems with delimiters to a minimum. */ function autocomplete_deluxe_taxonomy_implode_tags($tags, $vid = NULL) { $typed_tags = array(); foreach ($tags as $tag) { // Extract terms belonging to the vocabulary in question. if (!isset($vid) || $tag->vid == $vid) { // Make sure we have a completed loaded taxonomy term. if (isset($tag->name)) { // Commas and quotes in tag names are special cases, so encode 'em. if (strpos($tag->name, ',') !== FALSE || strpos($tag->name, '"') !== FALSE) { $tag->name = '"' . str_replace('"', '""', $tag->name) . '"'; } $typed_tags[] = $tag->name; } } } return implode(',', $typed_tags); } /** * Implements hook_field_widget_error(). */ function autocomplete_deluxe_field_widget_error($element, $error, $form, &$form_state) { form_error($element, $error['message']); } /** * Implements hook_field_widget_form(). */ function autocomplete_deluxe_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { $element += array( '#type' => 'autocomplete_deluxe', '#size' => $instance['widget']['settings']['size'], '#limit' => isset($instance['widget']['settings']['limit']) ? $instance['widget']['settings']['limit'] : 10, '#min_length' => isset($instance['widget']['settings']['min_length']) ? $instance['widget']['settings']['min_length'] : 0, '#use_synonyms' =>isset($instance['widget']['settings']['use_synonyms']) ? $instance['widget']['settings']['use_synonyms'] : 0, '#delimiter' =>isset($instance['widget']['settings']['delimiter']) ? $instance['widget']['settings']['delimiter'] : '', '#not_found_message' =>isset($instance['widget']['settings']['not_found_message']) ? $instance['widget']['settings']['not_found_message'] : "The term '@term' will be added.", ); $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED ? TRUE : FALSE; $tags = array(); foreach ($items as $item) { $tags[$item['tid']] = isset($item['taxonomy_term']) ? $item['taxonomy_term'] : taxonomy_term_load($item['tid']); } $element['#element_validate'] = array('taxonomy_autocomplete_validate'); $element += array( '#multiple' => $multiple, '#autocomplete_deluxe_path' => url($instance['widget']['settings']['autocomplete_deluxe_path'] . '/' . $field['field_name'], array('absolute' => TRUE)), '#default_value' => autocomplete_deluxe_taxonomy_implode_tags($tags), ); return $element; } /** * Generates the basic form elements and javascript settings. */ function autocomplete_deluxe_element_process($element) { $element['#attached'] = array( 'library' => array(array('system', 'ui.autocomplete'), array('system', 'ui.button')), 'js' => array(drupal_get_path('module', 'autocomplete_deluxe') . '/autocomplete_deluxe.js'), 'css' => array(drupal_get_path('module', 'autocomplete_deluxe') . '/autocomplete_deluxe.css'), ); // Workaround for problems with jquery css in seven theme. if ($GLOBALS['theme'] == 'seven') { $element['#attached']['css'][] = drupal_get_path('module', 'autocomplete_deluxe') . '/autocomplete_deluxe.seven.css'; } $html_id = drupal_html_id('autocomplete-deluxe-input'); $element['#after_build'][] = 'autocomplete_deluxe_after_build'; // Set default options for multiple values. $element['#multiple'] = isset($element['#multiple']) ? $element['#multiple'] : FALSE; $element['textfield'] = array( '#type' => 'textfield', '#size' => isset($element['#size']) ? $element['#size'] : '', '#attributes' => array('class' => array('autocomplete-deluxe-form'), 'id' => array($html_id)), '#default_value' => '', '#prefix' => '