summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorAngie Byron <webchick@24967.no-reply.drupal.org>2009-08-18 06:01:07 +0000
committerAngie Byron <webchick@24967.no-reply.drupal.org>2009-08-18 06:01:07 +0000
commitae1d9c577baf30ce74c95ea557809b35d77b0806 (patch)
tree7edafb1fd6f344028152ee17a934f59dc3088951 /modules
parent80b4cd6610432fd1a8c29d747127b1480a2d2394 (diff)
downloadbrdo-ae1d9c577baf30ce74c95ea557809b35d77b0806.tar.gz
brdo-ae1d9c577baf30ce74c95ea557809b35d77b0806.tar.bz2
#526122 by bangpound, yched, and catch: Added an autocomplete widget for taxonomy term fields.
Diffstat (limited to 'modules')
-rw-r--r--modules/node/node.pages.inc1
-rw-r--r--modules/taxonomy/taxonomy.module197
-rw-r--r--modules/taxonomy/taxonomy.pages.inc57
3 files changed, 252 insertions, 3 deletions
diff --git a/modules/node/node.pages.inc b/modules/node/node.pages.inc
index 56b210df7..adcd326d4 100644
--- a/modules/node/node.pages.inc
+++ b/modules/node/node.pages.inc
@@ -330,6 +330,7 @@ function theme_node_form($form) {
*/
function node_preview($node) {
if (node_access('create', $node) || node_access('update', $node)) {
+ _field_invoke_multiple('load', 'node', array($node->nid => $node));
// Load the user's name when needed.
if (isset($node->name)) {
// The use of isset() is mandatory in the context of user IDs, because
diff --git a/modules/taxonomy/taxonomy.module b/modules/taxonomy/taxonomy.module
index f78a409b6..c68056337 100644
--- a/modules/taxonomy/taxonomy.module
+++ b/modules/taxonomy/taxonomy.module
@@ -84,6 +84,9 @@ function taxonomy_theme() {
'field_formatter_taxonomy_term_plain' => array(
'arguments' => array('element' => NULL),
),
+ 'taxonomy_autocomplete' => array(
+ 'arguments' => array('element' => NULL),
+ ),
);
}
@@ -228,6 +231,13 @@ function taxonomy_menu() {
'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
);
+ // TODO: remove with taxonomy_term_node_*
+ $items['taxonomy/autocomplete/legacy'] = array(
+ 'title' => 'Autocomplete taxonomy',
+ 'page callback' => 'taxonomy_autocomplete_legacy',
+ 'access arguments' => array('access content'),
+ 'type' => MENU_CALLBACK,
+ );
$items['admin/structure/taxonomy/%taxonomy_vocabulary'] = array(
'title' => 'Vocabulary', // this is replaced by callback
@@ -689,7 +699,7 @@ function taxonomy_form_alter(&$form, $form_state, $form_id) {
'#description' => $help,
'#required' => $vocabulary->required,
'#default_value' => $typed_string,
- '#autocomplete_path' => 'taxonomy/autocomplete/' . $vocabulary->vid,
+ '#autocomplete_path' => 'taxonomy/autocomplete/legacy/' . $vocabulary->vid,
'#weight' => $vocabulary->weight,
'#maxlength' => 1024,
);
@@ -1841,6 +1851,35 @@ function taxonomy_field_info() {
}
/**
+ * Implement hook_field_widget_info().
+ *
+ * We need custom handling of multiple values because we need
+ * to combine them into a options list rather than display
+ * cardinality elements. We will use the field module's default
+ * handling for default values.
+ *
+ * Callbacks can be omitted if default handing is used.
+ * They're included here just so this module can be used
+ * as an example for custom modules that might do things
+ * differently.
+ */
+function taxonomy_field_widget_info() {
+ return array(
+ 'taxonomy_autocomplete' => array(
+ 'label' => t('Autocomplete term widget (tagging)'),
+ 'field types' => array('taxonomy_term'),
+ 'settings' => array(
+ 'size' => 60,
+ 'autocomplete_path' => 'taxonomy/autocomplete',
+ ),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_CUSTOM,
+ ),
+ ),
+ );
+}
+
+/**
* Implement hook_field_widget_info_alter().
*/
function taxonomy_field_widget_info_alter(&$info) {
@@ -1874,6 +1913,19 @@ function taxonomy_field_schema($field) {
*/
function taxonomy_field_validate($obj_type, $object, $field, $instance, $items, &$errors) {
$allowed_values = taxonomy_allowed_values($field);
+ $widget = field_info_widget_types($instance['widget']['type']);
+
+ // Check we don't exceed the allowed number of values for widgets with custom
+ // behavior for multiple values (taxonomy_autocomplete widget).
+ if ($widget['behaviors']['multiple values'] == FIELD_BEHAVIOR_CUSTOM && $field['cardinality'] >= 2) {
+ if (count($items) > $field['cardinality']) {
+ $errors[$field['field_name']][0][] = array(
+ 'error' => 'taxonomy_term_illegal_value',
+ 'message' => t('%name: this field cannot hold more that @count values.', array('%name' => t($instance['label']), '@count' => $field['cardinality'])),
+ );
+ }
+ }
+
foreach ($items as $delta => $item) {
if (!empty($item['value'])) {
if (!isset($allowed_values[$item['value']])) {
@@ -2052,3 +2104,146 @@ function _taxonomy_clean_field_cache($term) {
function taxonomy_term_title($term) {
return check_plain($term->name);
}
+
+/**
+ * Implement hook_field_widget().
+ */
+function taxonomy_field_widget(&$form, &$form_state, $field, $instance, $items, $delta = NULL) {
+ $element = array(
+ '#type' => $instance['widget']['type'],
+ '#default_value' => !empty($items) ? $items : array(),
+ );
+ return $element;
+}
+
+/**
+ * Implement hook_field_widget_error().
+ */
+function taxonomy_field_widget_error($element, $error) {
+ $field_key = $element['#columns'][0];
+ form_error($element[$field_key], $error['message']);
+}
+
+/**
+ * Process an individual autocomplete widget element.
+ *
+ * Build the form element. When creating a form using FAPI #process, note that
+ * $element['#value'] is already set.
+ *
+ * The $field and $instance arrays are in $form['#fields'][$element['#field_name']].
+ *
+ * @todo For widgets to be actual FAPI 'elements', reusable outside of a 'field'
+ * context, they shoudn't rely on $field and $instance. The bits of information
+ * needed to adjust the behavior of the 'element' should be extracted in
+ * hook_field_widget() above.
+ */
+function taxonomy_autocomplete_elements_process($element, &$form_state, $form) {
+ $field = $form['#fields'][$element['#field_name']]['field'];
+ $instance = $form['#fields'][$element['#field_name']]['instance'];
+ $field_key = $element['#columns'][0];
+
+ // See if this element is in the database format or the transformed format,
+ // and transform it if necessary.
+ if (is_array($element['#value']) && !array_key_exists($field_key, $element['#value'])) {
+ $tags = array();
+ foreach ($element['#default_value'] as $item) {
+ $tags[$item['value']] = isset($item['taxonomy_term']) ? $item['taxonomy_term'] : taxonomy_term_load($item['value']);
+ }
+ $element['#value'] = taxonomy_implode_tags($tags);
+ }
+ $typed_string = $element['#value'];
+
+ $value = array();
+ $element[$field_key] = array(
+ '#type' => 'textfield',
+ '#default_value' => $typed_string,
+ '#autocomplete_path' => 'taxonomy/autocomplete/'. $element['#field_name'] .'/'. $element['#bundle'],
+ '#size' => $instance['widget']['settings']['size'],
+ '#attributes' => array('class' => 'text'),
+ '#title' => $element['#title'],
+ '#description' => $element['#description'],
+ '#required' => $element['#required'],
+ );
+ $element[$field_key]['#maxlength'] = !empty($field['settings']['max_length']) ? $field['settings']['max_length'] : NULL;
+
+ // Set #element_validate in a way that it will not wipe out other validation
+ // functions already set by other modules.
+ if (empty($element['#element_validate'])) {
+ $element['#element_validate'] = array();
+ }
+ array_unshift($element['#element_validate'], 'taxonomy_autocomplete_validate');
+
+ // Make sure field info will be available to the validator which does not get
+ // the values in $form.
+ $form_state['#fields'][$element['#field_name']] = $form['#fields'][$element['#field_name']];
+ return $element;
+}
+
+/**
+ * FAPI function to validate taxonomy term autocomplete element.
+ */
+function taxonomy_autocomplete_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 ($form_state['values'][$element['#field_name']]['value']) {
+
+ // @see taxonomy_node_save
+ $field = $form_state['#fields'][$element['#field_name']]['field'];
+ $field_key = $element['#columns'][0];
+ $vids = array();
+ foreach ($field['settings']['allowed_values'] as $tree) {
+ $vids[] = $tree['vid'];
+ }
+ $typed_terms = drupal_explode_tags($form_state['values'][$element['#field_name']]['value']);
+ $values = array();
+
+ foreach ($typed_terms as $typed_term) {
+
+ // See if the term exists in the chosen vocabulary and return the tid;
+ // otherwise, add a new record.
+ $possibilities = taxonomy_term_load_multiple(array(), array('name' => trim($typed_term), 'vid' => $vids));
+ $typed_term_tid = NULL;
+
+ // tid match, if any.
+ foreach ($possibilities as $possibility) {
+ $typed_term_tid = $possibility->tid;
+ break;
+ }
+ if (!$typed_term_tid) {
+ $vocabulary = taxonomy_vocabulary_load($vids[0]);
+ $edit = array(
+ 'vid' => $vids[0],
+ 'name' => $typed_term,
+ 'vocabulary_machine_name' => $vocabulary->machine_name,
+ );
+ $term = (object) $edit;
+ if ($status = taxonomy_term_save($term)) {
+ $typed_term_tid = $term->tid;
+ }
+ }
+ $values[$typed_term_tid] = $typed_term_tid;
+ }
+ $results = options_transpose_array_rows_cols(array($field_key => $values));
+ form_set_value($element, $results, $form_state);
+ }
+}
+
+/**
+ * Implement FAPI hook_elements().
+ *
+ * Any FAPI callbacks needed for individual widgets can be declared here,
+ * and the element will be passed to those callbacks for processing.
+ *
+ * Drupal will automatically theme the element using a theme with
+ * the same name as the hook_elements key.
+ */
+function taxonomy_elements() {
+ return array(
+ 'taxonomy_autocomplete' => array(
+ '#input' => TRUE,
+ '#columns' => array('value'),
+ '#delta' => 0,
+ '#process' => array('taxonomy_autocomplete_elements_process'),
+ ),
+ );
+}
diff --git a/modules/taxonomy/taxonomy.pages.inc b/modules/taxonomy/taxonomy.pages.inc
index 983ea3ac9..7d50436da 100644
--- a/modules/taxonomy/taxonomy.pages.inc
+++ b/modules/taxonomy/taxonomy.pages.inc
@@ -90,7 +90,7 @@ function taxonomy_term_edit($term) {
/**
* Helper function for autocompletion
*/
-function taxonomy_autocomplete($vid = 0, $tags_typed = '') {
+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));
@@ -131,11 +131,64 @@ function taxonomy_autocomplete($vid = 0, $tags_typed = '') {
$name = t('Did you mean %suggestion', array('%suggestion' => $name));
$synonym_matches[$prefix . $n] = filter_xss($name);
}
+ }
+ }
+
+ drupal_json(array_merge($term_matches, $synonym_matches));
+}
+
+/**
+ * Helper function for autocompletion
+ */
+function taxonomy_autocomplete($field_name, $bundle, $tags_typed = '') {
+ $instance = field_info_instance($field_name, $bundle);
+ $field = field_info_field($field_name);
+
+ // 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 != '') {
+
+ // Part of the criteria for the query come from the field's own settings.
+ $vids = array();
+ foreach ($field['settings']['allowed_values'] as $tree) {
+ $vids[] = $tree['vid'];
+ }
+
+ $query = db_select('taxonomy_term_data', 't');
+ $query->addTag('term_access');
+
+ // Do not select already entered terms.
+ if (!empty($tags_typed)) {
+ $query->condition('t.name', $tags_typed, 'NOT IN');
+ }
+ $tags_return = $query
+ ->fields('t', array('tid', 'name'))
+ ->condition('t.vid', $vids)
+ // Select rows that match by term name.
+ ->condition(db_or()
+ ->where("t.name LIKE :last_string", array(':last_string' => '%' . $tag_last . '%'))
+ )
+ ->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] = filter_xss($name);
}
}
}
- drupal_json(array_merge($term_matches, $synonym_matches));
+ drupal_json($term_matches);
}