Index: modules/node/node.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.pages.inc,v
retrieving revision 1.73
diff -u -p -r1.73 node.pages.inc
--- modules/node/node.pages.inc	4 Aug 2009 06:44:48 -0000	1.73
+++ modules/node/node.pages.inc	14 Aug 2009 17:53:55 -0000
@@ -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
Index: modules/taxonomy/taxonomy.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v
retrieving revision 1.495
diff -u -p -r1.495 taxonomy.module
--- modules/taxonomy/taxonomy.module	11 Aug 2009 15:50:56 -0000	1.495
+++ modules/taxonomy/taxonomy.module	14 Aug 2009 17:53:58 -0000
@@ -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, $fo
           '#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) {
@@ -2052,3 +2091,144 @@ function _taxonomy_clean_field_cache($te
 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 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.
+ * @todo Determine whether usability is harmed by allowing "multiple" to be 
+ * configurable.
+ */
+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) {
+      // TODO: do i need to load the term? when is the term not available?
+      $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) {
+  // Free tagging vocabularies do not send their tids in the form,
+  // so we'll 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'),
+    ),
+  );
+}
Index: modules/taxonomy/taxonomy.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.pages.inc,v
retrieving revision 1.31
diff -u -p -r1.31 taxonomy.pages.inc
--- modules/taxonomy/taxonomy.pages.inc	8 Jul 2009 07:18:08 -0000	1.31
+++ modules/taxonomy/taxonomy.pages.inc	14 Aug 2009 17:53:58 -0000
@@ -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,
         $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);
 }
