=== modified file '.bzrignore'
--- .bzrignore	2008-12-25 06:38:36 +0000
+++ .bzrignore	2009-01-20 19:39:54 +0000
@@ -1,3 +1,9 @@
 CVS
 ./sites/default/files
 ./sites/default/settings.php
+./sites/all/modules/devel
+./sites/all/modules/admin_menu
+./.cache
+./.project
+./.settings
+.buildpath

=== modified file 'includes/form.inc'
--- includes/form.inc	2009-01-19 11:00:08 +0000
+++ includes/form.inc	2009-01-20 19:39:55 +0000
@@ -920,8 +920,10 @@ function form_builder($form_id, $form, &
   // after normal input parsing has been completed.
   if (isset($form['#after_build']) && !isset($form['#after_build_done'])) {
     foreach ($form['#after_build'] as $function) {
-      $form = $function($form, $form_state);
-      $form['#after_build_done'] = TRUE;
+      if (drupal_function_exists($function)) {
+        $form = $function($form, $form_state);
+        $form['#after_build_done'] = TRUE;
+      }
     }
   }
 

=== modified file 'includes/install.inc'
--- includes/install.inc	2009-01-18 07:00:09 +0000
+++ includes/install.inc	2009-01-20 19:39:55 +0000
@@ -490,8 +490,10 @@ function drupal_verify_profile($profile,
  *
  * @param $module_list
  *   The modules to install.
+ * @param $disable_modules_installed_hook
+ *   Normally just testing wants to set this to TRUE.
  */
-function drupal_install_modules($module_list = array()) {
+function drupal_install_modules($module_list = array(), $disable_modules_installed_hook = FALSE) {
   $files = module_rebuild_cache();
   $module_list = array_flip(array_values($module_list));
   do {
@@ -511,7 +513,7 @@ function drupal_install_modules($module_
   asort($module_list);
   $module_list = array_keys($module_list);
   $modules_installed = array_filter($module_list, '_drupal_install_module');
-  if (!empty($modules_installed)) {
+  if (!$disable_modules_installed_hook && !empty($modules_installed)) {
     module_invoke_all('modules_installed', $modules_installed);
   }
   module_enable($module_list);

=== modified file 'includes/module.inc'
--- includes/module.inc	2009-01-14 13:00:07 +0000
+++ includes/module.inc	2009-01-20 19:39:55 +0000
@@ -267,7 +267,7 @@ function module_enable($module_list) {
     // Force to regenerate the stored list of hook implementations.
     registry_rebuild();
   }
-
+  
   foreach ($invoke_modules as $module) {
     module_invoke($module, 'enable');
     // Check if node_access table needs rebuilding.
@@ -427,7 +427,6 @@ function module_implements($hook, $sort 
     }
     $loaded[$hook] = TRUE;
   }
-
   if ($sort) {
     if (!isset($sorted_implementations[$hook])) {
       $sorted_implementations[$hook] = $implementations[$hook];

=== modified file 'includes/theme.inc'
--- includes/theme.inc	2009-01-18 07:00:09 +0000
+++ includes/theme.inc	2009-01-20 19:39:55 +0000
@@ -2011,7 +2011,11 @@ function template_preprocess_node(&$vari
   }
   // Clean up name so there are no underscores.
   $variables['template_files'][] = 'node-' . str_replace('_', '-', $node->type);
-  $variables['template_files'][] = 'node-' . $node->nid;
+  $variables['template_files'][] = 'node-' . $node->nid;
+
+  // Add $FIELD_NAME_rendered variables for fields.
+  drupal_function_exists('field_attach_preprocess');
+  $variables += field_attach_preprocess('node', $node);
 }
 
 /**

=== added directory 'modules/field'
=== added file 'modules/field/D6 - D7 API changes.txt'
--- modules/field/D6 - D7 API changes.txt	1970-01-01 00:00:00 +0000
+++ modules/field/D6 - D7 API changes.txt	2009-01-20 19:39:54 +0000
@@ -0,0 +1,59 @@
+- content_types() -> field_info_*();
+- content_fields() -> field_info_*()
+- _content_field_types() -> field_info_field_types()
+  _content_widget_types() -> field_info_widget_types()
+- conntent_view_field() -> field_view_field(), with new signature
+- hook_field_info(), hook_widget_info(), hook_field_formatter_info():
+  hook_field_info() needs to specify default widget and formatters
+  new format for 'behaviors'
+  names of widget types and formatters need to be prefixed with the module name to avoid name clashes.
+- pattern for formatter theme functions has changed
+- hook_default_value() -> hook_widget_default_value()
+
+hook comparison: D7                              D6                                       Remarks
+-----------------------------------------------------------------------------------------------------------------------------
+hook_field_info()                                unchanged
+hook_field_columns($field)                       hook_field_settings('database columns')  defines 'data pins' in SQL-lingo
+hook_field_settings($field_type)                 hook_field_settings('save')              also returns default values for the settings - move into hook_field_info() ?
+hook_field_settings_form($field)                 hook_field_settings('form')              only for UI
+hook_field_settings_form_validate($field)        hook_field_settings('validate')          only for UI + fully deprecate in favor of regular FAPI validation ?
+hook_field_is_empty($item, $field)               unchanged                                Unlike other hook_field_*(), this operatates on a single value ($item, not $items)
+Previous hook_field() $op :
+hook_field_validate($obj_type, $object, $field, $instance, $items, $form)
+hook_field_presave($obj_type, $object, $field, $instance, $items)                         Use cases ?
+hook_field_load($obj_type, $object, $field, $instance, $items)
+hook_field_insert($obj_type, $object, $field, $instance, $items)
+hook_field_update($obj_type, $object, $field, $instance, $items)
+hook_field_delete($obj_type, $object, $field, $instance, $items)
+hook_field_delete_revision($obj_type, $object, $field, $instance, $items)
+hook_field_prepare_translation($obj_type, $object, $field, $instance, $items)
+hook_field_sanitize($obj_type, $object, $field, $instance, $items)                        Typically operates on single values, like hook_field_is_empty().
+
+hook_field_instance_settings($field_type)        -                                        also returns default values for the settings - move into hook_field_info() ?
+hook_field_instance_settings_form($instance)     -                                        only for UI
+hook_field_instance_settings_form_validate($instance) -                                   only for UI + fully deprecate for regular FAPI validation ?
+
+hook_widget_info()                               unchanged
+hook_widget_settings($widget_type)               hook_widget_settings('save')             also returns default values for the settings - move into hook_widget_info() ?
+hook_widget_settings_form($instance)             hook_widget_settings('form')             only for UI
+hook_widget_settings_form_validate($instance)    hook_widget_settings('validate')         only for UI + fully deprecate in favor of regular FAPI validation ?
+hook_widget()                                    unchanged                                return FAPI element
+hook_widget_default_value()                      hook_default_value()                     Signature looks strange
+
+hook_field_formatter_info()                      unchanged
+hook_field_formatter_settings($formatter_type)   -                                        also returns default values for the settings - move into hook_field_formatter_info() ?
+
+hook_field(): D6 $op    hook_field_*()              Default implementation
+--------------------------------------------------------------------------
+'validate'              x                           x (currently unused)
+'presave'               x (use cases ?)             x
+'load'                  x                           - (content_storage)
+'insert'                x                           - (content_storage)
+'update'                x                           - (content_storage)
+'delete'                x                           - (content_storage)
+'delete revision'       x                           - (content_storage)
+'prepare translation'   x (noderef uses it)         x
+'view'                  -                           x
+'preprocess_node'       -                           x
+
+'sanitize'              x                           -

=== added file 'modules/field/field.attach.inc'
--- modules/field/field.attach.inc	1970-01-01 00:00:00 +0000
+++ modules/field/field.attach.inc	2009-01-20 19:39:55 +0000
@@ -0,0 +1,367 @@
+<?php
+// $Id: content.nodeapi.inc,v 1.4 2008/12/06 20:14:15 yched Exp $
+
+// TODO D7 : consistency - do field_attach_functions return a value or alter in place ?
+
+// TOTO D7 : consistency - some of these functions process individual fields
+// and others process the combined value of all fields.
+// Should all iteration through available fields be done here instead of in Field?
+
+// Should hook_field_attach_op() pass id, vid, and bundle to the modules that invoke the hook
+// or let the invoking module extract them using field_attach_extract_ids()?
+// The field_invoke() hooks don't pass them, and passing all those extra variables is messier.
+// Making the helper function public for now since it appears other modules may need to use it.
+
+/**
+ * TODO : document the resulting $form structure, like we do for field_attach_view().
+ */
+function _field_attach_form($obj_type, $object, &$form, $form_state) {
+  // TODO : something's not right here : do we alter the form or return a value ?
+  $form += (array) _field_invoke_default('form', $obj_type, $object, $form, $form_state);
+
+  // Let other modules make changes to the form.
+  foreach (module_implements('field_attach_form') as $module) {
+    $function = $module .'_field_attach_form';
+    $function($obj_type, $object, $form, $form_state);
+  }
+}
+
+/**
+ * Load all fields for the most current version of each of a set of
+ * objects of a single object type.
+ *
+ * @param $obj_type
+ *   The type of objects for which to load fields; e.g. 'node' or
+ *   'user'.
+ * @param $objects
+ *   An array of objects for which to load fields.  The keys for
+ *   primary id and bundle name to load are identified by
+ *   hook_fieldable_info for $obj_type.
+ * @param $age
+ *   FIELD_LOAD_CURRENT to load the most recent revision for all
+ *   fields, or FIELD_LOAD_REVISION to load the version indicated by
+ *   each object.  Defaults to FIELD_LOAD_CURRENT; use
+ *   field_attach_load_revision() instead of passing FIELD_LOAD_REVISION.
+ * @returns
+ *   On return, the objects in $objects are modified by having the
+ *   appropriate set of fields added.
+ */
+function _field_attach_load($obj_type, $objects, $age = FIELD_LOAD_CURRENT) {
+  $queried_objs = array();
+
+  // Fetch avaliable nodes from cache.
+  foreach ($objects as $obj) {
+    list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $obj);
+    $cid = "field:$obj_type:$id:$vid";
+    if ($cacheable && $cached = cache_get($cid, 'cache_field')) {
+      foreach ($cached->data as $key => $value) {
+        $obj->$key = $value;
+      }
+    }
+    else {
+      $queried_objs[$id] = $objects[$id];
+    }
+  }
+  // Fetch other nodes from the database.
+  if ($queried_objs) {
+    // We need the raw additions to be able to cache them, so
+    // content_storage_load() and hook_field_load() must not alter
+    // nodes directly but return their additions.
+    $additions = module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'load', $obj_type, $queried_objs, $age);
+    foreach ($additions as $id => $obj_additions) {
+      foreach ($obj_additions as $key => $value) {
+        $queried_objs[$id]->$key = $value;
+      }
+    }
+
+    // TODO D7 : to be consistent we might want to make hook_field_load() accept
+    // multiple objects too. Which forbids going through _field_invoke(), but
+    // requires manually iterating the instances instead.
+    foreach ($queried_objs as $id => $object) {
+      $custom_additions = _field_invoke('load', $obj_type, $object);
+      foreach ($custom_additions as $key => $value) {
+        $queried_objs[$id]->$key = $value;
+        $additions[$id][$key] = $value;
+      }
+
+      // Let other modules act on loading the object.
+      foreach (module_implements('field_attach_load') as $module) {
+        $function = $module .'_field_attach_load';
+        $function($obj_type, $queried_objs[$id]);
+      }
+
+      // Cache the data.
+      list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $obj);
+      if ($cacheable) {
+        $cid = "field:$obj_type:$id:$vid";
+        $data = isset($additions[$id]) ? $additions[$id] : array();
+        cache_set($cid, $data, 'cache_field');
+      }
+    }
+  }
+}
+
+/**
+ * Load all fields for a previous version of each of a set of
+ * objects of a single object type.
+ *
+ * @param $obj_type
+ *   The type of objects for which to load fields; e.g. 'node' or
+ *   'user'.
+ * @param $objects
+ *   An array of objects for which to load fields.  The keys for
+ *   primary id, revision id, and bundle name to load are identified by
+ *   hook_fieldable_info for $obj_type.
+ * @returns
+ *   On return, the objects in $objects are modified by having the
+ *   appropriate set of fields added.
+ */
+function _field_attach_load_revision($obj_type, $objects) {
+  return field_attach_load($obj_type, $objects, FIELD_LOAD_REVISION);
+}
+
+/**
+ *
+ */
+function _field_attach_validate($obj_type, &$object, $form = NULL) {
+  _field_invoke('validate', $obj_type, $object, $form);
+  _field_invoke_default('validate', $obj_type, $object, $form);
+
+  // Let other modules validate the object.
+  foreach (module_implements('field_attach_validate') as $module) {
+    $function = $module .'_field_attach_validate';
+    $function($obj_type, $object, $form);
+  }
+}
+
+/**
+ * Accounts for d-n-d reordering of field values.
+ * TODO D7 : rename to 'field_attach_submit' ?
+ */
+function _field_attach_presave($obj_type, &$object) {
+  _field_invoke('presave', $obj_type, $object);
+  _field_invoke_default('presave', $obj_type, $object);
+
+  // Let other modules act on presaving the object.
+  foreach (module_implements('field_attach_presave') as $module) {
+    $function = $module .'_field_attach_presave';
+    $function($obj_type, $object);
+  }
+}
+
+/**
+ *
+ */
+function _field_attach_insert($obj_type, &$object) {
+
+  // Let other modules act on inserting the object.
+  foreach (module_implements('field_attach_insert') as $module) {
+    $function = $module .'_field_attach_insert';
+    $function($obj_type, $object);
+  }
+
+  _field_invoke('insert', $obj_type, $object);
+  module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'write', $obj_type, $object);
+
+  list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
+  if ($cacheable) {
+    cache_clear_all("field:$obj_type:$id:", 'cache_field', TRUE);
+  }
+}
+
+/**
+ *
+ */
+function _field_attach_update($obj_type, &$object) {
+
+  // Let other modules act on updating the object.
+  foreach (module_implements('field_attach_update') as $module) {
+    $function = $module .'_field_attach_update';
+    $function($output, $obj_type, $object);
+  }
+
+  _field_invoke('update', $obj_type, $object);
+  module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'write', $obj_type, $object, TRUE);
+
+  list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
+  if ($cacheable) {
+    cache_clear_all("field:$obj_type:$id:$vid", 'cache_field');
+  }
+}
+
+/**
+ *
+ */
+function _field_attach_delete($obj_type, &$object) {
+  _field_invoke('delete', $obj_type, $object);
+  module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'delete', $obj_type, $object);
+
+  // Let other modules act on deleting the object.
+  foreach (module_implements('field_attach_delete') as $module) {
+    $function = $module .'_field_attach_delete';
+    $function($obj_type, $object);
+  }
+
+  list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
+  if ($cacheable) {
+    cache_clear_all("field:$obj_type:$id:", 'cache_field', TRUE);
+  }
+}
+
+/**
+ *
+ */
+function _field_attach_delete_revision($obj_type, &$object) {
+  _field_invoke('delete revision', $obj_type, $object);
+  module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'delete_revision', $obj_type, $object);
+
+  // Let other modules act on deleting the revision.
+  foreach (module_implements('field_attach_delete_revision') as $module) {
+    $function = $module .'_field_attach_delete_revision';
+    $function($obj_type, $object);
+  }
+
+  list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
+  if ($cacheable) {
+    cache_clear_all("field:$obj_type:$id:$vid", 'cache_field');
+  }
+}
+
+/**
+ *
+ */
+function _field_attach_view($obj_type, &$object, $teaser = FALSE, $page = FALSE) {
+  // Let field modules sanitize their data for output.
+  _field_invoke('sanitize', $obj_type, $object);
+
+  $output = _field_invoke_default('view', $obj_type, $object, $teaser, $page);
+
+  // Let other modules make changes after rendering the view.
+  foreach (module_implements('field_attach_view') as $module) {
+    $function = $module .'_field_attach_view';
+    $function($output, $obj_type, $object, $teaser, $page);
+  }
+
+  return $output;
+
+}
+
+/**
+ * To be called in entity preprocessor.
+ *
+ * - Adds $FIELD_NAME_rendered variables
+ *   containing the themed output for the whole field.
+ * - Adds the formatted values in the 'view' key of the items.
+ */
+function _field_attach_preprocess($obj_type, &$object) {
+  return _field_invoke_default('preprocess', $obj_type, $object);
+}
+
+/**
+ * Implementation of hook_nodeapi_prepare_translation.
+ */
+function _field_attach_prepare_translation(&$node) {
+  // Prevent against invalid 'nodes' built by broken 3rd party code.
+  if (isset($node->type)) {
+    $type = content_types($node->type);
+    // Save cycles if the type has no fields.
+    if (!empty($type['instances'])) {
+      $default_additions = _field_invoke_default('prepare translation', $node);
+      $additions = _field_invoke('prepare translation', $node);
+      // Merge module additions after the default ones to enable overriding
+      // of field values.
+      $node = (object) array_merge((array) $node, $default_additions, $additions);
+    }
+  }
+}
+
+/**
+ * Notify field.module that a new bundle was created.
+ *
+ * The default SQL-based storage doesn't need to do anytrhing about it, but
+ * others might.
+ *
+ * @param $bundle
+ *   The name of the newly created bundle.
+ */
+function _field_attach_create_bundle($bundle) {
+  module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'create_bundle', $bundle);
+
+  // Clear the cache.
+  field_cache_clear();
+
+  foreach (module_implements('field_attach_create_bundle') as $module) {
+    $function = $module .'_field_attach_create_bundle';
+    $function($bundle);
+  }
+}
+
+/**
+ * Notify field.module that a bundle was renamed.
+ *
+ * @param $bundle_old
+ *   The previous name of the bundle.
+ * @param $bundle_new
+ *   The new name of the bundle.
+ */
+function _field_attach_rename_bundle($bundle_old, $bundle_new) {
+  module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'rename_bundle', $bundle_old, $bundle_new);
+  db_update('field_config_instance')
+    ->fields(array('bundle' => $bundle_new))
+    ->condition('bundle', $bundle_old)
+    ->execute();
+
+  // Clear the cache.
+  field_cache_clear();
+
+  foreach (module_implements('field_attach_rename_bundle') as $module) {
+    $function = $module .'_field_attach_rename_bundle';
+    $function($bundle_old, $bundle_new);
+  }
+}
+
+/**
+ * Notify field.module the a bundle was deleted.
+ *
+ * This deletes the data for the field instances as well as the field instances
+ * themselves.  This function actually just marks the data and field instances
+ * and deleted, leaving the garbage collection for a separate process, because
+ * it is not always possible to delete this much data in a single page request
+ * (particularly since for some field types, the deletion is more than just a
+ * simple DELETE query).
+ *
+ * @param $bundle
+ *   The bundle to delete.
+ */
+function _field_attach_delete_bundle($bundle) {
+  // Let other modules act on deleting the bundle
+  foreach (module_implements('field_attach_delete_bundle') as $module) {
+    $function = $module .'_field_attach_delete_bundle';
+    $function($bundle);
+  }
+
+  // Delete the instances themseves
+  $instances = field_info_instances($bundle);
+  foreach ($instances as $instance) {
+    field_delete_instance($instance['field_name'], $bundle);
+  }
+}
+
+/**
+ * Helper function to extract id, vid, and bundle name from an object.
+ *
+ * Made public so that it can be used by modules implementing
+ * hook_field_attach_op().
+ */
+function _field_attach_extract_ids($object_type, $object) {
+  // TODO D7 : prevent against broken 3rd party $node without 'type'.
+  $info = field_info_fieldable_types($object_type);
+  // Objects being created might not have id/vid yet.
+  $id = isset($object->{$info['id key']}) ? $object->{$info['id key']} : NULL;
+  $vid = ($info['revision key'] && isset($object->{$info['revision key']})) ? $object->{$info['revision key']} : NULL;
+  // If no bundle key provided, then we assume a single bundle, named after the
+  // type of the object.
+  $bundle = $info['bundle key'] ? $object->{$info['bundle key']} : $object_type;
+  $cacheable = isset($info['cacheable']) ? $info['cacheable'] : FALSE;
+  return array($id, $vid, $bundle, $cacheable);
+}

=== added file 'modules/field/field.autoload.inc'
--- modules/field/field.autoload.inc	1970-01-01 00:00:00 +0000
+++ modules/field/field.autoload.inc	2009-01-20 19:39:55 +0000
@@ -0,0 +1,228 @@
+<?php
+/**
+ * DO NOT EDIT THIS FILE.  It contains autoloading functions generated
+ * automatically by generate-autoload.pl.  The function names,
+ * arguments, and PHPdoc are copied from the file that defines the
+ * underlying function. To edit them, edit the underlying file instead.
+ */
+
+/**
+ * TODO : document the resulting $form structure, like we do for field_attach_view().
+ */
+function field_attach_form($obj_type, $object, &$form, $form_state) {
+  if (drupal_function_exists('_field_attach_form')) {
+    return _field_attach_form($obj_type, $object, $form, $form_state);
+  }
+  throw new FieldException('cannot autoload function _field_attach_form');
+}
+
+/**
+ * Load all fields for the most current version of each of a set of
+ * objects of a single object type.
+ *
+ * @param $obj_type
+ *   The type of objects for which to load fields; e.g. 'node' or
+ *   'user'.
+ * @param $objects
+ *   An array of objects for which to load fields.  The keys for
+ *   primary id and bundle name to load are identified by
+ *   hook_fieldable_info for $obj_type.
+ * @param $age
+ *   FIELD_LOAD_CURRENT to load the most recent revision for all
+ *   fields, or FIELD_LOAD_REVISION to load the version indicated by
+ *   each object.  Defaults to FIELD_LOAD_CURRENT; use
+ *   field_attach_load_revision() instead of passing FIELD_LOAD_REVISION.
+ * @returns
+ *   On return, the objects in $objects are modified by having the
+ *   appropriate set of fields added.
+ */
+function field_attach_load($obj_type, $objects, $age = FIELD_LOAD_CURRENT) {
+//  if (drupal_function_exists('_field_attach_load')) {
+  // QUICKFIX.
+  include_once DRUPAL_ROOT .'/modules/field/field.attach.inc';
+    return _field_attach_load($obj_type, $objects, $age);
+//  }
+  
+  throw new FieldException('cannot autoload function _field_attach_load');
+}
+
+/**
+ * Load all fields for a previous version of each of a set of
+ * objects of a single object type.
+ *
+ * @param $obj_type
+ *   The type of objects for which to load fields; e.g. 'node' or
+ *   'user'.
+ * @param $objects
+ *   An array of objects for which to load fields.  The keys for
+ *   primary id, revision id, and bundle name to load are identified by
+ *   hook_fieldable_info for $obj_type.
+ * @returns
+ *   On return, the objects in $objects are modified by having the
+ *   appropriate set of fields added.
+ */
+function field_attach_load_revision($obj_type, $objects) {
+  if (drupal_function_exists('_field_attach_load_revision')) {
+    return _field_attach_load_revision($obj_type, $objects);
+  }
+  throw new FieldException('cannot autoload function _field_attach_load_revision');
+}
+
+/**
+ *
+ */
+function field_attach_validate($obj_type, &$object, $form = NULL) {
+  if (drupal_function_exists('_field_attach_validate')) {
+    return _field_attach_validate($obj_type, $object, $form);
+  }
+  throw new FieldException('cannot autoload function _field_attach_validate');
+}
+
+/**
+ * Accounts for d-n-d reordering of field values.
+ * TODO D7 : rename to 'field_attach_submit' ?
+ */
+function field_attach_presave($obj_type, &$object) {
+  if (drupal_function_exists('_field_attach_presave')) {
+    return _field_attach_presave($obj_type, $object);
+  }
+  throw new FieldException('cannot autoload function _field_attach_presave');
+}
+
+/**
+ *
+ */
+function field_attach_insert($obj_type, &$object) {
+  if (drupal_function_exists('_field_attach_insert')) {
+    return _field_attach_insert($obj_type, $object);
+  }
+  throw new FieldException('cannot autoload function _field_attach_insert');
+}
+
+/**
+ *
+ */
+function field_attach_update($obj_type, &$object) {
+  if (drupal_function_exists('_field_attach_update')) {
+    return _field_attach_update($obj_type, $object);
+  }
+  throw new FieldException('cannot autoload function _field_attach_update');
+}
+
+/**
+ *
+ */
+function field_attach_delete($obj_type, &$object) {
+  if (drupal_function_exists('_field_attach_delete')) {
+    return _field_attach_delete($obj_type, $object);
+  }
+  throw new FieldException('cannot autoload function _field_attach_delete');
+}
+
+/**
+ *
+ */
+function field_attach_delete_revision($obj_type, &$object) {
+  if (drupal_function_exists('_field_attach_delete_revision')) {
+    return _field_attach_delete_revision($obj_type, $object);
+  }
+  throw new FieldException('cannot autoload function _field_attach_delete_revision');
+}
+
+/**
+ *
+ */
+function field_attach_view($obj_type, &$object, $teaser = FALSE, $page = FALSE) {
+  if (drupal_function_exists('_field_attach_view')) {
+    return _field_attach_view($obj_type, $object, $teaser, $page);
+  }
+  throw new FieldException('cannot autoload function _field_attach_view');
+}
+
+/**
+ * To be called in entity preprocessor.
+ *
+ * - Adds $FIELD_NAME_rendered variables
+ *   containing the themed output for the whole field.
+ * - Adds the formatted values in the 'view' key of the items.
+ */
+function field_attach_preprocess($obj_type, &$object) {
+  if (drupal_function_exists('_field_attach_preprocess')) {
+    return _field_attach_preprocess($obj_type, $object);
+  }
+  throw new FieldException('cannot autoload function _field_attach_preprocess');
+}
+
+/**
+ * Implementation of hook_nodeapi_prepare_translation.
+ */
+function field_attach_prepare_translation(&$node) {
+  if (drupal_function_exists('_field_attach_prepare_translation')) {
+    return _field_attach_prepare_translation($node);
+  }
+  throw new FieldException('cannot autoload function _field_attach_prepare_translation');
+}
+
+/**
+ * Notify field.module that a new bundle was created.
+ *
+ * The default SQL-based storage doesn't need to do anytrhing about it, but
+ * others might.
+ *
+ * @param $bundle
+ *   The name of the newly created bundle.
+ */
+function field_attach_create_bundle($bundle) {
+  if (drupal_function_exists('_field_attach_create_bundle')) {
+    return _field_attach_create_bundle($bundle);
+  }
+  throw new FieldException('cannot autoload function _field_attach_create_bundle');
+}
+
+/**
+ * Notify field.module that a bundle was renamed.
+ *
+ * @param $bundle_old
+ *   The previous name of the bundle.
+ * @param $bundle_new
+ *   The new name of the bundle.
+ */
+function field_attach_rename_bundle($bundle_old, $bundle_new) {
+  if (drupal_function_exists('_field_attach_rename_bundle')) {
+    return _field_attach_rename_bundle($bundle_old, $bundle_new);
+  }
+  throw new FieldException('cannot autoload function _field_attach_rename_bundle');
+}
+
+/**
+ * Notify field.module the a bundle was deleted.
+ *
+ * This deletes the data for the field instances as well as the field instances
+ * themselves.  This function actually just marks the data and field instances
+ * and deleted, leaving the garbage collection for a separate process, because
+ * it is not always possible to delete this much data in a single page request
+ * (particularly since for some field types, the deletion is more than just a
+ * simple DELETE query).
+ *
+ * @param $bundle
+ *   The bundle to delete.
+ */
+function field_attach_delete_bundle($bundle) {
+  if (drupal_function_exists('_field_attach_delete_bundle')) {
+    return _field_attach_delete_bundle($bundle);
+  }
+  throw new FieldException('cannot autoload function _field_attach_delete_bundle');
+}
+
+/**
+ * Helper function to extract id, vid, and bundle name from an object.
+ *
+ * Made public so that it can be used by modules implementing
+ * hook_field_attach_op().
+ */
+function field_attach_extract_ids($object_type, $object) {
+  if (drupal_function_exists('_field_attach_extract_ids')) {
+    return _field_attach_extract_ids($object_type, $object);
+  }
+  throw new FieldException('cannot autoload function _field_attach_extract_ids');
+}

=== added file 'modules/field/field.crud.inc'
--- modules/field/field.crud.inc	1970-01-01 00:00:00 +0000
+++ modules/field/field.crud.inc	2009-01-20 19:39:55 +0000
@@ -0,0 +1,457 @@
+<?php
+
+class FieldException extends Exception {}
+
+/**
+ * @defgroup field_crud Field CRUD API
+ * @{
+ *
+ * $field:
+ * - field_name (required)
+ * - type (required)
+ * - cardinality
+ * - locked
+ * - settings
+ *   - ..
+ * - module (internal)
+ * - active (internal)
+ * - deleted (internal)
+ * - columns (internal)
+ *
+ * $instance:
+ * - field_name (required)
+ * - bundle (required)
+ * - label
+ * - description
+ * - weight
+ * - required
+ * - settings
+ *   - ...
+ * - deleted (internal)
+ * - widget
+ *   - type
+ *   - default_value
+ *   - default_value_php
+ *   - settings
+ *     - ...
+ *   - module (internal)
+ *   - active (internal)
+ * - display
+ *   - (context_1)
+ *     - label
+ *     - exclude
+ *     - type
+ *     - settings
+ *         - ...
+ *     - module (internal)
+ *   - (context 2)
+ *     - ...
+ */
+
+/**
+ * This structure represents a Field object to the Field API.
+ *
+ * @param $field
+ *   An associative array representing a field. Keys and values:
+ *     field_name:  The name of the field.
+ *     type: Name of the field type, comes from a module implementing
+ *      field APIs (eg. text).
+ *     cardinality:
+ *     locked: Dont show in UI?
+ *     settings: Array of Field Type-specific Field properties (e.g.
+ *       'max_length' or 'formatted' for text).
+ *   The following keys are filled in by this function.
+ *     module: Name of the module that implements the Field Type.
+ *     active: Boolean, whether $module (the field type module) is enabled.
+ *     deleted: Always set to 0. Indicates that the field is not marked for
+ *       deletion.
+ *     columns: Array of column name to Schema API structure.
+ */
+function field_create_field($field) {
+  // Field name is required.
+  if (empty($field['field_name'])) {
+    throw new FieldException('Attempt to create an unnamed field.');
+  }
+  // Field name cannot contain invalid characters.
+  if (preg_match('/[^a-z0-9_]/', $field['field_name'])) {
+    throw new FieldException('Attempt to create a field with invalid characters. Only alphanumeric characters and underscores are allowed.');
+  }
+
+  // TODO: check that field_name < 32 chars.
+
+  // Check that the field type is known.
+  $field_type = field_info_field_types($field['type']);
+  if (!$field_type) {
+    throw new FieldException(t('Attempt to create a field of unknown type %type.', array('%type' => $field['type'])));
+  }
+
+  // Ensure the field name is unique. We also check disabled or deleted fields.
+  // TODO : do we want specific messages when clashing with a disabled or inactive field ?
+  $prior_field = field_read_field($field['field_name'], array('include_inactive' => TRUE, 'include_deleted' => TRUE));
+  if (!empty($prior_field)) {
+    throw new FieldException(t('Attempt to create field name %name which already exists.', array('%name' => $field['field_name'])));
+  }
+
+  $field += array(
+    'cardinality' => 1,
+    'locked' => FALSE,
+    'settings' => array(),
+  );
+  $module = $field_type['module'];
+  // Create all per-field-type properties (needed here as long as we have
+  // settings that impact column definitions).
+  $field['settings'] += field_info_field_settings($field['type']);
+  $field['module'] = $module;
+  $field['active'] = 1;
+  $field['deleted'] = 0;
+  // Create the data table. We need to populate the field columns, even though
+  // we don't actually store them.
+  $field['columns'] = _field_columns($field);
+  module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'create_table', $field);
+
+  drupal_write_record('field_config', $field);
+
+  // Clear caches
+  field_cache_clear(TRUE);
+}
+
+function field_read_field($field_name, $include_additional = array()) {
+  $fields = field_read_fields(array('field_name' => $field_name), $include_additional);
+  return $fields ? current($fields) : FALSE;
+}
+
+/**
+ * Read in fields that match an array of conditions.
+ *
+ * @params
+ *   An array of conditions to match against.
+ * @include_additional
+ *   The default behavior of this function is to not return fields that
+ *   are inactive or have been deleted. Setting $include_additional['include_inactive']
+ *   or $include_additional['include_deleted'] will override this behavior.
+ */
+function field_read_fields($params = array(), $include_additional = array()) {
+  $query = db_select('field_config', 'fc', array('fetch' => PDO::FETCH_ASSOC));
+  $query->fields('fc');
+
+  // Turn the conditions into a query.
+  foreach ($params as $key => $value) {
+    $query->condition($key, $value);
+  }
+  if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) {
+    $query->condition('fc.active', 1);
+  }
+  if (!isset($include_additional['include_deleted']) || !$include_additional['include_deleted']) {
+    $query->condition('fc.deleted', 0);
+  }
+
+  $fields = array();
+  $results = $query->execute();
+  foreach ($results as $field) {
+    // drupal_write_record() writes an empty string for empty arrays.
+    $field['settings'] = $field['settings'] ? unserialize($field['settings']) : array();
+
+    module_invoke_all('field_read_field', $field);
+
+    // Populate storage columns.
+    $field['columns'] = _field_columns($field);
+
+
+    $fields[$field['field_name']] = $field;
+  }
+  return $fields;
+}
+
+/**
+ * Mark a field for deletion, including all its instances and all data
+ * associated with it.
+ */
+function field_delete_field($field_name) {
+  // Mark the field for deletion.
+  db_update('field_config')
+    ->fields(array('deleted' => 1))
+    ->condition('field_name', $field_name)
+    ->execute();
+
+  // Mark any instances of the field for deletion.
+  db_update('field_config_instance')
+    ->fields(array('deleted' => 1))
+    ->condition('field_name', $field_name)
+    ->execute();
+  module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'delete_table', $field_name);
+  // Clear the cache.
+  field_cache_clear(TRUE);
+}
+
+/**
+ * Populate the storage columns for a field.
+ */
+function _field_columns($field) {
+  $columns = (array) module_invoke($field['module'], 'field_columns', $field);
+  // Actual column name : prefix with field name.
+  foreach($columns as $column_name => $attributes) {
+    $columns[$column_name]['column'] = $field['field_name'] . '_' . $column_name;
+  }
+  return $columns;
+}
+
+/**
+ * Creates an instance of a field, binding it to a bundle.
+ * @param $instance
+ *   An associative array represeting an instance structure. Keys and values:
+ *     field_name:  The name of the field.
+ *     bundle:  The bundle this field belongs to. (For eg. node_story)
+ *     label:  The human-readable name for this instance.
+ *     description:  A description of what this instance holds.
+ *     weight:  The weight for this instance, used by the Form API
+ *       representation, view rendering, etc.
+ *     settings:  Array of instance-specific properties (e.g. whether a
+ *       user-reference shows as a backreference on userpages, or allowed
+ *       file extensions on an update field).
+ *     widget: An associative array storing the edit widget and its settings.
+ *       type: The widget type (e.g. text_textfield or text_textarea).
+ * @TODO: cleanup from this point.
+ *   Internal properties:
+ *   $module: The module that defines the widget type.
+ *   $active: Whether $module is enabled.
+ *   $deleted: Always set to 0. Indicates that the field instance is not
+ *     marked for deletion.
+ *   $settings: Per-widget-type settings (e.g. rows for text_textarea).
+ * public $display: An array mapping display context names to
+ * public $display_settings: An array mapping display context names to
+ *   properties.  The properties can include:
+ *   'label': How the label should be displayed when this field is
+ *     rendered: 'above', 'inline', 'hidden'.
+ *   'formatter': TODO.
+ *   'exclude': Whether to omit this field value from the rendered content.
+ *   public $default_value.  TODO: DEFINE.
+ *   public $default_value_php. TODO: DEFINE.
+ *
+ ** Internal properties.  These are protected if possible.
+ * public $widget_active;
+ * protected $field;
+ * protected $_field_name;
+ * protected $widget_class = 'WidgetSettings';
+ */
+function field_create_instance($instance) {
+  // Check that the specified field exists.
+  $field = field_read_field($instance['field_name']);
+  if (empty($field)) {
+    throw new FieldException("Attempt to create an instance of a field that doesn't exist.");
+  }
+
+  // TODO: Check that the specifed bundle exists.
+
+  // TODO: Check that the widget type is known and can handle the field type ?
+  // TODO: Check that the formatters are known and can handle the field type ?
+  // TODO: Check that the display build modes are known for the object type ?
+  // Those checks should probably happen in _field_write_instance() ?
+  // Problem : this would mean that CCK cannot update an instance with a disabled formatter.
+
+  // Ensure the field instance is unique.
+  // TODO : do we want specific messages when clashing with a disabled or inactive instance ?
+  $prior_instance = field_read_instance($instance['field_name'], $instance['bundle'], array('include_inactive' => TRUE, 'include_deleted' => TRUE));
+  if (!empty($prior_instance)) {
+    throw new FieldException('Attempt to create a field instance which already exists.');
+  }
+
+  _field_write_instance($instance);
+
+  module_invoke_all('field_create_instance', $instance);
+
+  // Clear caches
+  field_cache_clear();
+  return FALSE;
+}
+
+/*
+ * Update an instance of a field.
+ *
+ * @param $instance
+ *   An associative array represeting an instance structure. The required
+ *   keys and values are:
+ *     field_name:  The name of an existing field.
+ *     bundle:  The bundle this field belongs to.
+ *
+ * @see field_create_instance()
+ */
+function field_update_instance($instance) {
+  // Check that the specified field exists.
+  $field = field_read_field($instance['field_name']);
+  if (empty($field)) {
+    throw new FieldException("Attempt to update an instance of a nonexistent field.");
+  }
+
+  // Check that the field instance exists (even if it is inactive, since we
+  // want to be able to replace inactive widgets with new ones).
+  $prior_instance = field_read_instance($instance['field_name'], $instance['bundle'], array('include_inactive' => TRUE));
+  if (empty($prior_instance)) {
+    throw new FieldException("Attempt to update a field instance that doesn't exist.");
+  }
+
+  _field_write_instance($instance, TRUE);
+
+  // Clear caches.
+  field_cache_clear();
+}
+
+function _field_write_instance($instance, $update = FALSE) {
+  $field = field_read_field($instance['field_name']);
+  $field_type = field_info_field_types($field['type']);
+
+  // Set defaults.
+  $instance += array(
+    'settings' => array(),
+    'display' => array(),
+    'widget' => array(),
+    'required' => FALSE,
+    'label' => $instance['field_name'],
+    'description' => '',
+    'weight' => 0,
+    'deleted' => 0,
+  );
+
+  // Set default instance settings.
+  $instance['settings'] += field_info_instance_settings($field['type']);
+
+  // Set default widget and settings.
+  $instance['widget'] += array(
+    // TODO: what if no 'default_widget' specified ?
+    'type' => $field_type['default_widget'],
+    'settings' => array(),
+  );
+  // Check widget module.
+  $widget_type = field_info_widget_types($instance['widget']['type']);
+  $widget_module = $widget_type['module'];
+  $widget_active = module_exists($widget_module);
+  $instance['widget']['settings'] += field_info_widget_settings($instance['widget']['type']);
+  $instance['widget']['module'] = $widget_module;
+  $instance['widget']['active'] = $widget_active;
+
+  // Make sure there is at least display info for the 'full' context.
+  $instance['display'] += array(
+    'full' => array(),
+  );
+  // Set default display settings for each context.
+  foreach ($instance['display'] as $context => $display) {
+    $instance['display'][$context] += array(
+      'label' => 'above',
+      'exclude' => 0,
+      // TODO: what if no 'default_formatter' specified ?
+      'type' => $field_type['default_formatter'],
+      'settings' => array(),
+    );
+    $formatter_type = field_info_formatter_types($instance['display'][$context]['type']);
+    // TODO : 'hidden' will raise PHP warnings.
+    $instance['display'][$context]['module'] = $formatter_type['module'];
+    $instance['display'][$context]['settings'] += field_info_formatter_settings($instance['display'][$context]['type']);
+  }
+
+  // Create $data to contain everything from $instance that does not
+  // have its own column, and thus will be stored serialized.
+  $data = $instance;
+  unset($data['field_name'], $data['bundle'], $data['widget']['type'], $data['weight'], $data['deleted']);
+
+  $record = array(
+    'field_name' => $instance['field_name'],
+    'bundle' => $instance['bundle'],
+    'widget_type' => $instance['widget']['type'],
+    'widget_module' => $widget_module,
+    'widget_active' => $widget_active,
+    'weight' => $instance['weight'],
+    'data' => $data,
+    'deleted' => $instance['deleted'],
+  );
+  // We need to tell drupal_update_record() the primary keys to trigger an
+  // update.
+  $primary_keys = $update ? array('field_name', 'bundle') : array();
+  drupal_write_record('field_config_instance', $record, $primary_keys);
+}
+
+function field_read_instance($field_name, $bundle, $include_additional = array()) {
+  $instances = field_read_instances(array('field_name' => $field_name, 'bundle' => $bundle), $include_additional);
+  return $instances ? current($instances) : FALSE;
+}
+
+/**
+ * Load a field instance.
+ *
+ * @param $param
+ *   An array of properties to use in selecting a field instance. Valid keys:
+ *   - 'bundle' - The name of the bundle (i.e. content type) in which the instance exists.
+ *   - 'field_name' - The name of the field whose instance is to be loaded.
+ *   if NULL, all instances will be returned.
+ * @param $include_additional
+ *   The default behavior of this function is to not return field instances that
+ *   are inactive or have been marked deleted. Setting $include_additional['include_inactive']
+ *   or $include_additional['include_deleted'] will override this behavior.
+ * @return
+ *   The field arrays.
+ */
+function field_read_instances($params = array(), $include_additional = array()) {
+  $query = db_select('field_config_instance', 'fci', array('fetch' => PDO::FETCH_ASSOC));
+  $query->join('field_config', 'fc', 'fc.field_name = fci.field_name');
+  $query->fields('fci');
+  #$query->fields('fc', array('type'));
+
+  // Turn the conditions into a query.
+  foreach ($params as $key => $value) {
+    $query->condition('fci.'.$key, $value);
+  }
+  $query->condition('fc.active', 1);
+  if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) {
+    $query->condition('fci.widget_active', 1);
+  }
+  if (!isset($include_additional['include_deleted']) || !$include_additional['include_deleted']) {
+    $query->condition('fc.deleted', 0);
+    $query->condition('fci.deleted', 0);
+  }
+
+  $instances = array();
+  $results = $query->execute();
+
+  foreach ($results as $record) {
+    $instance = unserialize($record['data']);
+    $instance['field_name'] = $record['field_name'];
+    $instance['bundle'] = $record['bundle'];
+    $instance['weight'] = $record['weight'];
+    $instance['deleted'] = $record['deleted'];
+    $instance['widget']['type'] = $record['widget_type'];
+    $instance['widget']['module'] = $record['widget_module'];
+    $instance['widget']['active'] = $record['widget_active'];
+
+    // TODO D7 : Set default widget settings, default instance settings, default display settings.
+    // (the modules that defined them might have changed since the instance was last saved).
+
+    module_invoke_all('field_read_instance', $instance);
+    $instances[] = $instance;
+  }
+  return $instances;
+}
+
+/**
+ * Mark a field instance for deletion, including all data associated with
+ * it.
+ *
+ * @param $field_name
+ *   The name of the field whose instance will be deleted.
+ * @param $bundle
+ *   The bundle for the instance which will be deleted.
+ */
+function field_delete_instance($field_name, $bundle) {
+  // Mark the field instance for deletion.
+  db_update('field_config_instance')
+    ->fields(array('deleted' => 1))
+    ->condition('field_name', $field_name)
+    ->condition('bundle', $bundle)
+    ->execute();
+
+  // Mark all data associated with the field for deletion.
+  module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'delete_instance', $field_name, $bundle);
+  // Clear the cache.
+  field_cache_clear();
+}
+
+/**
+ * @} End of "defgroup field_crud".
+ */
\ No newline at end of file

=== added file 'modules/field/field.default.inc'
--- modules/field/field.default.inc	1970-01-01 00:00:00 +0000
+++ modules/field/field.default.inc	2009-01-20 19:39:55 +0000
@@ -0,0 +1,271 @@
+<?php
+
+/**
+ * @file
+ *
+ * Default 'implementations' of hook_field_*().
+ *
+ * Handles common field housekeeping.
+ * Those implementations are special, as field.module does not define any field
+ * types. Those functions take care of default stuff common to all field types.
+ *
+ * Storage ops ('load', 'insert', 'update', 'delete', 'delete revisions')
+ * are not executed field by field, and are thus handled separately.
+ */
+
+function field_field_validate($obj_type, $object, $field, $instance, $items, $form, &$form_state) {
+  // TODO: here we could validate that required fields are filled in (for programmatic save)
+}
+
+function field_field_presave($obj_type, $object, $field, $instance, &$items) {
+  // If there was an AHAH add more button in this field, don't save it.
+  unset($items[$field['field_name'] .'_add_more']);
+
+  if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) {
+    // Reorder items to account for drag-n-drop reordering.
+    $items = _field_sort_items($field, $items);
+  }
+
+  // Filter out empty values.
+  $items = field_set_empty($field, $items);
+}
+
+/**
+ * The 'view' operation constructs the $object in a way that you can use
+ * drupal_render() to display the formatted output for an individual field.
+ * i.e. print drupal_render($object->content['field_foo']);
+ *
+ * The code supports both single value formatters, which theme an individual
+ * item value, and multiple value formatters, which theme all values for the
+ * field in a single theme. The multiple value formatters could be used, for
+ * instance, to plot field values on a single map or display them in a graph.
+ * Single value formatters are the default, multiple value formatters can be
+ * designated as such in formatter_info().
+ *
+ * The $object array will look like:
+ *   $object->content['field_foo']['wrapper'] = array(
+ *     '#type' => 'field',
+ *     '#title' => 'label'
+ *     '#field_name' => 'field_name',
+ *     '#object' => $object,
+ *     '#object_type' => $obj_type,
+ *     // Value of the $teaser param of hook_nodeapi('view').
+ *     '#teaser' => $teaser,
+ *     'items' =>
+ *       0 => array(
+ *         '#item' => $items[0],
+ *         // Only for 'single-value' formatters
+ *         '#theme' => $theme,
+ *         '#field_name' => 'field_name',
+ *         '#bundle' => $bundle,
+ *         '#formatter' => $formatter_name,
+ *         '#settings' => $formatter_settings,
+ *         '#object' => $object,
+ *         '#object_type' => $obj_type,
+ *         '#delta' => 0,
+ *       ),
+ *       1 => array(
+ *         '#item' => $items[1],
+ *         // Only for 'single-value' formatters
+ *         '#theme' => $theme,
+ *         '#field_name' => 'field_name',
+ *         '#bundle' => $bundle_name,
+ *         '#formatter' => $formatter_name,
+ *         '#settings' => $formatter_settings,
+ *         '#object' => $object,
+ *         '#object_type' => $obj_type,
+ *         '#delta' => 1,
+ *       ),
+ *       // Only for 'multiple-value' formatters
+ *       '#theme' => $theme,
+ *       '#field_name' => 'field_name',
+ *       '#bundle' => $bundle_name,
+ *       '#formatter' => $formatter_name,
+ *       '#settings' => $formatter_settings,
+ *     ),
+ *   );
+ */
+function field_field_view($obj_type, $object, $field, $instance, $items, $teaser) {
+  list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+
+  $addition = array();
+
+  // Entities without build modes should provide a 'full' context.
+  // NODE_BUILD_NORMAL is 0, and ('whatever' == 0) is TRUE, so we need a ===.
+  if (!isset($object->build_mode)) {
+    $context = 'full';
+  }
+  elseif ($object->build_mode === NODE_BUILD_NORMAL
+    || $object->build_mode == NODE_BUILD_PREVIEW) {
+    $context = $teaser ? 'teaser' : 'full';
+  }
+  else {
+    $context = $object->build_mode;
+  }
+
+  // If we don't have specific settings for the current build_nmode, we use the
+  // (required) 'full' build_mode.
+  $display = isset($instance['display'][$context]) ? $instance['display'][$context] : $instance['display']['full'];
+  // Ensure we have a valid formatter and formatter settings.
+  $display = _field_get_formatter($display, $field);
+
+  if ($display['type'] && $display['type'] !== 'hidden') {
+    $theme = 'field_formatter_'. $display['type'];
+    $single = (field_behaviors_formatter('multiple values', $display) == FIELD_BEHAVIOR_DEFAULT);
+
+    $label_display = $display['label'];
+    if (isset($object->build_mode) && $object->build_mode == NODE_BUILD_SEARCH_INDEX) {
+      $label_display = 'hidden';
+    }
+
+    $info = array(
+      '#field_name' => $field['field_name'],
+      '#bundle' => $bundle,
+      '#object' => $object,
+      '#object_type' => $obj_type,
+    );
+
+    $element = $info + array(
+      '#type' => 'field',
+      '#title' => check_plain(t($instance['label'])),
+      '#access' => field_access('view', $field),
+      '#label_display' => $label_display,
+      '#teaser' => $teaser,
+      '#single' => $single,
+      'items' => array(),
+    );
+
+    // Fill-in items.
+    foreach ($items as $delta => $item) {
+      $element['items'][$delta] = array(
+        '#item' => $item,
+        '#weight' => $delta,
+      );
+    }
+
+    // Append formatter information either on each item ('single-value' formatter)
+    // or at the upper 'items' level ('multiple-value' formatter)
+    $format_info = $info + array(
+      '#formatter' => $display['type'],
+      '#settings' => $display['settings'],
+      '#theme' => $theme,
+    );
+
+    if ($single) {
+      foreach ($items as $delta => $item) {
+        $element['items'][$delta] += $format_info;
+        $element['items'][$delta]['#item']['#delta'] = $delta;
+      }
+    }
+    else {
+      $element['items'] += $format_info;
+    }
+
+    // The wrapper lets us get the themed output for the whole field
+    // to populate the $FIELD_NAME_rendered variable for templates,
+    // and hide it from the $content variable if needed.
+    // See 'preprocess' op and theme_content_field_wrapper()?
+    $wrapper = $info + array(
+      'field' => $element,
+      '#weight' => $instance['weight'],
+      '#post_render' => array('field_wrapper_post_render'),
+      '#context' => $context,
+    );
+
+    $addition = array($field['field_name'] => $wrapper);
+  }
+  return $addition;
+}
+
+/**
+ * Hide excluded fields from the $content variable in templates.
+ */
+function field_wrapper_post_render($content, $element) {
+  $instance = field_info_instance($element['#field_name'], $element['#bundle']);
+  if (theme('field_exclude', $content, $instance, $element['#context'])) {
+    return '';
+  }
+  return $content;
+}
+
+/**
+ * 'Theme' function for a field's addition to the combined template output,
+ * i.e. the node's $content or the user's $user_profile value.
+ * This allows more flexibility in templates : you can use custom markup
+ * around a few specific fields, and print the rest normally.
+ *
+ * This is a theme function, so it can be overridden in different
+ * themes to produce different results.
+ *
+ * The html for individual fields and groups are available in the
+ * $FIELD_NAME_rendered and $GROUP_NAME_rendered variables.
+ *
+ * @return
+ *   Whether or not the field's content is to be added in this context.
+ *   Uses the 'exclude' value from the field's display settings.
+ */
+function theme_field_exclude($content, $object, $context) {
+  if (empty($object['display'])
+    || empty($object['display'][$context])
+    || empty($object['display'][$context]['exclude'])) {
+    return FALSE;
+  }
+  else {
+    return TRUE;
+  }
+}
+
+function field_field_preprocess($obj_type, $object, $field, $instance, &$items) {
+  return array(
+    $field['field_name'] .'_rendered' => isset($object->content[$field['field_name']]['#children']) ? $object->content[$field['field_name']]['#children'] : '',
+  );
+}
+
+function field_field_prepare_translation($obj_type, $object, $field, $instance, &$items) {
+  $addition = array();
+  if (isset($object->translation_source->$field['field_name'])) {
+    $addition[$field['field_name']] = $object->translation_source->$field['field_name'];
+  }
+  return $addition;
+}
+
+/**
+ *  Helper function to return the correct default value for a field.
+ *
+ *  @param $object
+ *    The object.
+ *  @param $field
+ *    The field array.
+ *  @param $items
+ *    The value of the field in the node.
+ *  @return
+ *    The default value for that field.
+ */
+// TODO D7: [yched] I really don't get the signature of this function - &$form, &$form_state ??
+// Karen, does it ring a bell for you ?
+// [Karen] It is called from field_field_form() and the parameter order matches
+// the D6 parameters that content_field_form() received -- it passes the
+// same parameters it receives on to the field modules in the same order.
+// I'm altering it to match the new parameters in field_field_form().
+// [yched] The new order of arguments definitely makes sense.
+// What I don't get is why we pass $form and $form_state: the only goal of the function is to return
+// a default '$items' sructure, not a partial form array. $items structure are form and widget agnostic.
+// The fact that we then use this default $items in a form (field_field_form()) is just incidental;
+// conceptually we should be able to ask "what is the default $items value of this field" from a non form context.
+// I wonder what info would be needed from $form or $form_state that's not already available in $obj_type, $object,
+// $field, $instance ?
+function field_widget_default_value($obj_type, $object, $field, $instance, &$form, &$form_state, $delta) {
+  $default_value = array();
+  if (!empty($instance['widget']['default_value_php'])) {
+    ob_start();
+    $result = eval($instance['widget']['default_value_php']);
+    ob_end_clean();
+    if (is_array($result)) {
+      $default_value = $result;
+    }
+  }
+  elseif (!empty($instance['widget']['default_value'])) {
+    $default_value = $instance['widget']['default_value'];
+  }
+  return (array) $default_value;
+}
\ No newline at end of file

=== added file 'modules/field/field.info'
--- modules/field/field.info	1970-01-01 00:00:00 +0000
+++ modules/field/field.info	2009-01-20 20:36:50 +0000
@@ -0,0 +1,14 @@
+; $Id$
+name = Field
+description = Field API to add fields to objects like nodes and users.
+package = Core - fields
+core = 7.x
+files[] = field.module
+files[] = field.install
+files[] = field.crud.inc
+files[] = field.default.inc
+files[] = field.attach.inc
+files[] = field.node_form.inc
+files[] = field.autoload.inc
+dependencies[] = field_sql_storage
+required = TRUE

=== added file 'modules/field/field.install'
--- modules/field/field.install	1970-01-01 00:00:00 +0000
+++ modules/field/field.install	2009-01-20 19:39:55 +0000
@@ -0,0 +1,153 @@
+<?php
+// $Id$
+/**
+ * Implementation of hook_install().
+ */
+function field_install() {
+  drupal_install_schema('field');
+}
+
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function field_uninstall() {
+  drupal_uninstall_schema('field');
+}
+
+/**
+ * Implementation of hook_enable().
+ */
+function field_enable() {
+  // Make sure old data is emptied out of the caches, since it
+  // may no longer be valid since the module was last enabled,
+  // especially if not all the same field modules are enabled
+  // as before. Especially needed during updates.
+  module_load_include('inc', 'field', 'field.crud');
+  cache_clear_all('*', 'cache_field', TRUE);
+  field_cache_clear(TRUE);
+}
+
+/**
+ * Implementation of hook_disable().
+ */
+function field_disable() {
+  // Make sure old data is emptied out of the caches, since it
+  // may no longer be valid when the module is re-enabled.
+  module_load_include('inc', 'field', 'field.crud');
+  cache_clear_all('*', 'cache_field', TRUE);
+  field_cache_clear(TRUE);
+}
+
+/**
+ * Implementation of hook_schema.
+ */
+function field_schema() {
+  // Static (meta) tables.
+  $schema['field_config'] = array(
+    'fields' => array(
+      'field_name' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'description' => 'The name of this field',
+      ),
+      'type' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'description' => 'The type of this field, coming from a field module',
+      ),
+      'locked' => array(
+        'type' => 'int',
+        'size' => 'tiny',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => '@TODO',
+      ),
+      'settings' => array(
+        'type' => 'text',
+        'size' => 'medium',
+        'not null' => TRUE,
+        'serialize' => TRUE,
+        'description' => 'Field specific settings, for example maximum length',
+      ),
+      'module' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'cardinality' => array(
+        'type' => 'int',
+        'size' => 'tiny',
+        'not null' => TRUE,
+          'default' => 0,
+      ),
+      'active' => array(
+        'type' => 'int',
+        'size' => 'tiny',
+        'not null' => TRUE,
+          'default' => 0,
+      ),
+      'deleted' => array(
+        'type' => 'int',
+        'size' => 'tiny',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'primary key' => array('field_name'),
+    'indexes' => array(
+      // used by field_read_fields
+      'active_deleted' => array('active', 'deleted'),
+      // used by field_modules_disabled
+      'module' => array('module'),
+      // used by field_associate_fields
+      'type' => array('type'),
+    ),
+  );
+  $schema['field_config_instance'] = array(
+    'fields' => array(
+      'field_name'        => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
+      'bundle'            => array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''),
+      'widget_type'       => array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''),
+      'widget_module'     => array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''),
+      'widget_active' => array(
+        'type' => 'int',
+        'size' => 'tiny',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'data' => array(
+        'type' => 'text',
+        'size' => 'medium',
+        'not null' => TRUE,
+        'serialize' => TRUE,
+      ),
+      'weight' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'deleted' => array(
+        'type' => 'int',
+        'size' => 'tiny',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'primary key' => array('field_name', 'bundle'),
+    'indexes' => array(
+      // used by field_read_instances
+      'widget_active_deleted' => array('widget_active', 'deleted'),
+      // used by field_modules_disabled
+      'widget_module' => array('widget_module'),
+      // used by field_associate_fields
+      'widget_type' => array('widget_type'),
+    ),
+  );
+  $schema['cache_field'] = drupal_get_schema_unprocessed('system', 'cache');
+
+  return $schema;
+}

=== added file 'modules/field/field.module'
--- modules/field/field.module	1970-01-01 00:00:00 +0000
+++ modules/field/field.module	2009-01-20 19:39:55 +0000
@@ -0,0 +1,1138 @@
+<?php
+// $Id: field.module,v 1.8 2008/12/10 19:09:44 yched Exp $
+/**
+ * @file
+ * Allows administrators to associate custom fields to field types.
+ */
+
+define('FIELD_CARDINALITY_UNLIMITED', -1);
+
+define('FIELD_BEHAVIOR_NONE', 0x0001);
+define('FIELD_BEHAVIOR_DEFAULT', 0x0002);
+define('FIELD_BEHAVIOR_CUSTOM', 0x0004);
+
+/**
+ * Age constants for field_attach_load.
+ */
+define('FIELD_LOAD_CURRENT', 'FIELD_LOAD_CURRENT');
+define('FIELD_LOAD_REVISION', 'FIELD_LOAD_REVISION');
+
+/**
+ * Implementation of hook_flush_caches.
+ */
+function field_flush_caches() {
+  return array('cache_field');
+}
+
+/**
+ * Implementation of hook_init().
+ *
+ * TODO D7: Determine which functions need to always be "loaded", and
+ * put autoloaders for them into field.autoload.inc.
+ */
+function field_init() {
+  module_load_include('inc', 'field', 'field.crud');
+  module_load_include('inc', 'field', 'field.autoload');
+}
+
+/**
+ * Implementation of hook_menu().
+ */
+function field_menu() {
+  $items = array();
+
+  // Callback for AHAH add more buttons.
+  $items['field/js_add_more'] = array(
+    'page callback' => 'field_add_more_js',
+    'access arguments' => array('access content'),
+    'type' => MENU_CALLBACK,
+  );
+
+  return $items;
+}
+
+/**
+ * Implementation of hook elements().
+ */
+function field_elements() {
+  return array(
+    'field' => array(),
+  );
+}
+
+/**
+ * Implementation of hook_theme().
+ */
+function field_theme() {
+  $path = drupal_get_path('module', 'field') .'/theme';
+
+  return array(
+    'field' => array(
+      'template' => 'field',
+      'arguments' => array('element' => NULL),
+      'path' => $path,
+    ),
+    // TODO D7 : do we need that in core ?
+    // Probably no easy way for a contrib module to do this
+    // since the Field module creates and processes the template,
+    // so maybe it must be in core. In core for now.
+    // [yched]: This is just adding
+    // '#post_render' => array('field_wrapper_post_render')
+    // at the right places in the render array generated by field_field_view().
+    // Can be done in hook_field_attach_post_view if we want.
+    'field_exclude' => array(
+      'arguments' => array('content' => NULL, 'object' => array(), 'context' => NULL),
+    ),
+    'field_multiple_value_form' => array(
+      'arguments' => array('element' => NULL),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_modules_installed().
+ */
+function field_modules_installed($modules) {
+  field_cache_clear();
+}
+
+/**
+ * Implementation of hook_modules_uninstalled().
+ */
+function field_modules_uninstalled($modules) {
+  module_load_include('inc', 'field', 'field.crud');
+  foreach ($modules as $module) {
+    field_module_delete($module);
+  }
+}
+
+/**
+ * Implementation of hook_modules_enabled().
+ */
+function field_modules_enabled($modules) {
+  foreach ($modules as $module) {
+    field_associate_fields($module);
+  }
+  field_cache_clear();
+}
+
+/**
+ * Implementation of hook_modules_disabled().
+ */
+function field_modules_disabled($modules) {
+  foreach ($modules as $module) {
+    db_update('field_config')
+      ->fields(array('active' => 0))
+      ->condition('module', $module)
+      ->execute();
+    db_update('field_config_instance')
+      ->fields(array('widget_active' => 0))
+      ->condition('widget_module', $module)
+      ->execute();
+    field_cache_clear(TRUE);
+  }
+}
+
+/**
+ * Allows a module to update the database for fields and columns it controls.
+ *
+ * @param string $module
+ *   The name of the module to update on.
+ */
+function field_associate_fields($module) {
+  $module_fields = module_invoke($module, 'field_info');
+  if ($module_fields) {
+    foreach ($module_fields as $name => $field_info) {
+      watchdog('field', 'Updating field type %type with module %module.', array('%type' => $name, '%module' => $module));
+      db_update('field_config')
+        ->fields(array('module' => $module, 'active' => 1))
+        ->condition('type', $name)
+        ->execute();
+    }
+  }
+  $module_widgets = module_invoke($module, 'widget_info');
+  if ($module_widgets) {
+    foreach ($module_widgets as $name => $widget_info) {
+      watchdog('field', 'Updating widget type %type with module %module.', array('%type' => $name, '%module' => $module));
+      db_update('field_config_instance')
+        ->fields(array('widget_module' => $module, 'widget_active' => 1))
+        ->condition('widget_type', $name)
+        ->execute();
+    }
+  }
+}
+
+/**
+ * Helper function to filter out empty values.
+ *
+ * On order to keep marker rows in the database, the function ensures
+ * that the right number of 'all columns NULL' values is kept.
+ *
+ * @param array $field
+ * @param array $items
+ * @return array
+ *   returns filtered and adjusted item array
+ */
+function field_set_empty($field, $items) {
+  // Filter out empty values.
+  $filtered = array();
+  $function = $field['module'] .'_field_is_empty';
+  foreach ((array) $items as $delta => $item) {
+    if (!$function($item, $field)) {
+      $filtered[] = $item;
+    }
+  }
+  return $filtered;
+}
+
+/**
+ * Helper function to sort items in a field according to
+ * user drag-n-drop reordering.
+ */
+function _field_sort_items($field, $items) {
+  if (($field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) && isset($items[0]['_weight'])) {
+    usort($items, '_field_sort_items_helper');
+    foreach ($items as $delta => $item) {
+      if (is_array($items[$delta])) {
+        unset($items[$delta]['_weight']);
+      }
+    }
+  }
+  return $items;
+}
+
+/**
+ * Sort function for items order.
+ * (copied form element_sort(), which acts on #weight keys)
+ */
+function _field_sort_items_helper($a, $b) {
+  $a_weight = (is_array($a) && isset($a['_weight'])) ? $a['_weight'] : 0;
+  $b_weight = (is_array($b) && isset($b['_weight'])) ? $b['_weight'] : 0;
+  if ($a_weight == $b_weight) {
+    return 0;
+  }
+  return ($a_weight < $b_weight) ? -1 : 1;
+}
+
+/**
+ * Same as above, using ['_weight']['#value']
+ */
+function _field_sort_items_value_helper($a, $b) {
+  $a_weight = (is_array($a) && isset($a['_weight']['#value'])) ? $a['_weight']['#value'] : 0;
+  $b_weight = (is_array($b) && isset($b['_weight']['#value'])) ? $b['_weight']['#value'] : 0;
+  if ($a_weight == $b_weight) {
+    return 0;
+  }
+  return ($a_weight < $b_weight) ? -1 : 1;
+}
+
+/**
+ * Invoke a field hook.
+ *
+ * @param $op
+ * - Possible operations include:
+ *   - load
+ *   - form
+ *   - validate
+ *   - presave
+ *   - insert
+ *   - update
+ *   - delete
+ *   - delete revision
+ *   - sanitize
+ *   - view
+ *   - preprocess
+ *   - prepare translation
+ *
+ * @param $obj_type
+ * - Can be:
+ *   - node
+ *   - user
+ *   - Others not yet implemented.
+ *
+ * @param $object
+ * - The fully formed $obj_type object.
+ *
+ * @param $a
+ *  - The $form in the 'form' operation.
+ *  - The value of $teaser in the 'view' operation.
+ *  - Otherwise NULL.
+ *
+ * @param $b
+ *  - The value of $page in the 'view' operation.
+ *  - Otherwise NULL.
+ *
+ * @param $default
+ *  - TRUE: render the default field implementation of the field hook.
+ *  - FALSE: render the field module's implementation of the field hook.
+ */
+function _field_invoke($op, $obj_type, &$object, &$a = NULL, &$b = NULL, $default = FALSE) {
+  list(, , $bundle) = field_attach_extract_ids($obj_type, $object);
+  $instances = field_info_instances($bundle);
+  
+  $return = array();
+  foreach ($instances as $instance) {
+    $field_name = $instance['field_name'];
+    $field = field_info_field($field_name);
+    $items = isset($object->$field_name) ? $object->$field_name : array();
+
+    // Make sure AHAH 'add more' button isn't sent to the fields for processing.
+    // TODO D7 : needed ?
+    unset($items[$field_name .'_add_more']);
+
+    $function = $default ? 'field_field_'. $op : $field['module'] .'_field_'. $op;
+    if (drupal_function_exists($function)) {
+      $result = $function($obj_type, $object, $field, $instance, $items, $a, $b);
+      if (is_array($result)) {
+        $return = array_merge($return, $result);
+      }
+      else if (isset($result)) {
+        $return[] = $result;
+      }
+    }
+    // test for values in $items in case modules added items on insert
+    if (isset($object->$field_name) || count($items)) {
+      $object->$field_name = $items;
+    }
+  }
+
+  return $return;
+}
+
+/**
+ * Invoke field.module's version of a field hook.
+ */
+function _field_invoke_default($op, $obj_type, &$object, &$a = NULL, &$b = NULL) {
+  return _field_invoke($op, $obj_type, $object, $a, $b, TRUE);
+}
+
+
+/**
+ *  Helper function for determining the behavior of a field
+ *  with respect to a given operation.
+ *
+ *  @param $op
+ *    The name of the operation.
+ *    Currently supported : none
+ *    // TODO D7: no use cases (yet ?) - do we want to keep that function ?.
+ *  @param $field
+ *    The field array.
+ *  @return
+ *    FIELD_BEHAVIOR_NONE    - do nothing for this operation.
+ *    FIELD_BEHAVIOR_CUSTOM  - use the field's callback function.
+ *    FIELD_BEHAVIOR_DEFAULT - use field.module default behavior.
+ */
+function field_behaviors_field($op, $field) {
+  $info = field_info_field_types($field['type']);
+  return isset($info['behaviors'][$op]) ? $info['behaviors'][$op] : FIELD_BEHAVIOR_DEFAULT;
+}
+
+/**
+ *  Helper function for determining the behavior of a widget
+ *  with respect to a given operation.
+ *
+ *  @param $op
+ *    The name of the operation.
+ *    Currently supported: 'default value', 'multiple values'.
+ *  @param $instance
+ *    The field instance array.
+ *  @return
+ *    FIELD_BEHAVIOR_NONE    - do nothing for this operation.
+ *    FIELD_BEHAVIOR_CUSTOM  - use the widget's callback function.
+ *    FIELD_BEHAVIOR_DEFAULT - use field.module default behavior.
+ */
+function field_behaviors_widget($op, $instance) {
+  $info = field_info_widget_types($instance['widget']['type']);
+  return isset($info['behaviors'][$op]) ? $info['behaviors'][$op] : FIELD_BEHAVIOR_DEFAULT;
+}
+
+/**
+ *  Helper function for determining the behavior of a formatter
+ *  with respect to a given operation.
+ *
+ *  @param $op
+ *    The name of the operation.
+ *    Currently supported: 'multiple values'
+ *  @param $display
+ *    The $instance['display'][$build_mode] array.
+ *  @return
+ *    FIELD_BEHAVIOR_NONE    - do nothing for this operation.
+ *    FIELD_BEHAVIOR_CUSTOM  - use the formatter's callback function.
+ *    FIELD_BEHAVIOR_DEFAULT - use field module default behavior.
+ */
+function field_behaviors_formatter($op, $display) {
+  $info = field_info_formatter_types($display['type']);
+  return isset($info['behaviors'][$op]) ? $info['behaviors'][$op] : FIELD_BEHAVIOR_DEFAULT;
+}
+
+/**
+ * Return hook_field_info() data.
+ *
+ * @param $field_type
+ *   (optional) A field type name. If ommitted, all field types will be
+ *   returned.
+ * @return
+ *   Either a field type description, as provided by hook_field_info(), or an
+ *   array of all existing field types, keyed by field type name.
+ */
+function field_info_field_types($field_type = NULL) {
+  $info = _field_info_types();
+  $field_types = $info['field types'];
+  if ($field_type) {
+    if (isset($field_types[$field_type])) {
+      return $field_types[$field_type];
+    }
+  }
+  else {
+    return $field_types;
+  }
+}
+
+/**
+ * Return hook_field_widget_info() data.
+ *
+ * @param $widget_type
+ *   (optional) A widget type name. If ommitted, all widget types will be
+ *   returned.
+ * @return
+ *   Either a widget type description, as provided by
+ *   hook_field_widget_info(), or an array of all existing widget
+ *   types, keyed by widget type name.
+ */
+function field_info_widget_types($widget_type = NULL) {
+  $info = _field_info_types();
+  $widget_types = $info['widget types'];
+  if ($widget_type) {
+    if (isset($widget_types[$widget_type])) {
+      return $widget_types[$widget_type];
+    }
+  }
+  else {
+    return $widget_types;
+  }
+}
+
+/**
+ * Return hook_field_formatter_info() data.
+ *
+ * @param $formatter_type
+ *   (optional) A formatter type name. If ommitted, all formatter types will be
+ *   returned.
+ * @return
+ *   Either a formatter type description, as provided by hook_field_formatter_info(),
+ *   or an array of all existing widget types, keyed by widget type name.
+ */
+function field_info_formatter_types($formatter_type = NULL) {
+  $info = _field_info_types();
+  $formatter_types = $info['formatter types'];
+  if ($formatter_type) {
+    if (isset($formatter_types[$formatter_type])) {
+      return $formatter_types[$formatter_type];
+    }
+  }
+  else {
+    return $formatter_types;
+  }
+}
+
+/**
+ * Return hook_fieldable_info() data.
+ *
+ * @param $obj_type
+ *   (optional) A fieldable type name. If ommitted, all fieldable types will be
+ *   returned.
+ * @return
+ *   Either a fieldable type description, as provided by hook_fieldable_info(),
+ *   or an array of all existing fieldable types, keyed by fieldable type name.
+ */
+function field_info_fieldable_types($obj_type = NULL) {
+  $info = _field_info_types();
+  $fieldable_types = $info['fieldable types'];
+  if ($obj_type) {
+    if (isset($fieldable_types[$obj_type])) {
+      return $fieldable_types[$obj_type];
+    }
+  }
+  else {
+    return $fieldable_types;
+  }
+}
+
+/**
+ * Return an array of fieldable bundle names and labels, for an individual
+ * object type or for all object types.
+ */
+function field_info_bundles($obj_type = NULL) {
+  $info = _field_info_types();
+  $bundles = array();
+  foreach ($info['fieldable types'] as $type => $fieldable_info) {
+    if (empty($obj_type) || $obj_type == $type) {
+      $bundles += $fieldable_info['bundles'];
+    }
+  }
+  return $bundles;
+}
+
+/**
+ * Identity the type of entity that created a bundle.
+ * // TODO : might not be needed depending on how we solve
+ * // the 'namespace bundle names' issue
+ */
+function field_info_bundle_entity($bundle) {
+  $info = _field_info_types();
+  foreach ($info['fieldable types'] as $type => $fieldable_info) {
+    if (isset($fieldable_info['bundles'][$bundle])) {
+      return $type;
+    }
+  }
+  return FALSE;
+}
+
+/**
+ * Return array of all field data, keyed by field name.
+ *
+ * @return
+ *   An array of Field objects.
+ */
+function field_info_fields() {
+  $info = _field_info_fields();
+  return $info['fields'];
+}
+
+/**
+ * Return data about an individual field.
+ *
+ * @param $field_name
+ */
+function field_info_field($field_name) {
+  $info = _field_info_fields();
+  if (isset($info['fields'][$field_name])) {
+    return $info['fields'][$field_name];
+  }
+}
+
+
+/**
+ * Return an array of instance data for a given bundle,
+ * or for all known bundles, keyed by bundle name and field name.
+ *
+ * @param $bundle_name
+ *   If set, return information on just this bundle.
+ */
+function field_info_instances($bundle_name = NULL) {
+  $info = _field_info_fields();
+  if (!isset($bundle_name)) {
+    return $info['instances'];
+  }
+  if (isset($info['instances'][$bundle_name])) {
+    return $info['instances'][$bundle_name];
+  }
+  return array();
+}
+
+/**
+ * Return an array of instance data for a specific field and bundle.
+ */
+function field_info_instance($field_name, $bundle_name) {
+  $info = _field_info_fields();
+  if (isset($info['instances'][$bundle_name][$field_name])) {
+    return $info['instances'][$bundle_name][$field_name];
+  }
+}
+
+/**
+ * Return valid formatter type and settings.
+ *
+ * Backs up to default formatter and settings if the intended formatter is no
+ * longer available. This might happen when the formatter has been renamed in
+ * the module, or if the module has been disabled since then.
+ */
+function _field_get_formatter($display, $field) {
+  if ($display['type'] != 'hidden') {
+    $formatter_type = field_info_formatter_types($display['type']);
+    if (empty($formatter_type)) {
+      $field_type = field_info_field_types($field['type']);
+      $display['type'] = $field_type['default_formatter'];
+      $formatter_type = field_info_formatter_types($display['type']);
+    }
+    $function = $formatter_type['module'] .'_field_formatter_settings';
+    if (drupal_function_exists($function)) {
+      $display['settings'] += $function($display['type']);
+    }
+  }
+  return $display;
+}
+
+/**
+ * Return a field type's default settings.
+ *
+ * @param $type
+ *   A field type name.
+ * @return
+ *   The field type's default settings, as provided by hook_field_info(), or an
+ *   empty array.
+ */
+function field_info_field_settings($type) {
+  $info = field_info_field_types($type);
+  return isset($info['settings']) ? $info['settings'] : array();
+}
+
+/**
+ * Return a field type's default instance settings.
+ *
+ * @param $type
+ *   A field type name.
+ * @return
+ *   The field type's default instance settings, as provided by
+ *   hook_field_info(), or an empty array.
+ */
+function field_info_instance_settings($type) {
+  $info = field_info_field_types($type);
+  return isset($info['instance_settings']) ? $info['instance_settings'] : array();
+}
+
+/**
+ * Return a field widget's default settings.
+ *
+ * @param $type
+ *   A widget type name.
+ * @return
+ *   The field type's default settings, as provided by hook_field_info(), or an
+ *   empty array.
+ */
+function field_info_widget_settings($type) {
+  $info = field_info_widget_types($type);
+  return isset($info['settings']) ? $info['settings'] : array();
+}
+
+/**
+ * Return a field formatter's default settings.
+ *
+ * @param $type
+ *   A field formatter type name.
+ * @return
+ *   The field formatter's default settings, as provided by
+ *   hook_field_info(), or an empty array.
+ */
+function field_info_formatter_settings($type) {
+  $info = field_info_formatter_types($type);
+  return isset($info['settings']) ? $info['settings'] : array();
+}
+
+/**
+ * Collate all information on field types, widget types and related structures.
+ *
+ * @param $reset
+ *   If TRUE, clear the cache. The information will be rebuilt from the database
+ *   next time it is needed. Defaults to FALSE.
+ * @return
+ *   If $reset is TRUE, nothing.
+ *   If $reset is FALSE, an array containing the following elements:
+ *
+ * field types: array of hook_field_info() results, keyed by field_type.
+ *   * label, description, settings, instance_settings, default_widget,
+ *     default_formatter, behaviors: from hook_field_info()
+ *   * module: the module that exposes the field type
+ *
+ * widget types: array of hook_field_widget_info() results, keyed by
+ * widget_type.
+ *   * label, field types, settings, behaviors: from hook_field_widget_info()
+ *   * module: module that exposes the widget type
+ *
+ * formatter types: array of hook_field_formatter_info() results, keyed by
+ * formatter_type.
+ *   * label, field types, behaviors: from hook_field_formatter_info()
+ *   * module: module that exposes the formatter type
+
+ * fieldable types: array of hook_fieldable_info() results, keyed by entity_type.
+ *   * name, id key, revision key, bundle key, cacheable, bundles: from
+ *     hook_fieldable_info()
+ *   * module: module that exposes the entity type
+ */
+function _field_info_types($reset = FALSE) {
+  static $info;
+
+  if ($reset) {
+    $info = NULL;
+    cache_clear_all('field_info_types', 'cache_field');
+    return;
+  }
+
+  if (!isset($info)) {
+    if ($cached = cache_get('field_info_types', 'cache_field')) {
+      $info = $cached->data;
+    }
+    else {
+      $info = array(
+        'field types' => array(),
+        'widget types' => array(),
+        'formatter types' => array(),
+        'fieldable types' => array(),
+      );
+
+      // TODO D7 : document max length for field types, widget types, formatter names...
+
+      // Populate field types.
+      foreach (module_implements('field_info') as $module) {
+        $field_types = (array) module_invoke($module, 'field_info');
+        foreach ($field_types as $name => $field_info) {
+          $info['field types'][$name] = $field_info;
+          $info['field types'][$name]['module'] = $module;
+        }
+      }
+
+      // Populate widget types.
+      foreach (module_implements('field_widget_info') as $module) {
+        $widget_types = (array) module_invoke($module, 'field_widget_info');
+        foreach ($widget_types as $name => $widget_info) {
+          $info['widget types'][$name] = $widget_info;
+          $info['widget types'][$name]['module'] = $module;
+        }
+      }
+
+      // Populate formatters.
+      foreach (module_implements('field_formatter_info') as $module) {
+        $formatter_types = (array) module_invoke($module, 'field_formatter_info');
+        foreach ($formatter_types as $name => $formatter_info) {
+          $info['formatter types'][$name] = $formatter_info;
+          $info['formatter types'][$name]['module'] = $module;
+        }
+      }
+
+      // Populate information about 'fieldable' entities.
+      foreach (module_implements('fieldable_info') as $module) {
+        $fieldable_types = (array) module_invoke($module, 'fieldable_info');
+        foreach ($fieldable_types as $name => $fieldable_info) {
+          // Provide defaults.
+          $fieldable_info += array(
+            'revision key' => '',
+            'bundle key' => '',
+            'cacheable' => TRUE,
+            'bundles' => array(),
+          );
+          // If no bundle key provided, then we assume a single bundle, named
+          // after the type of the object. Make sure the bundle created
+          // has the human-readable name we need for bundle messages.
+          if (empty($fieldable_info['bundle key'])) {
+            $fieldable_info['bundles'] = array($name => $fieldable_info['name']);
+          }
+          $info['fieldable types'][$name] = $fieldable_info;
+          $info['fieldable types'][$name]['module'] = $module;
+        }
+      }
+
+      cache_set('field_info_types', $info, 'cache_field');
+    }
+  }
+
+  return $info;
+}
+
+/**
+ * Collate all information on existing fields and instances.
+ *
+ * @param $reset
+ *   If TRUE, clear the cache. The information will be rebuilt from the database
+ *   next time it is needed. Defaults to FALSE.
+ * @return
+ *   If $reset is TRUE, nothing.
+ *   If $reset is FALSE, an array containing the following elements:
+ *
+ * fields: array of all defined Field objects, keyed by field name
+ *
+ * instances: array of bundled field info, keyed by bundle name
+ *   * contains all FieldInstance objects for this bundle, keyed by field name.
+ */
+function _field_info_fields($reset = FALSE) {
+  static $info;
+
+  if ($reset) {
+    $info = NULL;
+    cache_clear_all('field_info_fields', 'cache_field');
+    return;
+  }
+
+  if (!isset($info)) {
+    if ($cached = cache_get('field_info_fields', 'cache_field')) {
+      $info = $cached->data;
+    }
+    else {
+      $info = array(
+        'fields' => field_read_fields(),
+        'instances' => array_fill_keys(array_keys(field_info_bundles()), array()),
+      );
+
+      // Populate instances.
+      $instances = field_read_instances();
+      foreach ($instances as $instance) {
+        $info['instances'][$instance['bundle']][$instance['field_name']] = $instance;
+      }
+
+      cache_set('field_info_fields', $info, 'cache_field');
+    }
+  }
+
+  return $info;
+}
+
+/**
+ * Registry of available build modes.
+ * TODO : move into hook_fieldable_info() ?
+ */
+function field_build_modes($obj_type) {
+  static $info;
+
+  if (!isset($info[$obj_type])) {
+    // module_invoke_all messes numeric keys.
+    // TODO : revisit when we move away from numeric build modes.
+    $info[$obj_type] = array();
+    foreach (module_implements('field_build_modes') as $module) {
+      $info[$obj_type] += module_invoke($module, 'field_build_modes', $obj_type);
+    }
+  }
+  return $info[$obj_type];
+}
+
+/**
+ * Clear the cached information; called in several places when field
+ * information is changed.
+ */
+function field_cache_clear($rebuild_schema = FALSE) {
+  cache_clear_all('*', 'cache_field', TRUE);
+  _field_info_types(TRUE);
+  _field_info_fields(TRUE);
+
+  // Refresh the schema to pick up new information.
+  // TODO : if db storage gets abstracted out, we'll need to revisit how and when
+  // we refresh the schema...
+  if ($rebuild_schema) {
+    $schema = drupal_get_schema(NULL, TRUE);
+  }
+}
+
+/**
+ * Manipulate a 2D array to reverse rows and columns.
+ *
+ *  // TODO D7 : do we need this ? Seems specific to option_widgets
+ * The default data storage for fields is delta first, column names second.
+ * This is sometimes inconvenient for field modules, so this function can be
+ * used to present the data in an alternate format.
+ *
+ * @param $array
+ *   The array to be transposed. It must be at least two-dimensional, and
+ *   the subarrays must all have the same keys or behavior is undefined.
+ * @return
+ *   The transposed array.
+ */
+function field_transpose_array_rows_cols($array) {
+  $result = array();
+  if (is_array($array)) {
+    foreach ($array as $key1 => $value1) {
+      if (is_array($value1)) {
+        foreach ($value1 as $key2 => $value2) {
+          if (!isset($result[$key2])) {
+            $result[$key2] = array();
+          }
+          $result[$key2][$key1] = $value2;
+        }
+      }
+    }
+  }
+  return $result;
+}
+
+/**
+ * Like filter_xss_admin(), but with a shorter list of allowed tags.
+ *
+ * Used for items entered by administrators, like field descriptions,
+ * allowed values, where some (mainly inline) mark-up may be desired
+ * (so check_plain() is not acceptable).
+ */
+function field_filter_xss($string) {
+  return filter_xss($string, _field_filter_xss_allowed_tags());
+}
+
+/**
+ * List of tags allowed by field_filter_xss().
+ */
+function _field_filter_xss_allowed_tags() {
+  return array('a', 'b', 'big',  'code', 'del', 'em', 'i', 'ins',  'pre', 'q', 'small', 'span', 'strong', 'sub', 'sup', 'tt', 'ol', 'ul', 'li', 'p', 'br', 'img');
+}
+
+/**
+ * Human-readable list of allowed tags, for display in help texts.
+ */
+function _field_filter_xss_display_allowed_tags() {
+  return '<'. implode('> <', _field_filter_xss_allowed_tags()) .'>';
+}
+
+/**
+ * Format a field item for display.
+ *
+ * TODO D7 : do we still need field_format ?
+ * - backwards compatibility of templates - check what fallbacks we can propose...
+ * - used by Views integration in CCK D6
+ * At least needs a little rehaul/update...
+ *
+ * Used to display a field's values outside the context of the $node, as
+ * when fields are displayed in Views, or to display a field in a template
+ * using a different formatter than the one set up on the Display Fields tab
+ * for the node's context.
+ *
+ * @param $field
+ *   Either a field array or the name of the field.
+ * @param $item
+ *   The field item(s) to be formatted (such as $node->field_foo[0],
+ *   or $node->field_foo if the formatter handles multiple values itself)
+ * @param $formatter_name
+ *   The name of the formatter to use.
+ * @param $node
+ *   Optionally, the containing node object for context purposes and
+ *   field-instance options.
+ *
+ * @return
+ *   A string containing the contents of the field item(s) sanitized for display.
+ *   It will have been passed through the necessary check_plain() or check_markup()
+ *   functions as necessary.
+ */
+function field_format($obj_type, $object, $field, $item, $formatter_name = NULL, $formatter_settings = array()) {
+  if (!is_array($field)) {
+    $field = field_info_field($field);
+  }
+
+  if (field_access('view', $field)) {
+    // Basically, we need $field, $instance, $obj_type, $object to be able to display a value...
+    list(, , $bundle) = field_attach_extract_ids($obj_type, $object);
+    $instance = field_info_instance($field['field_name'], $bundle);
+
+    $display = array(
+      'type' => $formatter_name,
+      'settings' => $formatter_settings,
+    );
+    $display = _field_get_formatter($display, $field);
+    if ($display['type'] && $display['type'] !== 'hidden') {
+      $theme = $formatter['module'] .'_formatter_'. $display['type'];
+
+      $element = array(
+        '#theme' => $theme,
+        '#field_name' => $field['field_name'],
+        '#bundle' => $bundle,
+        '#formatter' => $display['type'],
+        '#settings' => $display['settings'],
+        '#object' => $object,
+        '#delta' => isset($item['#delta']) ? $item['#delta'] : NULL,
+      );
+
+      if (field_behaviors_formatter('multiple values', $display) == FIELD_BEHAVIOR_DEFAULT) {
+        // Single value formatter.
+
+        // hook_field('sanitize') expects an array of items, so we build one.
+        $items = array($item);
+        $function = $field['module'] .'_field_sanitize';
+        if (function_exists($function)) {
+          $function($obj_type, $object, $field, $instance, $items);
+        }
+
+        $element['#item'] = $items[0];
+      }
+      else {
+        // Multiple values formatter.
+        $items = $item;
+        $function = $field['module'] .'_field_sanitize';
+        if (function_exists($function)) {
+          $function($obj_type, $object, $field, $instance, $items);
+        }
+
+        foreach ($items as $delta => $item) {
+          $element[$delta] = array(
+            '#item' => $item,
+            '#weight' => $delta,
+          );
+        }
+      }
+
+      return theme($theme, $element);
+    }
+  }
+}
+
+/**
+ * Render a single field, fully themed with label and multiple values.
+ *
+ * To be used by third-party code (Views, Panels...) that needs to output
+ * an isolated field. Do *not* use inside node templates, use the
+ * $FIELD_NAME_rendered variables instead.
+ *
+ * By default, the field is displayed using the settings defined for the
+ * 'full' or 'teaser' contexts (depending on the value of the $teaser param).
+ * Set $node->build_mode to a different value to use a different context.
+ *
+ * Different settings can be specified by adjusting $field['display'].
+ *
+ * @param $field
+ *   The field definition.
+ * @param $object
+ *   The object containing the field to display. Must at least contain the id key,
+ *   revision key (if applicable), bundle key, and the field data.
+ * @param $teaser
+ *   Similar to hook_nodeapi('view')
+ * @return
+ *   The themed output for the field.
+ */
+function field_view_field($obj_type, $object, $field, $instance, $teaser = FALSE) {
+  $output = '';
+  if (isset($object->$field['field_name'])) {
+    $items = $object->$field['field_name'];
+
+    // Use 'full'/'teaser' if not specified otherwise.
+    $object->build_mode = isset($object->build_mode) ? $object->build_mode : NODE_BUILD_NORMAL;
+
+    // One-field equivalent to _field_invoke('sanitize').
+    $function = $field['module'] .'_field_sanitize';
+    if (drupal_function_exists($function)) {
+      $function($obj_type, $object, $field, $instance, $items);
+      $object->$field['field_name'] = $items;
+    }
+
+    $view = field_field_view($obj_type, $object, $field, $instance, $items, $teaser);
+    // TODO : what about hook_field_attach_view ?
+
+    // field_field('view') adds a wrapper to handle variables and 'excluded'
+    // fields for node templates. We bypass it and render the actual field.
+    $output = drupal_render($view[$field['field_name']]['field']);
+  }
+  return $output;
+}
+
+/**
+ * Determine whether the user has access to a given field.
+ *
+ * @param $op
+ *   The operation to be performed. Possible values:
+ *   - "edit"
+ *   - "view"
+ * @param $field
+ *   The field on which the operation is to be performed.
+ * @param $account
+ *   (optional) The account to check, if not given use currently logged in user.
+ * @return
+ *   TRUE if the operation is allowed;
+ *   FALSE if the operation is denied.
+ */
+function field_access($op, $field, $account = NULL) {
+  global $user;
+
+  if (is_null($account)) {
+    $account = $user;
+  }
+
+  $field_access = module_invoke_all('field_access', $op, $field, $account);
+  foreach ($field_access as $value) {
+    if ($value === FALSE) {
+      return FALSE;
+    }
+  }
+  return TRUE;
+}
+
+/**
+ * Theme preprocess function for field.tpl.php.
+ *
+ * The $variables array contains the following arguments:
+ * - $object
+ * - $field
+ * - $items
+ * - $teaser
+ * - $page
+ *
+ * @see field.tpl.php
+ */
+function template_preprocess_field(&$variables) {
+  $element = $variables['element'];
+  list(, , $bundle) = field_attach_extract_ids($element['#object_type'], $element['#object']);
+  $instance = field_info_instance($element['#field_name'], $bundle);
+  $field = field_info_field($element['#field_name']);
+
+  $variables['object'] = $element['#object'];
+  $variables['field'] = $field;
+  $variables['instance'] = $instance;
+  $variables['items'] = array();
+
+  if ($element['#single']) {
+    // Single value formatter.
+    foreach (element_children($element['items']) as $delta) {
+      $variables['items'][$delta] = $element['items'][$delta]['#item'];
+      // Use isset() to avoid undefined index message on #children when field values are empty.
+      $variables['items'][$delta]['view'] = isset($element['items'][$delta]['#children']) ? $element['items'][$delta]['#children'] : '';
+    }
+  }
+  else {
+    // Multiple values formatter.
+    // We display the 'all items' output as $items[0], as if it was the
+    // output of a single valued field.
+    // Raw values are still exposed for all items.
+    foreach (element_children($element['items']) as $delta) {
+      $variables['items'][$delta] = $element['items'][$delta]['#item'];
+    }
+    $variables['items'][0]['view'] = $element['items']['#children'];
+  }
+
+  $variables['teaser'] = $element['#teaser'];
+  $variables['page'] = (bool)menu_get_object();
+
+  $field_empty = TRUE;
+
+  foreach ($variables['items'] as $delta => $item) {
+    if (!isset($item['view']) || (empty($item['view']) && (string)$item['view'] !== '0')) {
+      $variables['items'][$delta]['empty'] = TRUE;
+    }
+    else {
+      $field_empty = FALSE;
+      $variables['items'][$delta]['empty'] = FALSE;
+    }
+  }
+
+  $additions = array(
+    'field_type' => $field['type'],
+    'field_name' => $field['field_name'],
+    'field_type_css' => strtr($field['type'], '_', '-'),
+    'field_name_css' => strtr($field['field_name'], '_', '-'),
+    'label' => check_plain(t($instance['label'])),
+    'label_display' => $element['#label_display'],
+    'field_empty' => $field_empty,
+    'template_files' => array(
+      'field',
+      'field-'. $element['#field_name'],
+      'field-'. $bundle,
+      'field-'. $element['#field_name'] .'-'. $bundle,
+    ),
+  );
+  $variables = array_merge($variables, $additions);
+}
+
+// TODO D7: needed ?
+/**
+ * Helper function to identify inactive fields.
+ */
+function field_inactive_fields($type_name = NULL) {
+//  module_load_include('inc', 'field', 'includes/field.crud');
+//  if (!empty($type_name)) {
+//    $param = array('type_name' => $type_name);
+//    $inactive = array($type_name => array());
+//  }
+//  else {
+//    $param = array();
+//    $inactive = array();
+//  }
+//  $all = field_field_instance_read($param, TRUE);
+//  $active = array_keys(field_fields());
+//  foreach ($all as $field) {
+//    if (!in_array($field['field_name'], $active)) {
+//      $inactive[$field['type_name']][$field['field_name']] = field_field_instance_expand($field);
+//    }
+//  }
+//  if (!empty($type_name)) {
+//    return $inactive[$type_name];
+//  }
+//  return $inactive;
+}

=== added file 'modules/field/field.node_form.inc'
--- modules/field/field.node_form.inc	1970-01-01 00:00:00 +0000
+++ modules/field/field.node_form.inc	2009-01-20 19:39:55 +0000
@@ -0,0 +1,415 @@
+<?php
+// $Id: field.node_form.inc,v 1.2 2008/12/10 02:01:41 yched Exp $
+// TODO : merge with field.default.inc ?
+
+/**
+ * Create a separate form element for each field.
+ */
+function field_field_form($obj_type, $object, $field, $instance, $items, &$form, &$form_state, $get_delta = NULL) {
+  // TODO D7 : explain why we might not have any incoming object (CCK 'default value widget')
+  if ($object) {
+    list($id, ,) = field_attach_extract_ids($obj_type, $object);
+  }
+  $addition = array();
+
+  $field_name = $field['field_name'];
+  // Put field information at the top of the form, so that it can be easily
+  // retrieved.
+  // Note : widgets and other form handling code should *always* fetch
+  // field and instance information from $form['#fields'] rather than from
+  // field_info_field(). This lets us build forms for 'variants' of a field,
+  // for instance on admin screens.
+  $form['#fields'][$field_name] = array(
+    'field' => $field,
+    'instance' => $instance,
+  );
+  // TODO : why do we need this ?
+  $form['#cache'] = FALSE;
+
+  // If no access to edit the field, skip widget processing and just set the value.
+  $access = field_access('edit', $field);
+
+  if (!$access) {
+    $addition[$field_name] = array(
+      '#access' => $access,
+      '#type' => 'value',
+      '#value' => $items,
+    );
+    return $addition;
+  }
+
+  // Populate with default values if we're creating a new object.
+  if (empty($items) && empty($id)) {
+    switch (field_behaviors_widget('default value', $instance)) {
+      case FIELD_BEHAVIOR_CUSTOM:
+        $default_value_function = $instance['widget']['module'] .'_widget_default_value';
+        break;
+      case FIELD_BEHAVIOR_DEFAULT:
+        $default_value_function = 'field_widget_default_value';
+        break;
+    }
+    if (isset($default_value_function) && drupal_function_exists($default_value_function)) {
+      $items = $default_value_function($obj_type, $object, $field, $instance, $form, $form_state, 0);
+    }
+  }
+
+  $form_element = array();
+
+  // If field module handles multiple values for this form element,
+  // and we are displaying an individual element, process the multiple value
+  // form.
+  if (!isset($get_delta) && field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) {
+    $form_element = field_multiple_value_form($field, $instance, $items, $form, $form_state);
+  }
+  // If the widget is handling multiple values (e.g optionwidgets),
+  // or if we are displaying an individual element, just get a single form
+  // element and make it the $delta value.
+  else {
+    $delta = isset($get_delta) ? $get_delta : 0;
+    $function = $instance['widget']['module'] .'_field_widget';
+    if (drupal_function_exists($function)) {
+      if ($element = $function($form, $form_state, $instance, $items, $delta)) {
+        $defaults = array(
+          '#required' => $get_delta > 0 ? FALSE : $instance['required'],
+          '#columns'  => array_keys($field['columns']),
+          '#title' => check_plain(t($instance['label'])),
+          '#description' => field_filter_xss($instance['description']),
+          '#delta' => $delta,
+          '#field_name' => $field['field_name'],
+          '#bundle' => $instance['bundle'],
+        );
+        $element = array_merge($element, $defaults);
+        // If we're processing a specific delta value for a field where the
+        // field module handles multiples, set the delta in the result.
+        // For fields that handle their own processing, we can't make assumptions
+        // about how the field is structured, just merge in the returned value.
+        if (field_behaviors_widget('multiple values', $instance) == FIELD_BEHAVIOR_DEFAULT) {
+          $form_element[$delta] = $element;
+        }
+        else {
+          $form_element = $element;
+        }
+      }
+    }
+  }
+
+  if ($form_element) {
+    $defaults = array(
+      '#field_name' => $field['field_name'],
+      '#tree' => TRUE,
+      '#weight' => $instance['weight'],
+      '#access' => $access,
+    );
+
+    $addition[$field['field_name']] = array_merge($form_element, $defaults);
+    $form['#fields'][$field['field_name']]['form_path'] = array($field['field_name']);
+  }
+
+  return $addition;
+}
+
+/**
+ * Special handling to create form elements for multiple values.
+ *
+ * Handles generic features for multiple fields:
+ * - number of widgets
+ * - AHAH-'add more' button
+ * - drag-n-drop value reordering
+ */
+function field_multiple_value_form($field, $instance, $items, &$form, &$form_state) {
+  $field = field_info_field($instance['field_name']);
+  $field_name = $field['field_name'];
+
+  switch ($field['cardinality']) {
+    case FIELD_CARDINALITY_UNLIMITED:
+      $filled_items = field_set_empty($field, $items);
+      $current_item_count = isset($form_state['item_count'][$field_name])
+                            ? $form_state['item_count'][$field_name]
+                            : count($items);
+      // We always want at least one empty icon for the user to fill in.
+      $max = ($current_item_count > count($filled_items))
+              ? $current_item_count - 1
+              : $current_item_count;
+
+      break;
+    default:
+      $max = $field['cardinality'] - 1;
+      break;
+  }
+
+  $title = check_plain(t($instance['label']));
+  $description = field_filter_xss(t($instance['description']));
+
+  $form_element = array(
+    '#theme' => 'field_multiple_value_form',
+    '#multiple' => $field['cardinality'],
+    '#title' => $title,
+    '#required' => $instance['required'],
+    '#description' => $description,
+  );
+
+  $function = $instance['widget']['module'] .'_field_widget';
+  if (drupal_function_exists($function)) {
+    for ($delta = 0; $delta <= $max; $delta++) {
+      if ($element = $function($form, $form_state, $instance, $items, $delta)) {
+        $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED;
+        $defaults = array(
+          '#title' => $multiple ? '' : $title,
+          '#description' => $multiple ? '' : $description,
+          '#required' => $delta == 0 && $instance['required'],
+          '#weight' => $delta,
+          '#delta' => $delta,
+          '#columns' => array_keys($field['columns']),
+          '#field_name' => $field_name,
+          '#bundle' => $instance['bundle'],
+        );
+
+        // Add an input field for the delta (drag-n-drop reordering), which will
+        // be hidden by tabledrag js behavior.
+        if ($multiple) {
+          // We name the element '_weight' to avoid clashing with column names
+          // defined by field modules.
+          $element['_weight'] = array(
+            '#type' => 'weight',
+            '#delta' => $max, // this 'delta' is the 'weight' element's property
+            '#default_value' => isset($items[$delta]['_weight']) ? $items[$delta]['_weight'] : $delta,
+            '#weight' => 100,
+          );
+        }
+
+        $form_element[$delta] = array_merge($element, $defaults);
+      }
+    }
+
+    // Add AHAH add more button, if not working with a programmed form.
+    if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED && empty($form['#programmed'])) {
+      // Make sure the form is cached so ahah can work.
+      $form['#cache'] = TRUE;
+      $bundle_name_url_str = str_replace('_', '-', $instance['bundle']);
+      $field_name_url_str = str_replace('_', '-', $field_name);
+
+      $form_element[$field_name .'_add_more'] = array(
+        '#type' => 'submit',
+        '#name' => $field_name .'_add_more',
+        '#value' => t('Add another item'),
+        '#weight' => $instance['weight'] + $max + 1,
+        // Submit callback for disabled JavaScript.
+        '#submit' => array('field_add_more_submit'),
+        '#ahah' => array(
+          'path' => 'field/js_add_more/'. $bundle_name_url_str .'/'. $field_name_url_str,
+          'wrapper' => $field_name_url_str .'-items',
+          'method' => 'replace',
+          'effect' => 'fade',
+        ),
+        // When JS is disabled, the field_add_more_submit handler will find
+        // the relevant field using these entries.
+        '#field_name' => $field_name,
+        '#bundle' => $instance['bundle'],
+      );
+
+      // Add wrappers for the fields and 'more' button.
+      $form_element['#prefix'] = '<div class="clear-block" id="'. $field_name_url_str .'-add-more-wrapper"><div id="'. $field_name_url_str .'-items">';
+      $form_element[$field_name .'_add_more']['#prefix'] = '<div class="field-add-more">';
+      $form_element[$field_name .'_add_more']['#suffix'] =  '</div></div></div>';
+    }
+  }
+  return $form_element;
+}
+
+/**
+ * Theme an individual form element.
+ *
+ * Combine multiple values into a table with drag-n-drop reordering.
+ * TODO : convert to a template.
+ */
+function theme_field_multiple_value_form($element) {
+  $output = '';
+
+  if ($element['#multiple'] > 1 || $element['#multiple'] == FIELD_CARDINALITY_UNLIMITED) {
+    $table_id = $element['#field_name'] .'_values';
+    $order_class = $element['#field_name'] .'-delta-order';
+    $required = !empty($element['#required']) ? '<span class="form-required" title="'. t('This field is required.') .'">*</span>' : '';
+
+    $header = array(
+      array(
+        'data' => t('!title: !required', array('!title' => $element['#title'], '!required' => $required)),
+        'colspan' => 2
+      ),
+      t('Order'),
+    );
+    $rows = array();
+
+    // Sort items according to '_weight' (needed when the form comes back after
+    // preview or failed validation)
+    $items = array();
+    foreach (element_children($element) as $key) {
+      if ($key !== $element['#field_name'] .'_add_more') {
+        $items[] = &$element[$key];
+      }
+    }
+    usort($items, '_field_sort_items_value_helper');
+
+    // Add the items as table rows.
+    foreach ($items as $key => $item) {
+      $item['_weight']['#attributes']['class'] = $order_class;
+      $delta_element = drupal_render($item['_weight']);
+      $cells = array(
+        array('data' => '', 'class' => 'field-multiple-drag'),
+        drupal_render($item),
+        array('data' => $delta_element, 'class' => 'delta-order'),
+      );
+      $rows[] = array(
+        'data' => $cells,
+        'class' => 'draggable',
+      );
+    }
+
+    $output .= theme('table', $header, $rows, array('id' => $table_id, 'class' => 'field-multiple-table'));
+    $output .= $element['#description'] ? '<div class="description">'. $element['#description'] .'</div>' : '';
+    $output .= drupal_render($element[$element['#field_name'] .'_add_more']);
+
+    drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class);
+  }
+  else {
+    foreach (element_children($element) as $key) {
+      $output .= drupal_render($element[$key]);
+    }
+  }
+
+  return $output;
+}
+
+/**
+ * Submit handler to add more choices to a field form. This handler is used when
+ * JavaScript is not available. It makes changes to the form state and the
+ * entire form is rebuilt during the page reload.
+ */
+function field_add_more_submit($form, &$form_state) {
+  // Set the form to rebuild and run submit handlers.
+  node_form_submit_build_node($form, $form_state);
+  $field_name = $form_state['clicked_button']['#field_name'];
+
+  // Make the changes we want to the form state.
+  if ($form_state['values'][$field_name][$field_name .'_add_more']) {
+    $form_state['item_count'][$field_name] = count($form_state['values'][$field_name]);
+  }
+}
+
+/**
+ * Menu callback for AHAH addition of new empty widgets.
+ */
+function field_add_more_js($bundle_name, $field_name) {
+  // Arguments are coming from the url, so we translate back dashes.
+  $field_name = str_replace('-', '_', $field_name);
+
+  $invalid = FALSE;
+  if (empty($_POST['form_build_id'])) {
+    // Invalid request.
+    $invalid = TRUE;
+  }
+
+  // Retrieve the cached form.
+  $form_state = array('submitted' => FALSE);
+  $form_build_id = $_POST['form_build_id'];
+  $form = form_get_cache($form_build_id, $form_state);
+  if (!$form) {
+    // Invalid form_build_id.
+    $invalid = TRUE;
+  }
+
+  // Retrieve field information.
+  $field = $form['#fields'][$field_name]['field'];
+  $instance = $form['#fields'][$field_name]['instance'];
+  $form_path = $form['#fields'][$field_name]['form_path'];
+  // TODO D7 : abstract $_POST[$field_name] path too ?
+  if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED) {
+    // Ivnalid
+    $invalid = TRUE;
+  }
+
+  if ($invalid) {
+    drupal_json(array('data' => ''));
+    exit;
+  }
+
+  // We don't simply return a new empty widget row to append to existing ones,
+  // because:
+  // - ahah.js won't simply let us add a new row to a table
+  // - attaching the 'draggable' behavior won't be easy
+  // So we resort to rebuilding the whole table of widgets including the
+  // existing ones, which makes us jump through a few hoops.
+
+  // The form that we get from the cache is unbuilt. We need to build it so
+  // that _value callbacks can be executed and $form_state['values'] populated.
+  // We only want to affect $form_state['values'], not the $form itself
+  // (built forms aren't supposed to enter the cache) nor the rest of
+  // $form_state, so we use copies of $form and $form_state.
+  $form_copy = $form;
+  $form_state_copy = $form_state;
+  $form_copy['#post'] = array();
+  form_builder($_POST['form_id'], $form_copy, $form_state_copy);
+  // Just grab the data we need.
+  $form_state['values'] = $form_state_copy['values'];
+  // Reset cached ids, so that they don't affect the actual form we output.
+  form_clean_id(NULL, TRUE);
+
+  // Sort the $form_state['values'] we just built *and* the incoming $_POST data
+  // according to d-n-d reordering.
+  unset($form_state['values'][$field_name][$field['field_name'] .'_add_more']);
+  foreach ($_POST[$field_name] as $delta => $item) {
+    $form_state['values'][$field_name][$delta]['_weight'] = $item['_weight'];
+  }
+  $form_state['values'][$field_name] = _field_sort_items($field, $form_state['values'][$field_name]);
+  $_POST[$field_name]                = _field_sort_items($field, $_POST[$field_name]);
+
+  // Build our new form element for the whole field, asking for one more element.
+  $form_state['item_count'] = array($field_name => count($_POST[$field_name]) + 1);
+  $items = $form_state['values'][$field_name];
+  $form_element = field_field_form(NULL, NULL, $field, $instance, $items, $form, $form_state);
+  // Let other modules alter it.
+  drupal_alter('form', $form_element, array(), 'field_add_more_js');
+
+  // Add the new element at the right location in the (original, unbuilt) form.
+  $target = &$form;
+  foreach ($form_path as $key) {
+    $target = &$target[$key];
+  }
+  $target = $form_element[$field_name];
+
+  // Save the new definition of the form.
+  $form_state['values'] = array();
+  form_set_cache($form_build_id, $form, $form_state);
+
+  // Build the new form against the incoming $_POST values so that we can
+  // render the new element.
+  $delta = max(array_keys($_POST[$field_name])) + 1;
+  $_POST[$field_name][$delta]['_weight'] = $delta;
+  $form_state = array('submitted' => FALSE);
+  $form += array(
+    '#post' => $_POST,
+    '#programmed' => FALSE,
+  );
+  $form = form_builder($_POST['form_id'], $form, $form_state);
+
+  // Render the new output.
+  // We get fetch the form element from the built $form.
+  $field_form = $form;
+  foreach ($form_path as $key) {
+    $field_form = $field_form[$key];
+  }
+  // We add a div around the new field to receive the ahah effect.
+  $field_form[$delta]['#prefix'] = '<div class="ahah-new-field">'. (isset($field_form[$delta]['#prefix']) ? $field_form[$delta]['#prefix'] : '');
+  $field_form[$delta]['#suffix'] = (isset($field_form[$delta]['#suffix']) ? $field_form[$delta]['#suffix'] : '') .'</div>';
+
+  // If a newly inserted widget contains AHAH behaviors, they normally won't
+  // work because AHAH doesn't know about those - it just attaches to the exact
+  // form elements that were initially specified in the Drupal.settings object.
+  // The new ones didn't exist then, so we need to update Drupal.settings
+  // by ourselves in order to let AHAH know about those new form elements.
+  $javascript = drupal_add_js(NULL, NULL);
+  $output_js = isset($javascript['setting']) ? '<script type="text/javascript">jQuery.extend(Drupal.settings, '. drupal_to_js(call_user_func_array('array_merge_recursive', $javascript['setting'])) .');</script>' : '';
+
+  $output = theme('status_messages') . drupal_render($field_form) . $output_js;
+  drupal_json(array('status' => TRUE, 'data' => $output));
+  exit;
+}

=== added file 'modules/field/field.test'
--- modules/field/field.test	1970-01-01 00:00:00 +0000
+++ modules/field/field.test	2009-01-20 19:39:55 +0000
@@ -0,0 +1,979 @@
+<?php
+// $Id$
+
+// TODO : use drupalCreateField() / drupalCreateFieldInstance() all over ?
+
+class FieldAttachTestCase extends DrupalWebTestCase {
+  function getInfo() {
+    return array(
+      'name'  => t('Field Attach tests'),
+      'description'  => t("Test Field Attach API functions."),
+      'group' => t('Field')
+    );
+  }
+
+  function setUp() {
+    parent::setUp('field_sql_storage', 'field', 'field_test');
+
+    $this->field_name = strtolower($this->randomName().'_field_name');
+    $this->table = _field_sql_storage_tablename($this->field_name);
+    $this->revision_table = _field_sql_storage_revision_tablename($this->field_name);
+    $this->field = array('field_name' => $this->field_name, 'type' => 'test_field', 'cardinality' => 4);
+    field_create_field($this->field);
+    $this->instance = array(
+      'field_name' => $this->field_name,
+      'bundle' => 'test_bundle',
+      'label' => $this->randomName().'_label',
+      'description' => $this->randomName().'_description',
+      'weight' => mt_rand(0, 127),
+      'settings' => array(
+        'test_instance_setting' => $this->randomName(),
+      ),
+      'widget' => array(
+        'type' => 'test_field_widget',
+        'label' => 'Test Field',
+        'settings' => array(
+          'test_widget_setting' => $this->randomName(),
+        )
+      )
+    );
+    field_create_instance($this->instance);
+  }
+
+  function testFieldAttachLoad() {
+    $entity_type = 'field_test_entity';
+    $eid = 0;
+
+    $columns = array('entity', 'entity_id', 'revision_id', 'delta', $this->field_name .'_value');
+
+    // Insert data for four revisions to the field revisions table
+    $query = db_insert($this->revision_table)->fields($columns);
+    for ($evid = 0; $evid < 4; ++$evid) {
+      $values[$evid] = array();
+      // Note: we insert one extra value ('<=' instead of '<').
+      for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) {
+        $value = mt_rand(0, 127);
+        $values[$evid][] = $value;
+        $query->values(array($entity_type, $eid, $evid, $delta, $value));
+      }
+    }
+    $query->execute();
+
+    // Insert data for the "most current revision" into the field table
+    $query = db_insert($this->table)->fields($columns);
+    foreach ($values[0] as $delta => $value) {
+      $query->values(array($entity_type, $eid, 0, $delta, $value));
+    }
+    $query->execute();
+
+    // Load the "most current revision"
+    $entity = field_test_entity_get_entity($eid, 0, $this->instance['bundle']);
+    field_attach_load($entity_type, array($eid => $entity));
+    foreach ($values[0] as $delta => $value) {
+      if ($delta < $this->field['cardinality']) {
+        $this->assertEqual($entity->{$this->field_name}[$delta]['value'], $value, "Value $delta is loaded correctly for current revision");
+      }
+      else {
+        $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}), "No extraneous value gets loaded for current revision.");
+      }
+    }
+
+    // Load every revision
+    for ($evid = 0; $evid < 4; ++$evid) {
+      $entity = field_test_entity_get_entity($eid, $evid, $this->instance['bundle']);
+      field_attach_load_revision($entity_type, array($eid => $entity));
+      foreach ($values[$evid] as $delta => $value) {
+        if ($delta < $this->field['cardinality']) {
+          $this->assertEqual($entity->{$this->field_name}[$delta]['value'], $value, "Value $delta for revision $evid is loaded correctly");
+        }
+        else {
+          $this->assertFalse(array_key_exists($delta, $entity->{$this->field_name}), "No extraneous value gets loaded for revision $evid.");
+        }
+      }
+    }
+  }
+
+  function testFieldAttachLoadMultiple() {
+    // TODO : test the 'multiple' aspect of load:
+    // define 2 bundles, 3 fields
+    // bundle1 gets instances of field1, field2
+    // bundle2 gets instances of field1, field3
+    // load 2 entities (one for each bundle) in a single load
+    // check that everything gets loaded ok.
+  }
+
+  function testFieldAttachInsertAndUpdate() {
+    $entity_type = 'field_test_entity';
+    $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']);
+
+    // Test insert.
+    $values = array();
+    // Note: we try to insert one extra value ('<=' instead of '<').
+    // TODO : test empty values filtering and "compression" (store consecutive deltas).
+    for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) {
+      $values[$delta]['value'] = mt_rand(0, 127);
+    }
+    $entity->{$this->field_name} = $rev_values[0] = $values;
+    field_attach_insert($entity_type, $entity);
+
+    $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
+    foreach ($values as $delta => $value) {
+      if ($delta < $this->field['cardinality']) {
+        $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Value $delta is inserted correctly"));
+      }
+      else {
+        $this->assertFalse(array_key_exists($delta, $rows), "No extraneous value gets inserted.");
+      }
+    }
+
+    // Test update.
+    $entity = field_test_entity_get_entity(0, 1, $this->instance['bundle']);
+    $values = array();
+    // Note: we try to update one extra value ('<=' instead of '<').
+    for ($delta = 0; $delta <= $this->field['cardinality']; $delta++) {
+      $values[$delta]['value'] = mt_rand(0, 127);
+    }
+    $entity->{$this->field_name} = $rev_values[1] = $values;
+    field_attach_update($entity_type, $entity);
+    $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
+    foreach ($values as $delta => $value) {
+      if ($delta < $this->field['cardinality']) {
+        $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Value $delta is updated correctly"));
+      }
+      else {
+        $this->assertFalse(array_key_exists($delta, $rows), "No extraneous value gets updated.");
+      }
+    }
+
+    // Check that data for both revisions are in the revision table.
+    // We make sure each value is stored correctly, then unset it.
+    // When an entire revision's values are unset (remembering that we
+    // put one extra value in $values per revision), unset the entire
+    // revision.  Then, if $rev_values is empty at the end, all
+    // revision data was found.
+    $results = db_select($this->revision_table, 't')->fields('t')->execute();
+    foreach ($results as $row) {
+      $this->assertEqual($row->{$this->field_name . '_value'}, $rev_values[$row->revision_id][$row->delta]['value'], "Value {$row->delta} for revision {$row->revision_id} stored correctly");
+      unset($rev_values[$row->revision_id][$row->delta]);
+      if (count($rev_values[$row->revision_id]) == 1) {
+        unset($rev_values[$row->revision_id]);
+      }
+    }
+    $this->assertTrue(empty($rev_values), "All values for all revisions are stored in revision table {$this->revision_table}");
+
+    // Check that update leaves the field data untouched if $object has no
+    // $field_name key.
+    unset($entity->{$this->field_name});
+    field_attach_update($entity_type, $entity);
+    $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
+    foreach ($values as $delta => $value) {
+      if ($delta < $this->field['cardinality']) {
+        $this->assertEqual($rows[$delta][$this->field_name . '_value'], $value['value'], t("Update with no field_name entry leaves value $delta untouched"));
+      }
+    }
+
+    // Check that update with an empty $object->$field_name empties the field.
+    $entity->{$this->field_name} = NULL;
+    field_attach_update($entity_type, $entity);
+    $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
+    $this->assertEqual(count($rows), 0, t("Update with an empty field_name entry empties the field."));
+  }
+
+  // Test insert and update with missing or invalid fields.  For the
+  // most part, these tests pass by not crashing or causing exceptions.
+  function testFieldAttachSaveMissingData() {
+    $entity_type = 'field_test_entity';
+    $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']);
+
+    // Insert: Field is missing
+    field_attach_insert($entity_type, $entity);
+    $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}"));
+    $this->assertEqual($count, 0, 'Missing field results in no inserts');
+
+    // Insert: Field is NULL
+    $entity->{$this->field_name} = NULL;
+    field_attach_insert($entity_type, $entity);
+    $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}"));
+    $this->assertEqual($count, 0, 'NULL field results in no inserts');
+
+    // Add some real data
+    $entity->{$this->field_name} = array(0 => array('value' => 1));
+    field_attach_insert($entity_type, $entity);
+    $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}"));
+    $this->assertEqual($count, 1, 'Field data saved');
+    
+    // Update: Field is missing.  Data should survive.
+    unset($entity->{$this->field_name});
+    field_attach_update($entity_type, $entity);
+    $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}"));
+    $this->assertEqual($count, 1, 'Missing field leaves data in table');
+
+    // Update: Field is NULL  Data should be wiped.
+    $entity->{$this->field_name} = NULL;
+    field_attach_update($entity_type, $entity);
+    $count = db_result(db_query("SELECT COUNT(*) FROM {{$this->table}}"));
+    $this->assertEqual($count, 0, 'NULL field leaves no data in table');
+  }
+
+  function testFieldAttachViewAndPreprocess() {
+    $entity_type = 'field_test_entity';
+    $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']);
+
+    // Populate values to be displayed.
+    $values = array();
+    for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
+      $values[$delta]['value'] = mt_rand(0, 127);
+    }
+    $entity->{$this->field_name} = $values;
+
+    // Simple formatter, label displayed.
+    $formatter_setting = $this->randomName();
+    $this->instance['display'] = array(
+      'full' => array(
+        'label' => 'above',
+        'type' => 'field_test_default',
+        'settings' => array(
+          'test_formatter_setting' => $formatter_setting,
+        )
+      ),
+    );
+    field_update_instance($this->instance);
+    $entity->content = field_attach_view($entity_type, $entity);
+    $output = drupal_render($entity->content);
+    $variables = field_attach_preprocess($entity_type, $entity);
+    $variable = $this->instance['field_name'] . '_rendered';
+    $this->assertTrue(isset($variables[$variable]), "Variable $variable is available in templates.");
+    $this->content = $output;
+    $this->assertRaw($this->instance['label'], "Label is displayed.");
+    $this->content = $variables[$variable];
+    $this->assertRaw($this->instance['label'], "Label is displayed (template variable).");
+    foreach ($values as $delta => $value) {
+      $this->content = $output;
+      $this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied.");
+      $this->content = $variables[$variable];
+      $this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied (template variable).");
+    }
+
+    // Label hidden.
+    $this->instance['display']['full']['label'] = 'hidden';
+    field_update_instance($this->instance);
+    $entity->content = field_attach_view($entity_type, $entity);
+    $output = drupal_render($entity->content);
+    $variables = field_attach_preprocess($entity_type, $entity);
+    $this->content = $output;
+    $this->assertNoRaw($this->instance['label'], "Hidden label: label is not displayed.");
+    $this->content = $variables[$variable];
+    $this->assertNoRaw($this->instance['label'], "Hidden label: label is not displayed (template variable).");
+
+    // Field hidden.
+    $this->instance['display'] = array(
+      'full' => array(
+        'label' => 'above',
+        'type' => 'hidden',
+
+      ),
+    );
+    field_update_instance($this->instance);
+    $entity->content = field_attach_view($entity_type, $entity);
+    $output = drupal_render($entity->content);
+    $variables = field_attach_preprocess($entity_type, $entity);
+    $this->assertTrue(isset($variables[$variable]), "Hidden field: variable $variable is available in templates.");
+    $this->content = $output;
+    $this->assertNoRaw($this->instance['label'], "Hidden field: label is not displayed.");
+    foreach ($values as $delta => $value) {
+      $this->assertNoRaw($value['value'], "Hidden field: value $delta is not displayed.");
+    }
+
+    // Multiple formatter.
+    $formatter_setting = $this->randomName();
+    $this->instance['display'] = array(
+      'full' => array(
+        'label' => 'above',
+        'type' => 'field_test_multiple',
+        'settings' => array(
+          'test_formatter_setting_multiple' => $formatter_setting,
+        )
+      ),
+    );
+    field_update_instance($this->instance);
+    $entity->content = field_attach_view($entity_type, $entity);
+    $output = drupal_render($entity->content);
+    $variables = field_attach_preprocess($entity_type, $entity);
+    $display = $formatter_setting;
+    foreach ($values as $delta => $value) {
+      $display .= "|$delta:{$value['value']}";
+    }
+    $this->content = $output;
+    $this->assertRaw($display, "Multiple formatter: all values are displayed, formatter settings are applied.");
+    $this->content = $variables[$variable];
+    $this->assertRaw($display, "Multiple formatter: all values are displayed, formatter settings are applied (template variable).");
+
+    // TODO:
+    // - check that the 'exclude' option works (if we keep it in core)
+    // - check display order with several fields
+  }
+
+  function testFieldAttachDelete() {
+    $entity_type = 'field_test_entity';
+    $rev[0] = field_test_entity_get_entity(0, 0, $this->instance['bundle']);
+
+    // Create revision 0
+    $values = array();
+    for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
+      $values[$delta]['value'] = mt_rand(0, 127);
+    }
+    $rev[0]->{$this->field_name} = $values;
+    field_attach_insert($entity_type, $rev[0]);
+
+    // Create revision 1
+    $rev[1] = field_test_entity_get_entity(0, 1, $this->instance['bundle']);
+    $rev[1]->{$this->field_name} = $values;
+    field_attach_update($entity_type, $rev[1]);
+
+    // Create revision 2
+    $rev[2] = field_test_entity_get_entity(0, 2, $this->instance['bundle']);
+    $rev[2]->{$this->field_name} = $values;
+    field_attach_update($entity_type, $rev[2]);
+
+    // Confirm each revision loads
+    foreach (array_keys($rev) as $vid) {
+      $read = field_test_entity_get_entity(0, $vid, $this->instance['bundle']);
+      field_attach_load_revision($entity_type, array(0 => $read));
+      $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values.");
+    }
+
+    // Delete revision 1, confirm the other two still load.
+    field_attach_delete_revision($entity_type, $rev[1]);
+    foreach (array(0, 2) as $vid) {
+      $read = field_test_entity_get_entity(0, $vid, $this->instance['bundle']);
+      field_attach_load_revision($entity_type, array(0 => $read));
+      $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values.");
+    }
+
+    // Confirm the current revision still loads
+    $read = field_test_entity_get_entity(0, 2, $this->instance['bundle']);
+    field_attach_load($entity_type, array(0 => $read));
+    $this->assertEqual(count($read->{$this->field_name}), $this->field['cardinality'], "The test object current revision has {$this->field['cardinality']} values.");
+
+    // Delete all field data, confirm nothing loads
+    field_attach_delete($entity_type, $rev[2]);
+    foreach (array(0, 1, 2) as $vid) {
+      $read = field_test_entity_get_entity(0, $vid, $this->instance['bundle']);
+      field_attach_load_revision($entity_type, array(0 => $read));
+      $this->assertFalse(isset($read->{$this->field_name}), "The test object revision $vid is deleted.");
+    }
+    $read = field_test_entity_get_entity(0, 2, $this->instance['bundle']);
+    field_attach_load($entity_type, array(0 => $read));
+      $this->assertFalse(isset($read->{$this->field_name}), "The test object current revision is deleted.");
+  }
+
+  function testFieldAttachCreateRenameBundle() {
+    // Create a new bundle. This has to be initiated by the module so that its
+    // hook_fieldable_info() is consistent.
+    $new_bundle = 'test_bundle_' . strtolower($this->randomName());
+    field_test_create_bundle($new_bundle, $this->randomName());
+
+    // Add an instance to that bundle.
+    $this->instance['bundle'] = $new_bundle;
+    field_create_instance($this->instance);
+
+    // Save an object with data in the field.
+    $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']);
+    $values = array();
+    for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
+      $values[$delta]['value'] = mt_rand(0, 127);
+    }
+    $entity->{$this->field_name} = $values;
+    $entity_type = 'field_test_entity';
+    field_attach_insert($entity_type, $entity);
+
+    // Verify the field data is present on load.
+    $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']);
+    field_attach_load($entity_type, array(0 => $entity));
+    $this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], "Data are retrieved for the new bundle");
+
+    // Rename the bundle. This has to be initiated by the module so that its
+    // hook_fieldable_info() is consistent.
+    $new_bundle = 'test_bundle_' . strtolower($this->randomName());
+    field_test_rename_bundle($this->instance['bundle'], $new_bundle);
+
+    // Check that the instance definition has been updated.
+    $this->instance = field_info_instance($this->field_name, $new_bundle);
+    $this->assertIdentical($this->instance['bundle'], $new_bundle, "Bundle name has been updated in the instance.");
+
+    // Verify the field data is present on load.
+    $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']);
+    field_attach_load($entity_type, array(0 => $entity));
+    $this->assertEqual(count($entity->{$this->field_name}), $this->field['cardinality'], "Bundle name has been updated in the field storage");
+  }
+
+  function testFieldAttachDeleteBundle() {
+    // Create a new bundle. This has to be initiated by the module so that its
+    // hook_fieldable_info() is consistent.
+    $new_bundle = 'test_bundle_' . strtolower($this->randomName());
+    field_test_create_bundle($new_bundle, $this->randomName());
+
+    // Add an instance to that bundle.
+    $this->instance['bundle'] = $new_bundle;
+    field_create_instance($this->instance);
+
+    // Create a second field for the test bundle
+    $field_name = strtolower($this->randomName().'_field_name');
+    $table = _field_sql_storage_tablename($field_name);
+    $revision_table = _field_sql_storage_revision_tablename($field_name);
+    $field = array('field_name' => $field_name, 'type' => 'test_field', 'cardinality' => 1);
+    field_create_field($field);
+    $instance = array(
+      'field_name' => $field_name,
+      'bundle' => $this->instance['bundle'],
+      'label' => $this->randomName().'_label',
+      'description' => $this->randomName().'_description',
+      'weight' => mt_rand(0, 127),
+      // test_field has no instance settings
+      'widget' => array(
+        'type' => 'test_field_widget',
+        'settings' => array(
+          'size' => mt_rand(0, 255))));
+    field_create_instance($instance);
+
+    // Save an object with data for both fields
+    $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']);
+    $values = array();
+    for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
+      $values[$delta]['value'] = mt_rand(0, 127);
+    }
+    $entity->{$this->field_name} = $values;
+    $entity->{$field_name} = array(0 => array('value' => 99));
+    $entity_type = 'field_test_entity';
+    field_attach_insert($entity_type, $entity);
+
+    // Verify the fields are present on load
+    $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']);
+    field_attach_load($entity_type, array(0 => $entity));
+    $this->assertEqual(count($entity->{$this->field_name}), 4, "First field got loaded");
+    $this->assertEqual(count($entity->{$field_name}), 1, "Second field got loaded");
+
+    // Delete the bundle. This has to be initiated by the module so that its
+    // hook_fieldable_info() is consistent.
+    field_test_delete_bundle($this->instance['bundle']);
+
+    // Verify no data gets loaded
+    $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']);
+    field_attach_load($entity_type, array(0 => $entity));
+    $this->assertFalse(isset($entity->{$this->field_name}), "No data for first field");
+    $this->assertFalse(isset($entity->{$field_name}), "No data for second field");
+
+    // Verify that the instances are gone
+    $this->assertFalse(field_read_instance($this->field_name, $this->instance['bundle']), "First field is deleted");
+    $this->assertFalse(field_read_instance($field_name, $instance['bundle']), "Second field is deleted");
+  }
+
+  function testFieldAttachCache() {
+    // Create a revision
+    $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']);
+    $values = array();
+    for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
+      $values[$delta]['value'] = mt_rand(0, 127);
+    }
+    $entity->{$this->field_name} = $values;
+
+    $noncached_type = 'field_test_entity';
+    $cached_type = 'field_test_cacheable_entity';
+
+    // Non-cached type:
+    $cid = "field:$noncached_type:0:0";
+
+    // Confirm no initial cache entry
+    $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no initial cache entry');
+
+    // Save, and confirm no cache entry
+    field_attach_insert($noncached_type, $entity);
+    $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no cache entry on save');
+
+    // Load, and confirm no cache entry
+    field_attach_load($noncached_type, array(0 => $entity));
+    $this->assertFalse(cache_get($cid, 'cache_field'), 'Non-cached: no cache entry on load');
+
+    // Cached type:
+    $cid = "field:$cached_type:0:0";
+
+    // Confirm no initial cache entry
+    $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no initial cache entry');
+
+    // Save, and confirm no cache entry
+    field_attach_insert($cached_type, $entity);
+    $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on save');
+
+    // Load, and confirm cache entry
+    field_attach_load($cached_type, array(0 => $entity));
+    $cache = cache_get($cid, 'cache_field');
+    $this->assertEqual($cache->data[$this->field_name], $values, 'Cached: correct cache entry on load');
+
+    // Delete, and confirm no cache entry
+    field_attach_delete($cached_type, $entity);
+    $this->assertFalse(cache_get($cid, 'cache_field'), 'Cached: no cache entry on save');
+  }
+
+  // Verify that field_attach_validate() invokes the correct
+  // hook_field_validate.  NOTE: This tests the FAPI-connected
+  // behavior of hook_field_validate.  As discussed at
+  // http://groups.drupal.org/node/18019, field validation will
+  // eventually be disconnected from FAPI, at which point this
+  // function will have to be rewritten.
+  function testFieldAttachValidate() {
+    $entity_type = 'field_test_entity';
+    $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']);
+
+    // Set up values to generate errors
+    $values = array();
+    for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
+      $values[$delta]['value'] = mt_rand(1, 127);
+      $values[$delta]['_error_element'] = 'field_error_'.$delta;
+    }
+    // Arrange for item 1 not to generate an error
+    $values[1]['value'] = 1;
+    $entity->{$this->field_name} = $values;
+
+    field_attach_validate($entity_type, $entity, array());
+    
+    $errors = form_get_errors();
+    foreach ($values as $delta => $value) {
+      if ($value['value'] != 1) {
+        $this->assertTrue(isset($errors[$value['_error_element']]), "Error is set on {$value['_error_element']}: {$errors[$value['_error_element']]}");
+        unset($errors[$value['_error_element']]);
+      }
+      else {
+        $this->assertFalse(isset($errors[$value['_error_element']]), "Error is not set on {$value['_error_element']}");
+      }
+    }
+    $this->assertEqual(count($errors), 0, 'No extraneous form errors set');
+  }
+
+  // Validate that FAPI elements are generated.  This could be much
+  // more thorough, but it does verify that the correct widgets show up.
+  function testFieldAttachForm() {
+    $entity_type = 'field_test_entity';
+    $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']);
+
+    $form = $form_state = array();
+    field_attach_form($entity_type, $entity, $form, $form_state);
+
+    $this->assertEqual($form[$this->field_name]['#title'], $this->instance['label'], "Form title is {$this->instance['label']}");
+    for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
+      // field_test_widget uses 'textfield'
+      $this->assertEqual($form[$this->field_name][$delta]['#type'], 'textfield', "Form delta $delta widget is textfield");
+    }
+  }
+
+  function testFieldAttachPresave() {
+    $entity_type = 'field_test_entity';
+    $entity = field_test_entity_get_entity(0, 0, $this->instance['bundle']);
+    
+    $values = array();
+    for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
+      $values[$delta]['value'] = mt_rand(1, 127);
+    }
+    // field_test fields are empty if empty()
+    $values[1]['value'] = 0;
+    $entity->{$this->field_name} = $values;
+
+    field_attach_presave($entity_type, $entity);
+
+    unset($values[1]);
+    $values = array_merge($values);
+    $this->assertIdentical($entity->{$this->field_name}, $values, 'Presave filters empty values');
+  }
+}
+
+class FieldInfoTestCase extends DrupalWebTestCase {
+
+  function getInfo() {
+    return array(
+      'name'  => t('Field info tests'),
+      'description'  => t("Get information about existing fields, instances and bundles."),
+      'group' => t('Field')
+    );
+  }
+
+  function setUp() {
+    parent::setUp('field_sql_storage', 'field', 'field_test');
+  }
+
+  function testFieldInfo() {
+    // Test that field_test module's fields, widgets, and formatters show up.
+    $field_test_info = field_test_field_info();
+    $formatter_info = field_test_field_formatter_info();
+    $widget_info = field_test_field_widget_info();
+
+    $info = field_info_field_types();
+    foreach ($field_test_info as $t_key => $field_type) {
+      foreach ($field_type as $key => $val) {
+        $this->assertEqual($info[$t_key][$key], $val, t("Field type $t_key key $key is $val"));
+      }
+      $this->assertEqual($info[$t_key]['module'], 'field_test',  t("Field type field_test module appears"));
+    }
+
+    $info = field_info_formatter_types();
+    foreach ($formatter_info as $f_key => $formatter) {
+      foreach ($formatter as $key => $val) {
+        $this->assertEqual($info[$f_key][$key], $val, t("Formatter type $f_key key $key is $val"));
+      }
+      $this->assertEqual($info[$f_key]['module'], 'field_test',  t("Formatter type field_test module appears"));
+    }
+
+    $info = field_info_widget_types();
+    foreach ($widget_info as $w_key => $widget) {
+      foreach ($widget as $key => $val) {
+        $this->assertEqual($info[$w_key][$key], $val, t("Widget type $w_key key $key is $val"));
+      }
+      $this->assertEqual($info[$w_key]['module'], 'field_test',  t("Widget type field_test module appears"));
+    }
+
+    // Verify that no fields or instances exist
+    $fields = field_info_fields();
+    $instances = field_info_instances(FIELD_TEST_BUNDLE);
+    $this->assertTrue(empty($fields), t('With no fields, info fields is empty.'));
+    $this->assertTrue(empty($instances), t('With no instances, info bundles is empty.'));
+
+    // Create a field, verify it shows up.
+    $field = array(
+      'field_name' => strtolower($this->randomName()),
+      'type' => 'test_field',
+    );
+    field_create_field($field);
+    $fields = field_info_fields();
+    $this->assertEqual(count($fields), 1, t('One field exists'));
+    $this->assertEqual($fields[$field['field_name']]['field_name'], $field['field_name'], t('info fields contains field name'));
+    $this->assertEqual($fields[$field['field_name']]['type'], $field['type'], t('info fields contains field type'));
+    $this->assertEqual($fields[$field['field_name']]['module'], 'field_test', t('info fields contains field module'));
+    $settings = array('test_field_setting' => 'dummy test string');
+    foreach ($settings as $key => $val) {
+      $this->assertEqual($fields[$field['field_name']]['settings'][$key], $val, t("Field setting $key has correct default value $val"));
+    }
+    $this->assertEqual($fields[$field['field_name']]['cardinality'], 1, t('info fields contains cardinality 1'));
+    $this->assertEqual($fields[$field['field_name']]['active'], 1, t('info fields contains active 1'));
+
+    // Create an instance, verify that it shows up
+    $instance = array(
+      'field_name' => $field['field_name'],
+      'bundle' => FIELD_TEST_BUNDLE,
+      'label' => $this->randomName(),
+      'description' => $this->randomName(),
+      'weight' => mt_rand(0, 127),
+      // test_field has no instance settings
+      'widget' => array(
+        'type' => 'test_field_widget',
+        'settings' => array(
+          'test_setting' => 999)));
+    field_create_instance($instance);
+
+    $instances = field_info_instances($instance['bundle']);
+    $this->assertEqual(count($instances), 1, t('One instance shows up in info when attached to a bundle.'));
+    $this->assertTrue($instance < $instances[$instance['field_name']], t('Instance appears in info correctly'));
+  }
+
+  // Test that the field_info settings convenience functions work
+  function testSettingsInfo() {
+    $info = field_test_field_info();
+    foreach ($info as $type => $data) {
+      $this->assertIdentical(field_info_field_settings($type), $data['settings'], "field_info_field_settings returns {$type}'s field settings");
+      $this->assertIdentical(field_info_instance_settings($type), $data['instance_settings'], "field_info_field_settings returns {$type}'s field instance settings");
+    }
+
+    $info = field_test_field_widget_info();
+    foreach ($info as $type => $data) {
+      $this->assertIdentical(field_info_widget_settings($type), $data['settings'], "field_info_widget_settings returns {$type}'s widget settings");
+    }
+
+    $info = field_test_field_formatter_info();
+    foreach ($info as $type => $data) {
+      $this->assertIdentical(field_info_formatter_settings($type), $data['settings'], "field_info_formatter_settings returns {$type}'s formatter settings");
+    }
+  }
+}
+
+class FieldTestCase extends DrupalWebTestCase {
+  function getInfo() {
+    return array(
+      'name'  => t('Field tests'),
+      'description'  => t("Create / read /update a field."),
+      'group' => t('Field')
+    );
+  }
+
+  function setUp() {
+    parent::setUp('field_sql_storage', 'field', 'field_test');
+  }
+
+  // TODO : test creation with
+  // - a full fledged $field structure, check that all the values are there
+  // - a minimal $field structure, check all default values are set
+  // defer actual $field comparison to a helper function, used for the two cases above
+  /**
+   * Test the creation of a field.
+   */
+  function testCreateField() {
+    $field_definition = array(
+      'field_name' => strtolower($this->randomName()),
+      'type' => 'test_field',
+    );
+    field_create_field($field_definition);
+
+    $field = field_read_field($field_definition['field_name']);
+
+    // Ensure that basic properties are preserved.
+    $this->assertEqual($field['field_name'], $field_definition['field_name'], t('The field name is properly saved.'));
+    $this->assertEqual($field['type'], $field_definition['type'], t('The field type is properly saved.'));
+
+    // Ensure that cardinality defaults to 1.
+    $this->assertEqual($field['cardinality'], 1, t('Cardinality defaults to 1.'));
+
+    // Ensure that default settings are present.
+    $info = field_info_field_types($field['type']);
+    $settings = $info['settings'];
+    $this->assertIdentical($settings, $field['settings'] , t('Default field settings have been written.'));
+
+    // Check that a table has been created for the field.
+    $this->assertTrue(db_table_exists('field_data_' . $field_definition['field_name']), t('A table has been created for the field.'));
+
+    // Guarantee that the name is unique.
+    try {
+      field_create_field($field_definition);
+      $this->fail(t('Cannot create two fields with the same name.'));
+    } catch (FieldException $e) {
+      $this->pass(t('Cannot create two fields with the same name.'));
+    }
+
+    // Check that invalid field names are rejected.
+    $field_definition['field_name'] += '_#';
+    try {
+      field_create_field($field_definition);
+      $this->fail(t('Cannot create a field with an invalid name.'));
+    } catch (FieldException $e) {
+      $this->pass(t('Cannot create a field with an invalid name.'));
+    }
+
+    // TODO : other failures
+  }
+
+  function testReadField() {
+
+  }
+
+  /**
+   * Test the deletion of a field.
+   */
+  function testDeleteField() {
+    // TODO: Also test deletion of the data stored in the field ?
+
+    // Create two fields (so we can test that only one is deleted).
+    $this->field = $this->drupalCreateField('test_field', 'test_field_name');
+    $this->another_field = $this->drupalCreateField('test_field', 'another_test_field_name');
+
+    // Create instances for each.
+    $this->instance_definition = array(
+      'field_name' => $this->field['field_name'],
+      'bundle' => FIELD_TEST_BUNDLE,
+      'widget' => array(
+        'type' => 'test_field_widget',
+      ),
+    );
+    field_create_instance($this->instance_definition);
+    $this->another_instance_definition = $this->instance_definition;
+    $this->another_instance_definition['field_name'] = $this->another_field['field_name'];
+    field_create_instance($this->another_instance_definition);
+
+    // Test that the first field is not deleted, and then delete it.
+    $field = field_read_field($this->field['field_name'], array('include_deleted' => TRUE));
+    $this->assertTrue(!empty($field) && empty($field['deleted']), t('A new field is not marked for deletion.'));
+    field_delete_field($this->field['field_name']);
+
+    // Make sure that the field is marked as deleted when it is specifically
+    // loaded.
+    $field = field_read_field($this->field['field_name'], array('include_deleted' => TRUE));
+    $this->assertTrue(!empty($field['deleted']), t('A deleted field is marked for deletion.'));
+
+    // Make sure that this field's instance is marked as deleted when it is
+    // specifically loaded.
+    $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE));
+    $this->assertTrue(!empty($instance['deleted']), t('An instance for a deleted field is marked for deletion.'));
+
+    // Try to load the field normally and make sure it does not show up.
+    $field = field_read_field($this->field['field_name']);
+    $this->assertTrue(empty($field), t('A deleted field is not loaded by default.'));
+
+    // Try to load the instance normally and make sure it does not show up.
+    $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
+    $this->assertTrue(empty($instance), t('An instance for a deleted field is not loaded by default.'));
+
+    // Make sure the other field (and its field instance) are not deleted.
+    $another_field = field_read_field($this->another_field['field_name']);
+    $this->assertTrue(!empty($another_field) && empty($another_field['deleted']), t('A non-deleted field is not marked for deletion.'));
+    $another_instance = field_read_instance($this->another_instance_definition['field_name'], $this->another_instance_definition['bundle']);
+    $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), t('An instance of a non-deleted field is not marked for deletion.'));
+  }
+}
+
+class FieldInstanceTestCase extends DrupalWebTestCase {
+  protected $field;
+
+  function getInfo() {
+    return array(
+      'name'  => t('Field instance tests'),
+      'description'  => t("Create field entities by attaching fields to entities."),
+      'group' => t('Field')
+    );
+  }
+
+  function setUp() {
+    parent::setUp('field_sql_storage', 'field', 'field_test');
+
+    $this->field = $this->drupalCreateField('test_field');
+    $this->instance_definition = array(
+      'field_name' => $this->field['field_name'],
+      'bundle' => FIELD_TEST_BUNDLE,
+    );
+  }
+
+  // TODO : test creation with
+  // - a full fledged $instance structure, check that all the values are there
+  // - a minimal $instance structure, check all default values are set
+  // defer actual $instance comparison to a helper function, used for the two cases above,
+  // and for testUpdateFieldInstance
+  function testCreateFieldInstance() {
+    field_create_instance($this->instance_definition);
+    $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
+    $field_type = field_info_field_types($this->field['type']);
+
+    // Check that default values are set.
+    $this->assertIdentical($instance['required'], FALSE, t('Required defaults to false.'));
+    $this->assertIdentical($instance['label'], $this->instance_definition['field_name'], t('Label defaults to field name.'));
+    $this->assertIdentical($instance['description'], '', t('Description defaults to empty string.'));
+
+    // Check that default instance settings are set.
+    $settings = array('test_instance_setting' => 'dummy test string');
+    $this->assertIdentical($settings, $instance['settings'] , t('Default instance settings have been written.'));
+    // Check that the widget is the default one.
+    $this->assertIdentical($instance['widget']['type'], $field_type['default_widget'], t('Default widget has been written.'));
+    // Check that default widget settings are set.
+    $settings = array('test_widget_setting' => 'dummy test string');
+    $this->assertIdentical($settings, $instance['widget']['settings'] , t('Default widget settings have been written.'));
+    // Check that we have display info for 'full' build_mode.
+    $this->assertTrue(isset($instance['display']['full']), t('Display for "full" build_mode has been written.'));
+    // Check that the formatter is the default one.
+    $this->assertIdentical($instance['display']['full']['type'], $field_type['default_formatter'], t('Default formatter for "full" build_mode has been written.'));
+    // Check that the default formatter settings are set.
+    $info = field_info_formatter_types($instance['display']['full']['type']);
+    $settings = $info['settings'];
+    $this->assertIdentical($settings, $instance['display']['full']['settings'] , t('Default formatter settings for "full" build_mode have been written.'));
+
+    // Guarantee that the field/bundle combination is unique.
+    try {
+      field_create_instance($this->instance_definition);
+      $this->fail(t('Cannot create two instances with the same field / bundle combination.'));
+    }
+    catch (FieldException $e) {
+      $this->pass(t('Cannot create two instances with the same field / bundle combination.'));
+    }
+
+    // Check that the specified field exists.
+    try {
+      $this->instance_definition['field_name'] = $this->randomName();
+      field_create_instance($this->instance_definition);
+      $this->fail(t('Cannot create an instance of a non-existing field.'));
+    }
+    catch (FieldException $e) {
+      $this->pass(t('Cannot create an instance of a non-existing field.'));
+    }
+
+    // TODO: test other failures.
+  }
+
+  function testReadFieldInstance() {
+
+  }
+
+  function testUpdateFieldInstance() {
+    field_create_instance($this->instance_definition);
+    $field_type = field_info_field_types($this->field['type']);
+
+    // Check that basic changes are saved.
+    $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
+    $instance['required'] = !$instance['required'];
+    $instance['weight']++;
+    $instance['label'] = $this->randomName();
+    $instance['description'] = $this->randomName();
+    $instance['settings']['test_instance_setting'] = $this->randomName();
+    $instance['widget']['settings']['test_widget_setting'] =$this->randomName();
+    $instance['display']['full']['settings']['test_formatter_setting'] = $this->randomName();
+    field_update_instance($instance);
+
+    $instance_new = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
+    $this->assertEqual($instance['required'], $instance_new['required'], t('"required" change is saved'));
+    $this->assertEqual($instance['weight'], $instance_new['weight'], t('"weight" change is saved'));
+    $this->assertEqual($instance['label'], $instance_new['label'], t('"label" change is saved'));
+    $this->assertEqual($instance['description'], $instance_new['description'], t('"description" change is saved'));
+    $this->assertEqual($instance['widget']['settings']['test_widget_setting'], $instance_new['widget']['settings']['test_widget_setting'], t('Widget setting change is saved'));
+    $this->assertEqual($instance['display']['full']['settings']['test_formatter_setting'], $instance_new['display']['full']['settings']['test_formatter_setting'], t('Formatter setting change is saved'));
+
+    // Check that changing widget and formatter types updates the default settings.
+    $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
+    $instance['widget']['type'] = 'test_field_widget_multiple';
+    $instance['display']['full']['type'] = 'field_test_multiple';
+    field_update_instance($instance);
+
+    $instance_new = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
+    $this->assertEqual($instance['widget']['type'], $instance_new['widget']['type'] , t('Widget type change is saved.'));
+    $settings = (array)module_invoke($instance_new['widget']['module'], 'widget_settings', $instance_new['widget']['type']);
+    $this->assertIdentical($settings, array_intersect_key($instance_new['widget']['settings'], $settings) , t('Widget type change updates default settings.'));
+    $this->assertEqual($instance['display']['full']['type'], $instance_new['display']['full']['type'] , t('Formatter type change is saved.'));
+    $info = field_info_formatter_types($instance_new['display']['full']['type']);
+    $settings = $info['settings'];
+    $this->assertIdentical($settings, array_intersect_key($instance_new['display']['full']['settings'], $settings) , t('Changing formatter type updates default settings.'));
+
+    // Check that adding a new build mode is saved and gets default settings.
+    $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
+    $instance['display']['teaser'] = array();
+    field_update_instance($instance);
+
+    $instance_new = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
+    $this->assertTrue(isset($instance_new['display']['teaser']), t('Display for the new build_mode has been written.'));
+    $this->assertIdentical($instance_new['display']['teaser']['type'], $field_type['default_formatter'], t('Default formatter for the new build_mode has been written.'));
+    $info = field_info_formatter_types($instance_new['display']['teaser']['type']);
+    $settings = $info['settings'];
+    $this->assertIdentical($settings, $instance_new['display']['teaser']['settings'] , t('Default formatter settings for the new build_mode have been written.'));
+
+    // TODO: test failures.
+  }
+
+  function testDeleteFieldInstance() {
+    // TODO: Test deletion of the data stored in the field also.
+    // Need to check that data for a 'deleted' field / instance doesn't get loaded
+    // Need to check data marked deleted is cleaned on cron (not implemented yet...)
+
+    // Create two instances for the same field so we can test that only one
+    // is deleted.
+    field_create_instance($this->instance_definition);
+    $this->another_instance_definition = $this->instance_definition;
+    $this->another_instance_definition['bundle'] .= '_another_bundle';
+    field_create_instance($this->another_instance_definition);
+
+    // Test that the first instance is not deleted, and then delete it.
+    $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE));
+    $this->assertTrue(!empty($instance) && empty($instance['deleted']), t('A new field instance is not marked for deletion.'));
+    field_delete_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
+
+    // Make sure the instance is marked as deleted when the instance is
+    // specifically loaded.
+    $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE));
+    $this->assertTrue(!empty($instance['deleted']), t('A deleted field instance is marked for deletion.'));
+
+    // Try to load the instance normally and make sure it does not show up.
+    $instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
+    $this->assertTrue(empty($instance), t('A deleted field instance is not loaded by default.'));
+
+    // Make sure the other field instance is not deleted.
+    $another_instance = field_read_instance($this->another_instance_definition['field_name'], $this->another_instance_definition['bundle']);
+    $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), t('A non-deleted field instance is not marked for deletion.'));
+  }
+}

=== added directory 'modules/field/modules'
=== added directory 'modules/field/modules/field_sql_storage'
=== added file 'modules/field/modules/field_sql_storage/field_sql_storage.info'
--- modules/field/modules/field_sql_storage/field_sql_storage.info	1970-01-01 00:00:00 +0000
+++ modules/field/modules/field_sql_storage/field_sql_storage.info	2009-01-20 19:39:55 +0000
@@ -0,0 +1,8 @@
+; $Id$
+name = Field SQL storage
+description = Stores field data in an SQL database.
+package = Core - fields
+core = 7.x
+files[] = field_sql_storage.module
+files[] = field_sql_storage.install
+required = TRUE
\ No newline at end of file

=== added file 'modules/field/modules/field_sql_storage/field_sql_storage.install'
--- modules/field/modules/field_sql_storage/field_sql_storage.install	1970-01-01 00:00:00 +0000
+++ modules/field/modules/field_sql_storage/field_sql_storage.install	2009-01-20 19:39:55 +0000
@@ -0,0 +1,32 @@
+<?php
+// $Id$
+
+/**
+ * Implementation of hook_install().
+ */
+function field_sql_storage_install() {
+  drupal_install_schema('field_sql_storage');
+}
+
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function field_sql_storage_uninstall() {
+  drupal_uninstall_schema('field_sql_storage');
+}
+
+
+function field_sql_storage_schema() {
+  $schema = array();
+  // Dynamic (data) tables.
+  if (db_table_exists('field_config')) {
+    $fields = field_read_fields();
+    drupal_load('module', 'field_sql_storage');
+    $schema = array();
+    foreach ($fields as $field) {
+      $schema += _field_sql_storage_schema($field);
+    }
+  }
+  return $schema;
+}

=== added file 'modules/field/modules/field_sql_storage/field_sql_storage.module'
--- modules/field/modules/field_sql_storage/field_sql_storage.module	1970-01-01 00:00:00 +0000
+++ modules/field/modules/field_sql_storage/field_sql_storage.module	2009-01-20 19:39:55 +0000
@@ -0,0 +1,343 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Default implementation of the field storage API.
+ */
+
+/**
+ * Generate a table name for a field data table.
+ *
+ * @param $name
+ *   The name of the field
+ * @return
+ *   A string containing the generated name for the database table
+ */
+function _field_sql_storage_tablename($name) {
+  return 'field_data_' . $name;
+}
+
+/**
+ * Generate a table name for a field revision archive table.
+ *
+ * @param $name
+ *   The name of the field
+ * @return
+ *   A string containing the generated name for the database table
+ */
+function _field_sql_storage_revision_tablename($name) {
+  return 'field_data_revision_' . $name;
+}
+
+/**
+ * Return the database schema for a field.  This may contain one or
+ * more tables.  Each table will contain the columns relevant for the
+ * specified field.  Leave $field['columns'] empty to get only the
+ * base schema.
+ *
+ * @param $field
+ *   The field structure for which to generate a database schema.
+ * @return
+ *   One or more tables representing the schema for the field.
+ */
+function _field_sql_storage_schema($field) {
+  $current = array(
+    'description' => 'Data storage for field '. $field['field_name'],
+    'fields' => array(
+      'entity' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'description' => 'The entity type this data is attached to',
+      ),
+      'bundle' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The field instance bundle to which this row belongs, used when deleting a field instance',
+      ),
+      'deleted' => array(
+        'type' => 'int',
+        'size' => 'tiny',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => 'A boolean indicating whether this data item has been deleted'
+      ),
+      'entity_id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => 'The entity id this data is attached to',
+      ),
+      'revision_id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => FALSE,
+        'description' => 'The entity revision id this data is attached to, or NULL if the entity type is not versioned',
+      ),
+      'delta' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => 'The sequence number for this data item, used for multi-value fields',
+      ),
+    ),
+    'primary key' => array('entity', 'entity_id', 'deleted', 'delta'),
+    // TODO : index on 'bundle'
+  );
+
+  // Add field columns.
+  foreach ($field['columns'] as $attributes) {
+    $column_name = $attributes['column'];
+    unset($attributes['column']);
+    $current['fields'][$column_name] = $attributes;
+  }
+
+  // Construct the revision table.  The primary key includes
+  // revision_id but not entity_id so that multiple revision loads can
+  // use the IN operator.
+  $revision = $current;
+  $revision['description'] = 'Revision archive storage for field '. $field['field_name'];
+  $revision['revision_id']['description'] = 'The entity revision id this data is attached to';
+  $revision['primary key'] = array('entity', 'revision_id', 'deleted', 'delta');
+
+  return array(
+    _field_sql_storage_tablename($field['field_name']) => $current,
+    _field_sql_storage_revision_tablename($field['field_name']) => $revision,
+  );
+}
+
+function field_sql_storage_create_table($field) {
+  $schema = _field_sql_storage_schema($field);
+  foreach ($schema as $name => $table) {
+    db_create_table($ret, $name, $table);
+  }
+}
+
+function field_sql_storage_delete_table($field_name) {
+  // Mark all data associated with the field for deletion.
+  $table = _field_sql_storage_tablename($field_name);
+  db_update($table)
+    ->fields(array('deleted' => 1))
+    ->execute();
+}
+
+/**
+ * Load field data for a set of objects from the database.
+ *
+ * @param $obj_type
+ *   The entity type of objects being loaded, such as 'node' or
+ *   'user'.
+ * @param $objects
+ *   The array of objects for which to load data.  The objects
+ *   referenced by this array will have the field data added to them
+ *   directly.
+ * @param $age
+ *   FIELD_LOAD_CURRENT to load the most recent revision for all
+ *   fields, or FIELD_LOAD_REVISION to load the version indicated by
+ *   each object.
+ * @return
+ *   An array of field data for the objects, keyed by entity id, field
+ *   name, and item delta number.
+ */
+function field_sql_storage_load($obj_type, $objects, $age) {
+  $load_current = $age == FIELD_LOAD_CURRENT;
+
+  // Gather ids needed for each field.
+  $field_ids = array();
+  $delta_count = array();
+  foreach ($objects as $obj) {
+    list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $obj);
+    foreach (field_info_instances($bundle) as $instance) {
+      $field_ids[$instance['field_name']][] = $load_current ? $id : $vid;
+      $delta_count[$id][$instance['field_name']] = 0;
+    }
+  }
+
+  $additions = array();
+  foreach ($field_ids as $field_name => $ids) {
+    $field = field_info_field($field_name);
+    $table = $load_current ? _field_sql_storage_tablename($field_name) : _field_sql_storage_revision_tablename($field_name);
+
+    $results = db_select($table, 't')
+      ->fields('t')
+      ->condition('entity', $obj_type)
+      ->condition($load_current ? 'entity_id' : 'revision_id', $ids, 'IN')
+      ->condition('deleted', 0)
+      ->orderBy('delta')
+      ->execute();
+
+    foreach ($results as $row) {
+      if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$field_name] < $field['cardinality']) {
+        $item = array();
+        // For each column declared by the field, populate the item
+        // from the prefixed database column.
+        foreach ($field['columns'] as $column => $attributes) {
+          $item[$column] = $row->$attributes['column'];
+        }
+
+        // Add the item to the field values for the entity.
+        $additions[$row->entity_id][$field_name][] = $item;
+        $delta_count[$row->entity_id][$field_name]++;
+      }
+    }
+  }
+  return $additions;
+}
+
+function field_sql_storage_write($obj_type, $object, $update = FALSE) {
+  list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+
+  $instances = field_info_instances($bundle);
+  foreach ($instances as $instance) {
+    $field_name = $instance['field_name'];
+    $table_name = _field_sql_storage_tablename($field_name);
+    $revision_name = _field_sql_storage_revision_tablename($field_name);
+    $field = field_read_field($field_name);
+
+    // Leave the field untouched if $object comes with no $field_name property.
+    // Empty the field if $object->$field_name is NULL or an empty array.
+
+    // Function property_exists() is slower, so we catch the more frequent cases
+    // where it's an empty array with the faster isset().
+    if (isset($object->$field_name) || property_exists($object, $field_name)) {
+      // Delete and insert, rather than update, in case a value was added.
+      if ($update) {
+        db_delete($table_name)->condition('entity', $obj_type)->condition('entity_id', $id)->execute();
+        if (isset($vid)) {
+          db_delete($revision_name)->condition('entity', $obj_type)->condition('entity_id', $id)->condition('revision_id', $vid)->execute();
+        }
+      }
+
+      if ($object->$field_name) {
+        // Prepare the multi-insert query.
+        $columns = array('entity', 'entity_id', 'revision_id', 'bundle', 'delta');
+        foreach ($field['columns'] as $column => $attributes) {
+          $columns[] = $attributes['column'];
+        }
+        $query = db_insert($table_name)->fields($columns);
+        if (isset($vid)) {
+          $revision_query = db_insert($revision_name)->fields($columns);
+        }
+
+        $delta_count = 0;
+        foreach ($object->$field_name as $delta => $item) {
+          $record = array(
+            'entity' => $obj_type,
+            'entity_id' => $id,
+            'revision_id' => $vid,
+            'bundle' => $bundle,
+            'delta' => $delta,
+          );
+          foreach ($field['columns'] as $column => $attributes) {
+            $record[$attributes['column']] = isset($item[$column]) ? $item[$column] : NULL;
+          }
+          $query->values($record);
+          if (isset($vid)) {
+            $revision_query->values($record);
+          }
+
+          if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ++$delta_count == $field['cardinality']) {
+            break;
+          }
+        }
+
+        // Execute the insert.
+        $query->execute();
+        if (isset($vid)) {
+          $revision_query->execute();
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Delete all field data for a single object.  This function actually
+ * deletes the data from the database.
+ *
+ * @param $obj_type
+ *   The entity type of the object being deleted, such as 'node' or
+ *   'user'.
+ * @param $object
+ *   The object for which to delete field data.
+ */
+function field_sql_storage_delete($obj_type, $object) {
+  list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+
+  $instances = field_info_instances($bundle);
+  foreach ($instances as $instance) {
+    $field_name = $instance['field_name'];
+    $table_name = _field_sql_storage_tablename($field_name);
+    $revision_name = _field_sql_storage_revision_tablename($field_name);
+    db_delete($table_name)
+      ->condition('entity', $obj_type)
+      ->condition('entity_id', $id)
+      ->execute();
+    db_delete($revision_name)
+      ->condition('entity', $obj_type)
+      ->condition('entity_id', $id)
+      ->execute();
+  }
+}
+
+/**
+ * Delete field data for a single revision of a single object.
+ * Deleting the current (most recently written) revision is not
+ * allowed as has undefined results.  This function actually deletes
+ * the data from the database.
+ *
+ * @param $obj_type
+ *   The entity type of the object being deleted, such as 'node' or
+ *   'user'.
+ * @param $object
+ *   The object for which to delete field data.
+ */
+function field_sql_storage_delete_revision($obj_type, $object) {
+  list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+
+  if (isset($vid)) {
+    $instances = field_info_instances($bundle);
+    foreach ($instances as $instance) {
+      $field_name = $instance['field_name'];
+      $revision_name = _field_sql_storage_revision_tablename($field_name);
+      db_delete($revision_name)
+        ->condition('entity', $obj_type)
+        ->condition('entity_id', $id)
+        ->condition('revision_id', $vid)
+        ->execute();
+    }
+  }
+}
+
+function field_sql_storage_delete_instance($field_name, $bundle) {
+  // Mark all data associated with the field for deletion.
+  $table_name = _field_sql_storage_tablename($field_name);
+  $revision_name = _field_sql_storage_revision_tablename($field_name);
+  db_update($table_name)
+    ->fields(array('deleted' => 1))
+    ->condition('bundle', $bundle)
+    ->execute();
+  db_update($revision_name)
+    ->fields(array('deleted' => 1))
+    ->condition('bundle', $bundle)
+    ->execute();
+}
+
+function field_sql_storage_rename_bundle($bundle_old, $bundle_new) {
+  $instances = field_info_instances($bundle_old);
+  foreach ($instances as $instance) {
+    $table_name = _field_sql_storage_tablename($instance['field_name']);
+    $revision_name = _field_sql_storage_revision_tablename($instance['field_name']);
+    db_update($table_name)
+      ->fields(array('bundle' => $bundle_new))
+      ->condition('bundle', $bundle_old)
+      ->execute();
+    db_update($revision_name)
+      ->fields(array('bundle' => $bundle_new))
+      ->condition('bundle', $bundle_old)
+      ->execute();
+  }
+}
\ No newline at end of file

=== added directory 'modules/field/modules/list'
=== added file 'modules/field/modules/list/list.info'
--- modules/field/modules/list/list.info	1970-01-01 00:00:00 +0000
+++ modules/field/modules/list/list.info	2009-01-20 19:39:54 +0000
@@ -0,0 +1,7 @@
+; $Id$
+name = List
+description = Defines list field types. Use with Optionwidgets to create selection lists.
+package = Core - fields
+core = 7.x
+
+files[]=list.module

=== added file 'modules/field/modules/list/list.module'
--- modules/field/modules/list/list.module	1970-01-01 00:00:00 +0000
+++ modules/field/modules/list/list.module	2009-01-20 19:39:55 +0000
@@ -0,0 +1,206 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Defines list field types that can be used with option_widgets widgets.
+ */
+
+/**
+ * Implementation of hook_theme().
+ */
+function list_theme() {
+  return array(
+    'field_formatter_list_default' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'field_formatter_list_key' => array(
+      'arguments' => array('element' => NULL),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_field_info().
+ */
+function list_field_info() {
+  return array(
+    'list' => array(
+      'label' => t('List'),
+      'description' => t('This field stores numeric keys from key/value lists of allowed values where the key is a simple alias for the position of the value, i.e. 0|First option, 1|Second option, 2|Third option.'),
+      'settings' => array('allowed_values' => '', 'allowed_values_function' => ''),
+      'default_widget' => 'option_widgets_select',
+      'default_formatter' => 'list_default',
+    ),
+    'list_boolean' => array(
+      'label' => t('Boolean'),
+      'description' => t('This field stores simple on/off or yes/no options.'),
+      'settings' => array('allowed_values' => '', 'allowed_values_function' => ''),
+      'default_widget' => 'option_widgets_select',
+      'default_formatter' => 'list_default',
+    ),
+    'list_number' => array(
+      'label' => t('List (numeric)'),
+      'description' => t('This field stores keys from key/value lists of allowed numbers where the stored numeric key has significance and must be preserved, i.e. \'Lifetime in days\': 1|1 day, 7|1 week, 31|1 month.'),
+      'settings' => array('allowed_values' => '', 'allowed_values_function' => ''),
+      'default_widget' => 'option_widgets_select',
+      'default_formatter' => 'list_default',
+    ),
+    'list_text' => array(
+      'label' => t('List (text)'),
+      'description' => t('This field stores keys from key/value lists of allowed values where the stored key has significance and must be a varchar, i.e. \'US States\': IL|Illinois, IA|Iowa, IN|Indiana'),
+      'settings' => array('allowed_values' => '', 'allowed_values_function' => ''),
+      'default_widget' => 'option_widgets_select',
+      'default_formatter' => 'list_default',
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_field_schema().
+ */
+function list_field_columns($field) {
+  switch ($field['type']) {
+    case 'list_text':
+      $columns = array(
+        'value' => array(
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => FALSE,
+        ),
+      );
+      break;
+    case 'list_number':
+      $columns = array(
+        'value' => array(
+          'type' => 'float',
+          'unsigned' => TRUE,
+          'not null' => FALSE,
+        ),
+      );
+      break;
+    default:
+      $columns = array(
+        'value' => array(
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => FALSE,
+        ),
+      );
+      break;
+  }
+  return $columns;
+}
+
+/**
+ * Implementation of hook_field_validate().
+ */
+function list_field_validate($obj_type, $object, $field, $instance, $items, $form) {
+  $allowed_values = list_allowed_values($field);
+  if (is_array($items)) {
+    foreach ($items as $delta => $item) {
+      $error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
+      if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
+      if (!empty($item['value'])) {
+        if (count($allowed_values) && !array_key_exists($item['value'], $allowed_values)) {
+          form_set_error($error_element, t('%name: illegal value.', array('%name' => t($instance['label']))));
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Implementation of hook_field_is_empty().
+ */
+function list_field_is_empty($item, $field) {
+  if (empty($item['value']) && (string)$item['value'] !== '0') {
+    return TRUE;
+  }
+  return FALSE;
+}
+
+/**
+ * Implementation of hook_field_formatter_info().
+ */
+function list_field_formatter_info() {
+  return array(
+    'list_default' => array(
+      'label' => t('Default'),
+      'field types' => array('list', 'list_boolean', 'list_text', 'list_number'),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+      ),
+    ),
+    'list_key' => array(
+      'label' => t('Key'),
+      'field types' => array('list', 'list_boolean', 'list_text', 'list_number'),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+      ),
+    ),
+  );
+}
+
+/**
+ * Theme function for 'default' list field formatter.
+ */
+function theme_field_formatter_list_default($element) {
+  $field = field_info_field($element['#field_name']);
+  if (($allowed_values = list_allowed_values($field)) && isset($allowed_values[$element['#item']['value']])) {
+    return $allowed_values[$element['#item']['value']];
+  }
+  // If no match was found in allowed values, fall back to the key.
+  return $element['#item']['safe'];
+}
+
+/**
+ * Theme function for 'key' list field formatter.
+ */
+function theme_field_formatter_list_key($element) {
+  return $element['#item']['safe'];
+}
+
+/**
+ *  Create an array of the allowed values for this field.
+ *
+ *  Used by number and text fields, expects to find either
+ *  a function that will return the correct value, or a string
+ *  with keys and labels separated with '|' and with each
+ *  new value on its own line.
+ */
+// TODO Rework this to create a method of selecting plugable allowed values lists.
+function list_allowed_values($field) {
+  static $allowed_values;
+
+  if (isset($allowed_values[$field['field_name']])) {
+    return $allowed_values[$field['field_name']];
+  }
+
+  $allowed_values[$field['field_name']] = array();
+
+  if (isset($field['settings']['allowed_values_function'])) {
+    $function = $field['settings']['allowed_values_function'];
+    if (drupal_function_exists($function)) {
+      $allowed_values[$field['field_name']] = $function($field);
+    }
+  }
+
+  if (empty($allowed_values[$field['field_name']]) && isset($field['settings']['allowed_values'])) {
+    $list = explode("\n", $field['settings']['allowed_values']);
+    $list = array_map('trim', $list);
+    $list = array_filter($list, 'strlen');
+    foreach ($list as $opt) {
+      // Sanitize the user input with a permissive filter.
+      $opt = field_filter_xss($opt);
+      if (strpos($opt, '|') !== FALSE) {
+        list($key, $value) = explode('|', $opt);
+        $allowed_values[$field['field_name']][$key] = (isset($value) && $value !=='') ? $value : $key;
+      }
+      else {
+        $allowed_values[$field['field_name']][$opt] = $opt;
+      }
+    }
+  }
+  return $allowed_values[$field['field_name']];
+}

=== added directory 'modules/field/modules/number'
=== added file 'modules/field/modules/number/number.info'
--- modules/field/modules/number/number.info	1970-01-01 00:00:00 +0000
+++ modules/field/modules/number/number.info	2009-01-20 19:39:55 +0000
@@ -0,0 +1,7 @@
+; $Id$
+name = Number
+description = Defines numeric field types.
+package = Core - fields
+core = 7.x
+
+files[]=number.module

=== added file 'modules/field/modules/number/number.module'
--- modules/field/modules/number/number.module	1970-01-01 00:00:00 +0000
+++ modules/field/modules/number/number.module	2009-01-20 19:39:55 +0000
@@ -0,0 +1,424 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Defines numeric field types.
+ */
+
+/**
+ * Implementation of hook_theme().
+ */
+function number_theme() {
+  return array(
+    'number' => array('arguments' => array('element' => NULL)),
+    'field_formatter_number_integer' => array('arguments' => array('element' => NULL), 'function' => 'theme_field_formatter_number'),
+    'field_formatter_number_decimal' => array('arguments' => array('element' => NULL), 'function' => 'theme_field_formatter_number'),
+    'field_formatter_number_unformatted' => array('arguments' => array('element' => NULL)),
+  );
+}
+
+/**
+ * Implementation of hook_field_info().
+ */
+function number_field_info() {
+  return array(
+    'number_integer' => array(
+      'label' => t('Integer'),
+      'description' => t('This field stores a number in the database as an integer.'),
+      'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
+      'default_widget' => 'number',
+      'default_formatter' => 'number_integer',
+    ),
+    'number_decimal' => array(
+      'label' => t('Decimal'),
+      'description' => t('This field stores a number in the database in a fixed decimal format.'),
+      'settings' => array('precision' => 10, 'scale' => 2, 'decimal' => '.'),
+      'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
+      'default_widget' => 'number',
+      'default_formatter' => 'number_integer',
+    ),
+    'number_float' => array(
+      'label' => t('Float'),
+      'description' => t('This field stores a number in the database in a floating point format.'),
+      'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
+      'default_widget' => 'number',
+      'default_formatter' => 'number_integer',
+    ),
+  );
+}
+
+function number_field_columns($field) {
+  switch ($field['type']) {
+    case 'number_integer' :
+      $colums = array(
+        'value' => array(
+          'type' => 'int',
+          'not null' => FALSE
+        ),
+      );
+      break;
+
+    case 'number_float' :
+      $colums = array(
+        'value' => array(
+          'type' => 'float',
+          'not null' => FALSE
+        ),
+      );
+      break;
+
+    case 'number_decimal' :
+      $colums = array(
+        'value' => array(
+          'type' => 'numeric',
+          'precision' => $field['settings']['precision'],
+          'scale' => $field['settings']['scale'],
+          'not null' => FALSE
+        ),
+      );
+      break;
+  }
+  return $colums;
+}
+
+/**
+ * Implementation of hook_field_validate().
+ */
+function number_field_validate($obj_type, $node, $field, $instance, &$items, $form) {
+  if (is_array($items)) {
+    foreach ($items as $delta => $item) {
+      $error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
+      if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
+      if ($item['value'] != '') {
+        if (is_numeric($instance['settings']['min']) && $item['value'] < $instance['settings']['min']) {
+          form_set_error($error_element, t('%name: the value may be no smaller than %min.', array('%name' => t($instance['label']), '%min' => $instance['settings']['min'])));
+        }
+        if (is_numeric($instance['settings']['max']) && $item['value'] > $instance['settings']['max']) {
+          form_set_error($error_element, t('%name: the value may be no larger than %max.', array('%name' => t($instance['label']), '%max' => $instance['settings']['max'])));
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Implementation of hook_content_is_empty().
+ */
+function number_field_is_empty($item, $field) {
+  if (empty($item['value']) && (string)$item['value'] !== '0') {
+    return TRUE;
+  }
+  return FALSE;
+}
+
+/**
+ * Implementation of hook_field_formatter_info().
+ */
+function number_field_formatter_info() {
+  return array(
+    'number_integer' => array(
+      'label' => t('default'),
+      'field types' => array('number_integer'),
+      'settings' =>  array(
+        'thousand_separator' => ' ',
+        'decimal_separator' => '.',
+        'scale' => 0,
+        'prefix_suffix' => TRUE,
+      ),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+      ),
+    ),
+    'number_decimal' => array(
+      'label' => t('default'),
+      'field types' => array('number_decimal', 'number_float'),
+      'settings' =>  array(
+        'thousand_separator' => ' ',
+        'decimal_separator' => '.',
+        'scale' => 2,
+        'prefix_suffix' => TRUE,
+      ),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+      ),
+    ),
+    'number_unformatted' => array(
+      'label' => t('unformatted'),
+      'field types' => array('number_integer', 'number_decimal', 'number_float'),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+      ),
+    ),
+  );
+}
+
+/**
+ * Theme function for 'unformatted' number field formatter.
+ */
+function theme_field_formatter_number_unformatted($element) {
+  return $element['#item']['value'];
+}
+
+/**
+ * Proxy theme function for number field formatters.
+ */
+function theme_field_formatter_number($element) {
+  $field = field_info_field($element['#field_name']);
+  $instance = field_info_instance($element['#field_name'], $element['#bundle']);
+  $value = $element['#item']['value'];
+  $settings = $element['#settings'];
+  $formatter_type = $element['#formatter'];
+
+  if (empty($value) && $value !== '0') {
+    return '';
+  }
+
+  $output = number_format($value, $settings['scale'], $settings['decimal_separator'], $settings['thousand_separator']);
+
+  if ($settings['prefix_suffix']) {
+    $prefixes = isset($instance['settings']['prefix']) ? explode('|', check_plain($instance['settings']['prefix'])) : array(0 => '');
+    $suffixes = isset($instance['settings']['suffix']) ? explode('|', check_plain($instance['settings']['suffix'])) : array(0 => '');
+    $prefix = (count($prefixes) > 1) ? format_plural($value, $prefixes[0], $prefixes[1]) : $prefixes[0];
+    $suffix = (count($suffixes) > 1) ? format_plural($value, $suffixes[0], $suffixes[1]) : $suffixes[0];
+    $output = $prefix . $output . $suffix;
+  }
+
+  return $output;
+}
+
+/**
+ * Implementation of hook_field_widget_info().
+ *
+ * Here we indicate that the content module will handle
+ * the default value and multiple values for these widgets.
+ *
+ * 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 number_field_widget_info() {
+  return array(
+    'number' => array(
+      'label' => t('Text field'),
+      'field types' => array('number_integer', 'number_decimal', 'number_float'),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+        'default value' => FIELD_BEHAVIOR_DEFAULT,
+      ),
+    ),
+  );
+}
+
+/**
+ * Implementation of 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.
+ *
+ * Includes a regex to check for valid values as an additional parameter
+ * the validator can use. The regex can be overridden if necessary.
+ */
+function number_elements() {
+  return array(
+    'number' => array(
+      '#input' => TRUE,
+      '#columns' => array('value'), '#delta' => 0,
+      '#process' => array('number_process'),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_field_widget().
+ *
+ * Attach a single form element to the form. It will be built out and
+ * validated in the callback(s) listed in hook_elements. We build it
+ * out in the callbacks rather than here in hook_widget so it can be
+ * plugged into any module that can provide it with valid
+ * $field information.
+ *
+ * Content module will set the weight, field name and delta values
+ * for each form element. This is a change from earlier CCK versions
+ * where the widget managed its own multiple values.
+ *
+ * If there are multiple values for this field, the content module will
+ * call this function as many times as needed.
+ *
+ * @param $form
+ *   the entire form array, $form['#node'] holds node information
+ * @param $form_state
+ *   the form_state, $form_state['values'] holds the form values.
+ * @param $instance
+ *   the field instance array
+ * @param $delta
+ *   the order of this item in the array of subelements (0, 1, 2, etc)
+ *
+ * @return
+ *   the form item for a single element for this field
+ */
+function number_field_widget(&$form, &$form_state, $instance, $items, $delta = 0) {
+  $element = array(
+    '#type' => $instance['widget']['type'],
+    '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL,
+  );
+  return $element;
+}
+
+/**
+ * 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']].
+ */
+function number_process($element, $edit, $form_state, $form) {
+  $field_name = $element['#field_name'];
+  $field = field_info_field($element['#field_name']);
+  $instance = field_info_instance($element['#field_name'], $element['#bundle']);
+  $field_key  = $element['#columns'][0];
+
+  $value = isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : '';
+  if ($field['type'] == 'number_decimal') {
+    $value = str_replace('.', $field['settings']['decimal'], $value);
+  }
+
+  $element[$field_key] = array(
+    '#type' => 'textfield',
+    '#default_value' => $value,
+    // Need to allow a slightly larger size that the field length to allow
+    // for some configurations where all characters won't fit in input field.
+    '#size' => $field['type'] == 'number_decimal' ? $field['settings']['precision'] + 2 : 12,
+    '#maxlength' => $field['type'] == 'number_decimal' ? $field['settings']['precision'] : 10,
+    '#attributes' => array('class' => 'number'),
+    // The following values were set by the content module and need
+    // to be passed down to the nested element.
+    '#title' => $element['#title'],
+    '#description' => $element['#description'],
+    '#required' => $element['#required'],
+    '#field_name' => $element['#field_name'],
+    '#bundle' => $element['#bundle'],
+    '#delta' => $element['#delta'],
+    '#columns' => $element['#columns'],
+  );
+
+  if (!empty($instance['settings']['prefix'])) {
+    $prefixes = explode('|', $instance['settings']['prefix']);
+    $element[$field_key]['#field_prefix'] = array_pop($prefixes);
+  }
+  if (!empty($instance['settings']['suffix'])) {
+    $suffixes = explode('|', $instance['settings']['suffix']);
+    $element[$field_key]['#field_suffix'] = array_pop($suffixes);
+  }
+
+  // Make sure we don't wipe out element validation added elsewhere.
+  if (empty($element['#element_validate'])) {
+    $element['#element_validate'] = array();
+  }
+  switch ($field['type']) {
+    case 'number_float':
+      $element['#element_validate'][] = 'number_float_validate';
+      break;
+    case 'number_integer':
+      $element['#element_validate'][] = 'number_integer_validate';
+      break;
+    case 'number_decimal':
+      $element['#element_validate'][] = 'number_decimal_validate';
+      break;
+  }
+
+  // Used so that hook_field('validate') knows where to flag an error.
+  $element['_error_element'] = array(
+    '#type' => 'value',
+    '#value' => implode('][', array_merge($element['#parents'], array($field_key))),
+  );
+
+  return $element;
+}
+
+/**
+ * FAPI validation of an individual float element.
+ */
+function number_float_validate($element, &$form_state) {
+  $field = field_info_field($element['#field_name']);
+  $instance = field_info_instance($element['#field_name'], $element['#bundle']);
+  $field_key = $element['#columns'][0];
+  $value = $element['#value'][$field_key];
+
+  if (($element[$field_key]['#required'] || !empty($value))) {
+    $start = $value;
+    $value = preg_replace('@[^-0-9\.]@', '', $value);
+    if ($start != $value) {
+      $error_field = implode('][', $element['#parents']) .']['. $field_key;
+      form_set_error($error_field, t('Only numbers and decimals are allowed in %field.', array('%field' => t($instance['label']))));
+    }
+    else {
+      form_set_value($element[$field_key], $value, $form_state);
+    }
+  }
+}
+
+/**
+ * FAPI validation of an individual integer element.
+ */
+function number_integer_validate($element, &$form_state) {
+  $field = field_info_field($element['#field_name']);
+  $instance = field_info_instance($element['#field_name'], $element['#bundle']);
+  $field_key = $element['#columns'][0];
+  $value = $element['#value'][$field_key];
+
+  if (($element[$field_key]['#required'] || !empty($value))) {
+    $start = $value;
+    $value = preg_replace('@[^-0-9]@', '', $value);
+    if ($start != $value) {
+      $error_field = implode('][', $element['#parents']) .']['. $field_key;
+      form_set_error($error_field, t('Only numbers are allowed in %field.', array('%field' => t($instance['label']))));
+    }
+    else {
+      form_set_value($element[$field_key], $value, $form_state);
+    }
+  }
+}
+
+/**
+ * FAPI validation of an individual decimal element.
+ */
+function number_decimal_validate($element, &$form_state) {
+  $field = field_info_field($element['#field_name']);
+  $instance = field_info_instance($element['#field_name'], $element['#bundle']);
+  $field_key = $element['#columns'][0];
+  $value = $element['#value'][$field_key];
+
+  if (($element[$field_key]['#required'] || !empty($value))) {
+    $start = $value;
+    $value = preg_replace('@[^-0-9\\'. $field['settings']['decimal'] .']@', '', $value);
+    if ($start != $value) {
+      $error_field = implode('][', $element['#parents']) .']['. $field_key;
+      form_set_error($error_field, t('Only numbers and the decimal character (%decimal) are allowed in %field.', array('%decimal' => $field['settings']['decimal'], '%field' => t($instance['label']))));
+    }
+    else {
+      $value = str_replace($field['settings']['decimal'], '.', $value);
+      $value = round($value, $field['settings']['scale']);
+      form_set_value($element[$field_key], $value, $form_state);
+    }
+  }
+}
+
+/**
+ * FAPI theme for an individual number element.
+ *
+ * The textfield is already rendered by the textfield
+ * theme and the HTML output lives in $element['#children'].
+ * Override this theme to make custom changes to the output.
+ *
+ * $element['#field_name'] contains the field name
+ * $element['#delta]  is the position of this element in the group
+ */
+function theme_number($element) {
+  return $element['#children'];
+}
\ No newline at end of file

=== added directory 'modules/field/modules/option_widgets'
=== added file 'modules/field/modules/option_widgets/option_widgets.info'
--- modules/field/modules/option_widgets/option_widgets.info	1970-01-01 00:00:00 +0000
+++ modules/field/modules/option_widgets/option_widgets.info	2009-01-20 19:39:55 +0000
@@ -0,0 +1,6 @@
+; $Id$
+name = Optionwidgets
+description = Defines selection, check box and radio button widgets for text and numeric fields.
+package = Core - fields
+core = 7.x
+files[]=option_widgets.module

=== added file 'modules/field/modules/option_widgets/option_widgets.module'
--- modules/field/modules/option_widgets/option_widgets.module	1970-01-01 00:00:00 +0000
+++ modules/field/modules/option_widgets/option_widgets.module	2009-01-20 19:39:54 +0000
@@ -0,0 +1,438 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Defines selection, check box and radio button widgets for text and numeric fields.
+ */
+/**
+ * Create a Text Field structure.  Defines the additional field
+ * properties needed for text fields.
+ *
+ * // Plain text or formatted?  Controls the 'format' column.
+ * public $text_processing;
+ *
+ * // The length property of the 'value' column.
+ * public $max_length;
+ *
+ */
+function option_widgets_field_new($field_name = NULL) {
+  return field_new($field_name, 'option_widgets');
+}
+
+/**
+ * Defines a option_widgets FieldInstance.  The FieldInstance constructor will
+ * initialize $this['widget'] to an object of $widget_class, so set it
+ * to option_widgetsWidgetSettings here.
+ */
+function option_widgets_field_instance_new($field_name, $type_name, $widget_type) {
+  // TODO D7 : really needed, now that settings are pre-populated ?
+  $instance = field_instance_new($field_name, $type_name, $widget_type);
+  return $instance;
+}
+
+/**
+ * Implementation of hook_form_alter.
+ */
+function option_widgets_form_alter(&$form, $form_state, $form_id) {
+  // Provide additional help for the field settings form.
+  if ($form_id == 'field_field_edit_form' && isset($form['widget'])) {
+    $widget_type = $form['#field']['widget']['type'];
+    $field_type = $form['#field']['type'];
+    $label = $form['#field']['widget']['label'];
+
+    $output = '<p>'. t('Create a list of options as a list in <strong>Allowed values list</strong> or as an array in PHP code. These values will be the same for %field in all field types.', array('%field' => $label)) .'</p>';
+
+    if ($widget_type == 'option_widgets_onoff') {
+      $output .= '<p>'. t("For a 'single on/off checkbox' widget, define the 'off' value first, then the 'on' value in the <strong>Allowed values</strong> section. Note that the checkbox will be labeled with the label of the 'on' value.") .'</p>';
+    }
+    elseif ($widget_type == 'option_widgets_buttons') {
+      $output .= '<p>'. t("The 'checkboxes/radio buttons' widget will display checkboxes if the cardinality option is selected for this field, otherwise radios will be displayed.") .'</p>';
+    }
+
+    if (in_array($field_type, array('text', 'number_integer', 'number_float', 'number_decimal'))
+    && in_array($widget_type, array('option_widgets_onoff', 'option_widgets_buttons', 'option_widgets_select'))) {
+      $form['field']['allowed_values_fieldset']['#collapsed'] = FALSE;
+      $form['field']['allowed_values_fieldset']['#description'] = $output;
+
+      // If no 'allowed values' were set yet, add a remainder in the messages area.
+      if (empty($form_state['post'])
+      && empty($form['field']['allowed_values_fieldset']['allowed_values']['#default_value'])
+      && empty($form['field']['allowed_values_fieldset']['advanced_options']['allowed_values_php']['#default_value'])) {
+        drupal_set_message(t("You need to specify the 'allowed values' for this field."), 'warning');
+      }
+    }
+  }
+}
+
+/**
+ * Implementation of hook_theme().
+ */
+function option_widgets_theme() {
+  return array(
+    'option_widgets_select' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'option_widgets_buttons' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'option_widgets_onoff' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'option_widgets_none' => array(
+      'arguments' => array('widget_type' => NULL, 'field_name' => NULL, 'node_type' => NULL),
+      ),
+  );
+}
+
+/**
+ * Implementation of 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 option_widgets_field_widget_info() {
+
+  return array(
+    'option_widgets_select' => array(
+      'label' => t('Select list'),
+      'field types' => array('list', 'list_boolean', 'list_text', 'list_number'),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_CUSTOM,
+        'default value' => FIELD_BEHAVIOR_DEFAULT,
+      ),
+    ),
+    'option_widgets_buttons' => array(
+      'label' => t('Check boxes/radio buttons'),
+      'field types' => array('list', 'list_boolean', 'list_text', 'list_number'),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_CUSTOM,
+        'default value' => FIELD_BEHAVIOR_DEFAULT,
+      ),
+    ),
+    'option_widgets_onoff' => array(
+      'label' => t('Single on/off checkbox'),
+      'field types' => array('list_boolean'),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_CUSTOM,
+        'default value' => FIELD_BEHAVIOR_DEFAULT,
+      ),
+    ),
+  );
+}
+
+/**
+ * Implementation of 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 option_widgets_elements() {
+  return array(
+    'option_widgets_select' => array(
+      '#input' => TRUE,
+      '#columns' => array('value'), '#delta' => 0,
+      '#process' => array('option_widgets_select_process'),
+      ),
+    'option_widgets_buttons' => array(
+      '#input' => TRUE,
+      '#columns' => array('value'), '#delta' => 0,
+      '#process' => array('option_widgets_buttons_process'),
+      ),
+    'option_widgets_onoff' => array(
+      '#input' => TRUE,
+      '#columns' => array('value'), '#delta' => 0,
+      '#process' => array('option_widgets_onoff_process'),
+      ),
+    );
+}
+
+/**
+ * Implementation of hook_field_widget().
+ */
+function option_widgets_field_widget(&$form, &$form_state, $instance, $items, $delta = NULL) {
+  $element = array(
+    '#type' => $instance['widget']['type'],
+    '#default_value' => !empty($items) ? $items : array(),
+  );
+  return $element;
+}
+
+/**
+ * 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']].
+ */
+function option_widgets_buttons_process($element, $edit, &$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'])) {
+    $element['#value'] = option_widgets_data2form($element, $element['#default_value'], $field);
+  }
+  $options = option_widgets_options($field, $instance);
+  $multiple = isset($element['#multiple']) ? $element['#multiple'] : $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED;
+  $element[$field_key] = array(
+    '#type' => $multiple ? 'checkboxes' : 'radios',
+    '#title' => $element['#title'],
+    '#description' => $element['#description'],
+    '#required' => isset($element['#required']) ? $element['#required'] : $instance['required'],
+    '#multiple' => $multiple,
+    '#options' => $options,
+    '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : 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'], 'option_widgets_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;
+}
+
+/**
+ * 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']].
+ */
+function option_widgets_select_process($element, $edit, &$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'])) {
+    $element['#value'] = option_widgets_data2form($element, $element['#default_value'], $field);
+  }
+
+  $options = option_widgets_options($field, $instance);
+  $element[$field_key] = array(
+    '#type' => 'select',
+    '#title' => $element['#title'],
+    '#description' => $element['#description'],
+    '#required' => isset($element['#required']) ? $element['#required'] : $instance['required'],
+    '#multiple' => isset($element['#multiple']) ? $element['#multiple'] : $field['cardinality'],
+    '#options' => $options,
+    '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : 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'], 'option_widgets_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;
+}
+
+/**
+ * Process an individual element.
+ *
+ * Build the form element. When creating a form using FAPI #process,
+ * note that $element['#value'] is already set.
+ */
+function option_widgets_onoff_process($element, $edit, &$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'])) {
+    $element['#value'] = option_widgets_data2form($element, $element['#default_value'], $field);
+  }
+  $options = option_widgets_options($field, $instance);
+  $keys = array_keys($options);
+  $on_value = (!empty($keys) && isset($keys[1])) ? $keys[1] : NULL;
+  $element[$field_key] = array(
+    '#type' => 'checkbox',
+    '#title' => isset($options[$on_value]) ? $options[$on_value] : '',
+    '#description' => $element['#description'],
+    '#default_value' => isset($element['#value'][$field_key][0]) ? $element['#value'][$field_key][0] == $on_value : FALSE,
+    '#return_value' => $on_value,
+  );
+
+  // 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'], 'option_widgets_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 option_widgets element.
+ */
+function option_widgets_validate($element, &$form_state) {
+  // Transpose selections from field => delta to delta => field,
+  // turning cardinality selected options into cardinality parent elements.
+  // Immediate parent is the delta, need to get back to parent's parent
+  // to create cardinality elements.
+  $field = $form_state['#fields'][$element['#field_name']]['field'];
+  $items = option_widgets_form2data($element, $field);
+  form_set_value($element, $items, $form_state);
+
+  // Check we don't exceed the allowed number of values.
+  if ($field['cardinality'] >= 2) {
+    // Filter out 'none' value (if present, will always be in key 0)
+    $field_key = $element['#columns'][0];
+    if (isset($items[0][$field_key]) && $items[0][$field_key] === '') {
+      unset($items[0]);
+    }
+    if (count($items) > $field['cardinality']) {
+      $field_key  = $element['#columns'][0];
+      form_error($element[$field_key], t('%name: this field cannot hold more that @count values.', array('%name' => t($field['widget']['label']), '@count' => $field['cardinality'])));
+    }
+  }
+}
+
+/**
+ * Helper function to transpose the values as stored in the database
+ * to the format the widget needs. Can be called anywhere this
+ * transformation is needed.
+ */
+function option_widgets_data2form($element, $items, $field) {
+  $field_key  = $element['#columns'][0];
+  $field = field_info_field($element['#field_name']);
+  $instance = field_info_instance($element['#field_name'], $element['#bundle']);
+  $options    = option_widgets_options($field, $instance);
+
+  $items_transposed = field_transpose_array_rows_cols($items);
+  $values = (isset($items_transposed[$field_key]) && is_array($items_transposed[$field_key])) ? $items_transposed[$field_key] : array();
+  $keys = array();
+  foreach ($values as $value) {
+    $key = array_search($value, array_keys($options));
+    if (isset($key)) {
+      $keys[] = $value;
+    }
+  }
+  if ($field['cardinality'] || $element['#type'] == 'option_widgets_onoff') {
+    return array($field_key => $keys);
+  }
+  else {
+    return !empty($keys) ? array($field_key => $value) : array();
+  }
+}
+
+/**
+ * Helper function to transpose the values returned by submitting the widget
+ * to the format to be stored in the field. Can be called anywhere this
+ * transformation is needed.
+ */
+function option_widgets_form2data($element, $field) {
+  $field_key = $element['#columns'][0];
+  $field = field_info_field($element['#field_name']);
+  $instance = field_info_instance($element['#field_name'], $element['#bundle']);
+  $items = (array) $element[$field_key]['#value'];
+  $options = option_widgets_options($field, $instance);
+
+  $values = array_values($items);
+
+  if ($element['#type'] == 'option_widgets_onoff' && ($values[0] === 0)) {
+    $keys = array_keys($options);
+    $values = array(array_key_exists(0, $keys) ? $keys[0] : NULL);
+  }
+
+  if (empty($values)) {
+    $values[] = NULL;
+  }
+  $result = field_transpose_array_rows_cols(array($field_key => $values));
+  return $result;
+}
+
+/**
+ * Helper function for finding the allowed values list for a field.
+ *
+ * See if there is a module hook for the option values.
+ * Otherwise, try list_allowed_values() for an options list.
+ */
+function option_widgets_options($field, $instance) {
+  $function = $field['module'] .'_allowed_values';
+  $options = function_exists($function) ? $function($field) : (array) list_allowed_values($field);
+  // Add an empty choice for :
+  // - non required radios
+  // - non required selects
+  if (!$instance['required']) {
+    if ((in_array($instance['widget']['type'], array('option_widgets_buttons', 'node_reference_buttons', 'user_reference_buttons')) && !$field['cardinality'])
+      || (in_array($instance['widget']['type'], array('option_widgets_select', 'node_reference_select', 'user_reference_select')))) {
+      $options = array('' => theme('option_widgets_none', $instance)) + $options;
+    }
+  }
+  return $options;
+}
+
+/**
+ *  Theme the label for the empty value for options that are not required.
+ *  The default theme will display N/A for a radio list and blank for a select.
+ */
+function theme_option_widgets_none($instance) {
+  switch ($instance['widget']['type']) {
+    case 'option_widgets_buttons':
+    case 'node_reference_buttons':
+    case 'user_reference_buttons':
+      return t('N/A');
+    case 'option_widgets_select':
+    case 'node_reference_select':
+    case 'user_reference_select':
+      return t('- None -');
+    default :
+      return '';
+  }
+}
+
+/**
+ * FAPI themes for option_widgets.
+ *
+ * The select, checkboxes or radios are already rendered by the
+ * select, checkboxes, or radios themes and the HTML output
+ * lives in $element['#children']. Override this theme to
+ * make custom changes to the output.
+ *
+ * $element['#field_name'] contains the field name
+ * $element['#delta]  is the position of this element in the group
+ */
+function theme_option_widgets_select($element) {
+  return $element['#children'];
+}
+
+function theme_option_widgets_onoff($element) {
+  return $element['#children'];
+}
+
+function theme_option_widgets_buttons($element) {
+  return $element['#children'];
+}
\ No newline at end of file

=== added directory 'modules/field/modules/text'
=== added file 'modules/field/modules/text/text.info'
--- modules/field/modules/text/text.info	1970-01-01 00:00:00 +0000
+++ modules/field/modules/text/text.info	2009-01-20 19:39:54 +0000
@@ -0,0 +1,7 @@
+; $Id: text.info,v 1.9 2008/04/23 18:02:31 dww Exp $
+name = Text
+description = Defines simple text field types.
+package = Core - fields
+core = 7.x
+
+files[]=text.module

=== added file 'modules/field/modules/text/text.module'
--- modules/field/modules/text/text.module	1970-01-01 00:00:00 +0000
+++ modules/field/modules/text/text.module	2009-01-20 19:39:55 +0000
@@ -0,0 +1,400 @@
+<?php
+// $Id: text.module,v 1.5 2008/12/10 19:32:42 yched Exp $
+
+/**
+ * @file
+ * Defines simple text field types.
+ */
+
+/**
+ * Implementation of hook_theme().
+ */
+function text_theme() {
+  return array(
+    'text_textarea' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'text_textfield' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'field_formatter_text_default' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'field_formatter_text_plain' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'field_formatter_text_trimmed' => array(
+      'arguments' => array('element' => NULL),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_field_info().
+ */
+function text_field_info() {
+  return array(
+    'text' => array(
+      'label' => t('Text'),
+      'description' => t('This field stores varchar text in the database.'),
+      'settings' => array('max_length' => 255),
+      'instance_settings' => array('text_processing' => 0),
+      'default_widget' => 'text_textfield',
+      'default_formatter' => 'text_default',
+    ),
+    // TODO : not a good name...
+    'textarea' => array(
+      'label' => t('Textarea'),
+      'description' => t('This field stores long text in the database.'),
+      'instance_settings' => array('text_processing' => 0),
+      'default_widget' => 'text_textarea',
+      'default_formatter' => 'text_default',
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_field_schema().
+ */
+function text_field_columns($field) {
+  if ($field['type'] == 'textarea') {
+    $columns = array(
+      'value' => array(
+        'type' => 'text',
+        'size' => 'big',
+        'not null' => FALSE,
+      ),
+    );
+  }
+  else {
+    $columns = array(
+      'value' => array(
+        'type' => 'varchar',
+        'length' => $field['settings']['max_length'],
+        'not null' => FALSE,
+      ),
+    );
+  }
+  $columns += array(
+    'format' => array(
+      'type' => 'int',
+      'unsigned' => TRUE,
+      'not null' => FALSE,
+    ),
+  );
+  return $columns;
+}
+
+/**
+ * Implementation of hook_field_validate().
+ */
+function text_field_validate($obj_type, $object, $field, $instance, $items, $form) {
+  if (is_array($items)) {
+    foreach ($items as $delta => $item) {
+      $error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
+      if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
+      if (!empty($item['value'])) {
+        if (!empty($field['settings']['max_length']) && drupal_strlen($item['value']) > $field['settings']['max_length']) {
+          form_set_error($error_element, t('%name: the value may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length'])));
+        }
+      }
+    }
+  }
+}
+
+function text_field_sanitize($obj_type, $object, $field, $instance, &$items) {
+  global $language;
+  foreach ($items as $delta => $item) {
+    // TODO D7 : this code is really node-related.
+    if (!empty($field['text_processing'])) {
+      $check = is_null($object) || (isset($object->build_mode) && $object->build_mode == NODE_BUILD_PREVIEW);
+      $text = isset($item['value']) ? check_markup($item['value'], $item['format'], isset($object->language) ? $object->language : $language, $check) : '';
+    }
+    else {
+      $text = check_plain($item['value']);
+    }
+    $items[$delta]['safe'] = $text;
+  }
+}
+
+/**
+ * Implementation of hook_field_is_empty().
+ */
+function text_field_is_empty($item, $field) {
+  if (empty($item['value']) && (string)$item['value'] !== '0') {
+    return TRUE;
+  }
+  return FALSE;
+}
+
+/**
+ * Implementation of hook_field_formatter_info().
+ */
+function text_field_formatter_info() {
+  return array(
+    'text_default' => array(
+      'label' => t('Default'),
+      'field types' => array('text', 'textarea'),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+      ),
+    ),
+    'text_plain' => array(
+      'label' => t('Plain text'),
+      'field types' => array('text', 'textarea'),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+      ),
+    ),
+    'text_trimmed' => array(
+      'label' => t('Trimmed'),
+      'field types' => array('text', 'textarea'),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+      ),
+    ),
+  );
+}
+
+/**
+ * Theme function for 'default' text field formatter.
+ */
+function theme_field_formatter_text_default($element) {
+  return $element['#item']['safe'];
+}
+
+/**
+ * Theme function for 'plain' text field formatter.
+ */
+function theme_field_formatter_text_plain($element) {
+  return strip_tags($element['#item']['safe']);
+}
+
+/**
+ * Theme function for 'trimmed' text field formatter.
+ */
+function theme_field_formatter_text_trimmed($element) {
+  $field = field_info_field($element['#field_name']);
+  $instance = field_info_instance($element['#field_name'], $element['#bundle']);
+  return $field['settings']['text_processing'] ? $element['#item']['format'] : NULL;
+}
+
+/**
+ * Implementation of hook_field_widget_info().
+ *
+ * Here we indicate that the field module will handle
+ * the default value and multiple values for these widgets.
+ *
+ * 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 text_field_widget_info() {
+  return array(
+    'text_textfield' => array(
+      'label' => t('Text field'),
+      'field types' => array('text'),
+      'settings' => array('size' => 60),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+        'default value' => FIELD_BEHAVIOR_DEFAULT,
+      ),
+    ),
+    'text_textarea' => array(
+      'label' => t('Text area (multiple rows)'),
+      'field types' => array('textarea'),
+      'settings' => array('rows' => 5),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+        'default value' => FIELD_BEHAVIOR_DEFAULT,
+      ),
+    ),
+  );
+}
+
+/**
+ * Implementation of 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.
+ *
+ * Autocomplete_path is not used by text_field_widget but other
+ * widgets can use it (see nodereference and userreference).
+ */
+function text_elements() {
+  return array(
+    'text_textfield' => array(
+      '#input' => TRUE,
+      '#columns' => array('value'), '#delta' => 0,
+      '#process' => array('text_textfield_process'),
+      '#autocomplete_path' => FALSE,
+      ),
+    'text_textarea' => array(
+      '#input' => TRUE,
+      '#columns' => array('value', 'format'), '#delta' => 0,
+      '#process' => array('text_textarea_process'),
+      '#filter_value' => FILTER_FORMAT_DEFAULT,
+      ),
+  );
+}
+
+/**
+ * Implementation of hook_field_widget().
+ *
+ * Attach a single form element to the form. It will be built out and
+ * validated in the callback(s) listed in hook_elements. We build it
+ * out in the callbacks rather than here in hook_field_widget so it can be
+ * plugged into any module that can provide it with valid
+ * $field information.
+ *
+ * field module will set the weight, field name and delta values
+ * for each form element. This is a change from earlier CCK versions
+ * where the widget managed its own multiple values.
+ *
+ * If there are multiple values for this field, the field module will
+ * call this function as many times as needed.
+ *
+ * @param $form
+ *   the entire form array, $form['#node'] holds node information
+ * @param $form_state
+ *   the form_state, $form_state['values'][$field['field_name']]
+ *   holds the field's form values.
+ * @param $instance
+ *   the field instance array
+ * @param $items
+ *   array of default values for this field
+ * @param $delta
+ *   the order of this item in the array of subelements (0, 1, 2, etc)
+ *
+ * @return
+ *   the form item for a single element for this field
+ */
+function text_field_widget(&$form, &$form_state, $instance, $items, $delta = 0) {
+  $element = array(
+    '#type' => $instance['widget']['type'],
+    '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
+  );
+  return $element;
+}
+
+/**
+ * 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.
+ */
+function text_textfield_process($element, $edit, $form_state, $form) {
+  $field = $form['#fields'][$element['#field_name']]['field'];
+  $instance = $form['#fields'][$element['#field_name']]['instance'];
+  $field_key = $element['#columns'][0];
+  $delta = $element['#delta'];
+
+  $element[$field_key] = array(
+    '#type' => 'textfield',
+    '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
+    '#autocomplete_path' => $element['#autocomplete_path'],
+    '#size' => $instance['widget']['settings']['size'],
+    '#attributes' => array('class' => 'text'),
+    // The following values were set by the field module and need
+    // to be passed down to the nested element.
+    '#title' => $element['#title'],
+    '#description' => $element['#description'],
+    '#required' => $element['#required'],
+    '#field_name' => $element['#field_name'],
+    '#bundle' => $element['#bundle'],
+    '#delta' => $element['#delta'],
+    '#columns' => $element['#columns'],
+  );
+
+  $element[$field_key]['#maxlength'] = !empty($field['settings']['max_length']) ? $field['settings']['max_length'] : NULL;
+
+  if (!empty($instance['settings']['text_processing'])) {
+    $filter_key = $element['#columns'][1];
+    $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT;
+    $parents = array_merge($element['#parents'] , array($filter_key));
+    $element[$filter_key] = filter_form($format, 1, $parents);
+  }
+
+  // Used so that hook_field('validate') knows where to flag an error.
+  // TODO: rework that. See http://groups.drupal.org/node/18019.
+  $element['_error_element'] = array(
+    '#type' => 'value',
+    '#value' => implode('][', array_merge($element['#parents'], array($field_key))),
+  );
+
+  return $element;
+}
+
+/**
+ * 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']].
+ */
+function text_textarea_process($element, $edit, $form_state, $form) {
+  $field = $form['#fields'][$element['#field_name']]['field'];
+  $instance = $form['#fields'][$element['#field_name']]['instance'];
+  $field_key = $element['#columns'][0];
+  $delta = $element['#delta'];
+  $element[$field_key] = array(
+    '#type' => 'textarea',
+    '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
+    '#rows' => $instance['widget']['settings']['rows'],
+    '#weight' => 0,
+    // The following values were set by the field module and need
+    // to be passed down to the nested element.
+    '#title' => $element['#title'],
+    '#description' => $element['#description'],
+    '#required' => $element['#required'],
+    '#field_name' => $element['#field_name'],
+    '#bundle' => $element['#bundle'],
+    '#delta' => $element['#delta'],
+    '#columns' => $element['#columns'],
+  );
+
+  if (!empty($field['settings']['text_processing'])) {
+    $filter_key  = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format';
+    $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT;
+    $parents = array_merge($element['#parents'] , array($filter_key));
+    $element[$filter_key] = filter_form($format, 1, $parents);
+  }
+
+  // Used so that hook_field('validate') knows where to flag an error.
+  $element['_error_element'] = array(
+    '#type' => 'value',
+    '#value' => implode('][', array_merge($element['#parents'], array($field_key))),
+  );
+  return $element;
+}
+
+/**
+ * FAPI theme for an individual text elements.
+ *
+ * The textfield or textarea is already rendered by the
+ * textfield or textarea themes and the html output
+ * lives in $element['#children']. Override this theme to
+ * make custom changes to the output.
+ *
+ * $element['#field_name'] contains the field name
+ * $element['#delta]  is the position of this element in the group
+ */
+function theme_text_textfield($element) {
+  return $element['#children'];
+}
+
+function theme_text_textarea($element) {
+  return $element['#children'];
+}
\ No newline at end of file

=== added file 'modules/field/modules/text/text.test'
--- modules/field/modules/text/text.test	1970-01-01 00:00:00 +0000
+++ modules/field/modules/text/text.test	2009-01-20 19:39:55 +0000
@@ -0,0 +1,39 @@
+<?php
+// $Id$
+
+class TextFieldTestCase extends DrupalWebTestCase {
+  protected $instance;
+
+  function getInfo() {
+    return array(
+      'name'  => t('Text Field'),
+      'description'  => t("Test the creation of text fields."),
+      'group' => t('Field')
+    );
+  }
+
+  function setUp() {
+    parent::setUp('field', 'text', 'field_test');
+  }
+
+  // Test widgets.
+
+  /**
+   * Test textfield widget.
+   */
+  function testTextfieldWidget() {
+    // Create a field
+    $field = $this->drupalCreateField('text');
+    $this->instance = $this->drupalCreateFieldInstance($field['field_name'], 'text_textfield', 'text_default', FIELD_TEST_BUNDLE);
+
+  }
+
+  /**
+   * Test textarea widget.
+   */
+
+  // Test formatters.
+  /**
+   *
+   */
+}

=== added file 'modules/field/sample_code.php'
--- modules/field/sample_code.php	1970-01-01 00:00:00 +0000
+++ modules/field/sample_code.php	2009-01-20 19:39:55 +0000
@@ -0,0 +1,103 @@
+<?php
+
+$field = array(
+  'field_name' => 'field_single',
+  'type' => 'text',
+);
+field_create_field($field);
+
+$instance = array(
+  'field_name' => 'field_single',
+  'bundle' => 'article',
+  'label' => 'Single',
+  'widget' => array(
+    'type' => 'text_textfield',
+  ),
+  'display' => array(
+    'full' => array(
+      'label' => 'above',
+      'type' => 'text_default',
+      'exclude' => 0,
+    ),
+  ),
+);
+field_create_instance($instance);
+
+$instance['bundle'] = 'user';
+field_create_instance($instance);
+
+
+$field = array(
+  'field_name' => 'field_multiple',
+  'cardinality' => FIELD_CARDINALITY_UNLIMITED,
+  'type' => 'text',
+);
+field_create_field($field);
+
+$instance = array(
+  'field_name' => 'field_multiple',
+  'bundle' => 'article',
+  'label' => 'Multiple',
+  'widget' => array(
+    'type' => 'text_textfield',
+  ),
+  'display' => array(
+    'full' => array(
+      'label' => 'above',
+      'type' => 'text_default',
+      'exclude' => 0,
+    ),
+  ),
+);
+field_create_instance($instance);
+
+$instance['bundle'] = 'user';
+field_create_instance($instance);
+
+
+// Number
+$field = array(
+  'field_name' => 'field_integer',
+  'type' => 'number_integer',
+);
+field_create_field($field);
+$instance = array(
+  'field_name' => 'field_integer',
+  'bundle' => 'article',
+  'label' => 'Integer',
+  'widget' => array(
+    'type' => 'number',
+  ),
+  'display' => array(
+    'full' => array(
+      'label' => 'above',
+      'type' => 'number_integer',
+      'exclude' => 0,
+    ),
+  ),
+);
+field_create_instance($instance);
+
+$field = array(
+  'field_name' => 'field_decimal',
+  'type' => 'number_decimal',
+);
+field_create_field($field);
+$instance = array(
+  'field_name' => 'field_decimal',
+  'bundle' => 'article',
+  'label' => 'Decimal',
+  'widget' => array(
+    'type' => 'number',
+  ),
+  'display' => array(
+    'full' => array(
+      'label' => 'above',
+      'type' => 'number_decimal',
+      'exclude' => 0,
+    ),
+  ),
+);
+field_create_instance($instance);
+
+

=== added directory 'modules/field/theme'
=== added file 'modules/field/theme/field.css'
--- modules/field/theme/field.css	1970-01-01 00:00:00 +0000
+++ modules/field/theme/field.css	2009-01-20 19:39:55 +0000
@@ -0,0 +1,30 @@
+/* Node display */
+.field .field-label,
+.field .field-label-inline,
+.field .field-label-inline-first {
+  font-weight:bold;
+}
+.field .field-label-inline,
+.field .field-label-inline-first {
+  display:inline;
+}
+.field .field-label-inline {
+  visibility:hidden;
+}
+
+.node-form .field-multiple-table td.field-multiple-drag {
+  width:30px;
+  padding-right:0;
+}
+.node-form .field-multiple-table td.field-multiple-drag a.tabledrag-handle{
+  padding-right:.5em;
+}
+
+.node-form .field-add-more .form-submit{
+  margin:0;
+}
+
+.node-form .number {
+  display:inline;
+  width:auto;
+}
\ No newline at end of file

=== added file 'modules/field/theme/field.tpl.php'
--- modules/field/theme/field.tpl.php	1970-01-01 00:00:00 +0000
+++ modules/field/theme/field.tpl.php	2009-01-20 19:39:56 +0000
@@ -0,0 +1,49 @@
+<?php
+// $Id$
+
+/**
+ * @file field-field.tpl.php
+ * Default theme implementation to display the value of a field.
+ *
+ * Available variables:
+ * - $node: The node object.
+ * - $field: The field array.
+ * - $items: An array of values for each item in the field array.
+ * - $teaser: Whether this is displayed as a teaser.
+ * - $page: Whether this is displayed as a page.
+ * - $field_name: The field name.
+ * - $field_type: The field type.
+ * - $field_name_css: The css-compatible field name.
+ * - $field_type_css: The css-compatible field type.
+ * - $label: The item label.
+ * - $label_display: Position of label display, inline, above, or hidden.
+ * - $field_empty: Whether the field has any valid value.
+ *
+ * Each $item in $items contains:
+ * - 'view' - the themed view for that item
+ *
+ * @see template_preprocess_field()
+ */
+?>
+<?php if (!$field_empty) : ?>
+<div class="field field-type-<?php print $field_type_css ?> field-<?php print $field_name_css ?>">
+  <?php if ($label_display == 'above') : ?>
+    <div class="field-label"><?php print t($label) ?>:&nbsp;</div>
+  <?php endif;?>
+  <div class="field-items">
+    <?php $count = 1;
+    foreach ($items as $delta => $item) :
+      if (!$item['empty']) : ?>
+        <div class="field-item <?php print ($count % 2 ? 'odd' : 'even') ?>">
+          <?php if ($label_display == 'inline') { ?>
+            <div class="field-label-inline<?php print($delta ? '' : '-first')?>">
+              <?php print t($label) ?>:&nbsp;</div>
+          <?php } ?>
+          <?php print $item['view'] ?>
+        </div>
+      <?php $count++;
+      endif;
+    endforeach;?>
+  </div>
+</div>
+<?php endif; ?>

=== added directory 'modules/materialized_view'
=== added file 'modules/materialized_view/materialized_view.admin.inc'
--- modules/materialized_view/materialized_view.admin.inc	1970-01-01 00:00:00 +0000
+++ modules/materialized_view/materialized_view.admin.inc	2009-01-20 19:39:55 +0000
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * Callback function for administrative status page.
+ */
+function materialized_view_status_page() {
+  // TK: Update all of this to port from DNA.
+  
+  $content = array();
+  
+  $cols = array(
+    t('Module'),
+    t('Table'),
+    t('Remaining to index'),
+    t('Largest unindexed node ID'),
+  );
+  
+  $views = dna_views();
+  
+  foreach ($views as $table => $view) {
+    $view_row = db_fetch_object(db_query('SELECT * FROM {dna_view} WHERE foreign_table = "%s"', $table));
+    $rows[] = array(
+      $view['module'],
+      $table,
+      ($view_row ? db_result(db_query('SELECT COUNT(*) FROM {node} WHERE nid <= %d', $view_row->max_nid)) : t('Unknown')),
+      ($view_row ? $view_row->max_nid : t('Unknown')),
+    );
+  }
+  
+  if (empty($rows)) {
+    $content[] = t('No implementations of hook_dna() found.');
+  }
+  else {
+    $content[] = theme('table', $cols, $rows);
+  }
+  
+  return implode("\n", $content);
+}
\ No newline at end of file

=== added file 'modules/materialized_view/materialized_view.class.inc'
--- modules/materialized_view/materialized_view.class.inc	1970-01-01 00:00:00 +0000
+++ modules/materialized_view/materialized_view.class.inc	2009-01-20 19:39:55 +0000
@@ -0,0 +1,353 @@
+<?php
+
+define('MV_SORT_ASCENDING', 'ASC');
+define('MV_SORT_DESCENDING', 'DEC');
+
+class MaterializedView {
+  protected $static_filters = array();
+  protected $dynamic_filters = array();
+  protected $sorts = array();
+  protected $extra_columns = array();
+  protected $name;
+
+  /**
+   * Constructs a MaterializedView object.
+   *
+   * @param $name
+   *   The name of the materialized view.
+   */
+  public function __construct($name) {
+    $this->name = $name;
+  }
+
+  /**
+   * Gets the name of the materialized view.
+   *
+   * @returns
+   *   The name of the materialized view.
+   */
+  public function getName() {
+    return $this->name;
+  }
+
+  /**
+   * Adds extra column that provides data consolidation but
+   * is not indexed.
+   *
+   * @param $column
+   *   MVColumn object to be used as the data source.
+   */
+  public function addExtraColumn(MVColumn $column) {
+    $this->extra_columns[] = $column;
+  }
+
+  /**
+   * Adds filter with a predefined test that runs when objects are updated.
+   *
+   * @param $column
+   *   MVColumn object to be used for sorting.
+   * @param $operation
+   *   The operation to be used on this column.
+   * @param $value
+   *   The value or values to be used for the operation.
+   */
+  public function addStaticFilter(MVColumn $column, MVOperator $operator) {
+    $filter = new stdClass();
+    $filter->column = $column;
+    $filter->operator = $operator;
+    $this->static_filters[] = $filter;
+  }
+  
+  /**
+   * Adds filter configured to run at query time.
+   *
+   * @param $column
+   *   MVColumn object to be used for sorting.
+   * @param $operation
+   *   The operation to be used on this column.
+   */
+  public function addDynamicFilter(MVColumn $column, MVOperator $operator) {
+    $filter = new stdClass();
+    $filter->column = $column;
+    $filter->operator = $operator;
+    $this->dynamic_filters[] = $filter;    
+  }
+
+  /**
+   * Adds sort column configured to run at query time.
+   *
+   * @param $column
+   *   MVColumn object to be used for sorting.
+   * @param $direction
+   *   Constant defining sort order. Use MV_SORT_ASCENDING
+   *   or MV_SORT_DESCENDING.
+   */
+  public function addSort(MVColumn $column, $direction) {
+    $sort = new stdClass();
+    $sort->column = $column;
+    $sort->direction = $direction;
+    $this->sorts[] = $sort;
+  }
+  
+  /**
+   * Get an array listing the primary key columns.
+   */
+  protected function getPrimaryKey() {
+    $columns = array('entity', 'entity_id');
+    
+    // Add dynamic filter columns as components of the primary key
+    foreach ($this->dynamic_filters as $dynamic_filter) {
+      $columns[] = $dynamic_filter->column->getName();
+    }
+    
+    return $columns;
+  }
+
+  /**
+   * Get an array listing columns to be indexed.
+   */
+  protected function getIndex() {
+    $index_columns = array();
+    
+    // TODO: Order columns by =, LIKE, IN, and then the rest.
+    foreach ($this->dynamic_filters as $dynamic_filter) {
+      $index_columns[] = $dynamic_filter->column->getName();
+      // TODO: Return the current index columns if we encounter
+      // a filter that ends the index.
+    }
+
+    foreach ($this->sorts as $sort) {
+      // TODO: Add in sorts until direction changes
+      $index_columns[] = $sort->column->getName();
+    }
+    
+    return $index_columns;
+  }
+  
+  /**
+   * Build the schema for the materialized view table.
+   */
+  public function getSchema() {
+    $schema = array();
+    
+    $schema['fields'] = array(
+      'entity' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'unsigned' => TRUE,
+        'description' => 'The type ID of the entity.',
+      ),
+      'entity_id' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'unsigned' => TRUE,
+        'description' => 'The ID of the entity this row belongs to.',
+      ),
+    );
+
+    // Add dynamic filter columns
+    foreach ($this->dynamic_filters as $dynamic_filter) {
+      $schema['fields'][$dynamic_filter->column->getName()] = $dynamic_filter->column->getSchema();
+    }
+
+    // Add sort columns
+    foreach ($this->sorts as $sort) {
+      $schema['fields'][$sort->column->getName()] = $sort->column->getSchema();
+    }
+
+    // Add extra columns
+    foreach ($this->extra_columns as $extra_column) {
+      $schema['fields'][$extra_column->getName()] = $extra_column->getSchema();
+    }
+    
+    $schema['description'] = 'Materialized view storage for ' . $this->name . '.';
+    $schema['indexes'][$this->name . '_mv'] = $this->getIndex();
+    $schema['primary_key'] = $this->getPrimaryKey();
+    
+    return $schema;
+  }
+  
+  /**
+   * Install the schema for this materialized view.
+   */
+  public function installSchema() {
+    $ret = array();
+    
+    $this->uninstallSchema();
+    db_create_table($ret, $this->name, $this->getSchema());
+  }
+  
+  /**
+   * Uninstall the schema for this materialized view.
+   */
+  public function uninstallSchema() {
+    $ret = array();
+    if (db_table_exists($this->name)) {
+      db_drop_table($ret, $this->name);
+    }
+  }
+  
+  /**
+   * Update an object in this materialized view or remove it
+   * if it no longer meets criteria for inclusion.
+   *
+   * @param $entity
+   *   The entity type ID.
+   * @param $entity_id
+   *   The entity ID.
+   */
+  public function update($entity, $entity_id) {
+    $include = TRUE;
+    
+    // Start a transaction.
+    // TODO: Implement
+    
+    // Delete the existing rows related to the object in the database.
+    $this->delete($entity, $entity_id);
+  
+    // Loop through filters to determine if the updated object qualifies for inclusion.
+    foreach ($this->static_filters as $static_filter) {
+    
+      // Get the value of the column.
+      $value = $static_filter->column->getValue($entity, $entity_id);
+      
+      // Apply the operator
+      if (!$static_filter->operator->apply($value)) {
+        return;
+      }
+    }
+    
+    // Add the row(s) to the MV if the object meets static criteria.
+    if ($include) {
+      // We always need to write the entity and entity_id to the row.
+      $keys = $write = array(
+        'entity' => $entity,
+        'entity_id' => $entity_id,
+      );
+      
+      // Get values for the dynamic filter columns
+      foreach ($this->dynamic_filters as $dynamic_filter) {
+        $write[$dynamic_filter->column->getName()] = $dynamic_filter->column->getValue($entity, $entity_id);
+      }
+
+      // Get values for the sort columns
+      foreach ($this->sorts as $sort) {
+        $write[$sort->column->getName()] = $sort->column->getValue($entity, $entity_id);
+      }
+    
+      // Write the record to the database
+      db_insert($this->name)
+        ->fields(array_keys($write))
+        ->values($write)
+        ->execute();
+        
+      // TODO: Update this to do cartesian joins when getValue returns an array.
+    }
+  }
+  
+  /**
+   * Delete an object from this materialized view.
+   *
+   * @param $entity
+   *   The entity type ID.
+   * @param $entity_id
+   *   The entity ID.
+   */
+  public function delete($entity, $entity_id) {
+    // Delete rows related to the object.
+
+    watchdog('mv', 'Deleting: ' . $entity_id);
+
+    $num_deleted = db_delete($this->name)
+      ->condition('entity', $entity)
+      ->condition('entity_id', $entity_id)
+      ->execute();
+    
+    return $num_deleted;
+  }
+}
+
+/**
+ * Defines an operator for use in static and dynamic filters.
+ */
+abstract class MVOperator {
+  /**
+   * Applies the operator to $value.
+   *
+   * @param $value
+   *   The value to be compared.
+   *
+   * @returns boolean
+   */
+  abstract public function apply($value);
+
+  /**
+   * Determines whether the operator would end the
+   * usefulness of an index if added to the index.
+   *
+   * @returns boolean
+   */
+  abstract public function endsIndex();
+
+  /**
+   * Determines whether the operator is indexable.
+   *
+   * @returns boolean
+   */
+  abstract public function indexable();
+}
+
+/**
+ * Defines the equality operator for use in static and dynamic filters.
+ */
+class MVEqualityOperator extends MVOperator {
+  protected $comparison_value = NULL;
+
+  public function __construct($value = NULL) {
+    $this->comparison_value = $value;
+  }
+
+  public function apply($value) {
+    return $value == $this->comparison_value;
+  }
+  
+  public function endsIndex() {
+    return FALSE;
+  }
+
+  public function indexable() {
+    return TRUE;
+  }
+}
+
+/**
+ * Defines a column data source for use in materialized views.
+ */
+abstract class MVColumn {
+  /**
+   * Retreive the value to use for this column, given an entity
+   * and entity ID.
+   *
+   * @param $entity
+   *   The entity type ID.
+   * @param $entity_id
+   *   The entity ID.
+   */
+  abstract public function getValue($entity, $entity_id);
+  
+  /**
+   * Retrieve a column scheme suitable for use in $table['fields'].
+   *
+   * @returns The column scheme.
+   */
+  abstract public function getSchema();
+  
+  /**
+   * Retrieve the column name.
+   *
+   * @returns A column name.
+   */
+  abstract public function getName();
+}

=== added file 'modules/materialized_view/materialized_view.info'
--- modules/materialized_view/materialized_view.info	1970-01-01 00:00:00 +0000
+++ modules/materialized_view/materialized_view.info	2009-01-20 19:39:55 +0000
@@ -0,0 +1,11 @@
+; $Id$
+name = Materialized View
+description = Materializes defined views of fields for scalability.
+package = Core
+version = VERSION
+core = 7.x
+files[] = materialized_view.module
+files[] = materialized_view.class.inc
+files[] = materialized_view.admin.inc
+files[] = materialized_view.sources.inc
+dependencies[] = field_sql_storage

=== added file 'modules/materialized_view/materialized_view.install'
--- modules/materialized_view/materialized_view.install	1970-01-01 00:00:00 +0000
+++ modules/materialized_view/materialized_view.install	2009-01-20 19:39:55 +0000
@@ -0,0 +1,81 @@
+<?php
+// $Id$
+
+/**
+ * Implementation of hook_install().
+ */
+function materialized_view_install() {
+  // Create module tables.
+  drupal_install_schema('materialized_view');
+  
+  // Create materialized view tables and bootstrap indexing.
+  // TK Can we permanently remove this?
+  // materialized_view_reconcile();
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function materialized_view_uninstall() {
+  // Remove all materialized view tables.
+  $materialized_views = materialized_view_get();
+  foreach ($materialized_views as $materialized_view) {
+    $materialized_view->uninstallSchema();
+  }
+
+  // Remove module tables.
+  drupal_uninstall_schema('materialized_view');
+}
+
+/**
+ * Implementation of hook_schema().
+ */
+function materialized_view_schema() {
+  $schema['materialized_view'] = array(
+    'description' => 'Stores information about materialized views.',
+    'fields' => array(
+      'mvid' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'description' => 'The primary identifier for a materialized view.',
+      ),
+      'schema_hash' => array(
+        'type' => 'char',
+        'length' => 40,
+        'not null' => TRUE,
+        'description' => 'SHA1 hash of the serialized schema array.',
+      ),
+    ),
+    'primary key' => array('mvid'),
+  );
+
+  $schema['materialized_view_indexing'] = array(
+    'description' => 'Stores information about the status of materialized view indexing.',
+    'fields' => array(
+      'mvid' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'description' => 'The primary identifier for a materialized view.',
+      ),
+      'entity' => array(
+        'type' => 'varchar',
+        'not null' => TRUE,
+        'length' => 32,
+        'default' => '',
+        'description' => 'The entity type being indexed.',
+      ),
+      'max_indexed_id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => 'The maximum ID currently indexed for this content type and materialized view.',
+      ),
+    ),
+    'primary key' => array('mvid', 'entity'),
+  );
+
+  return $schema;
+}

=== added file 'modules/materialized_view/materialized_view.module'
--- modules/materialized_view/materialized_view.module	1970-01-01 00:00:00 +0000
+++ modules/materialized_view/materialized_view.module	2009-01-20 19:39:55 +0000
@@ -0,0 +1,210 @@
+<?php
+
+require drupal_get_path('module', 'materialized_view') . '/materialized_view.class.inc';
+require drupal_get_path('module', 'materialized_view') . '/materialized_view.sources.inc';
+
+/**
+ * Implementation of hook_modules_enabled().
+ */
+function materialized_view_modules_enabled($modules) {
+  materialized_view_reconcile();
+}
+
+/**
+ * Implementation of hook_modules_disabled().
+ */
+function materialized_view_modules_disabled($modules) {
+
+  // Clean up MV tables from the disabled module(s).
+  foreach ($modules as $module) {
+    $function = $module . '_materialized_view_info';
+    if (function_exists($function)) {
+      $materialized_views = $function();
+      if (is_array($materialized_views)) {
+        foreach ($materialized_views as $materialized_view) {
+          $materialized_view->uninstallSchema();
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Implementation of hook_exit().
+ */
+function materialized_view_exit() {
+
+  // Load the job queue.
+  $jobs = _materialized_view_queue();
+
+  // Load the materialized views.
+  $materialized_views = materialized_view_get();
+
+  // Loop through every combination of job and materialized view.
+  foreach ($jobs as $job) {
+    foreach ($materialized_views as $materialized_view) {
+    
+      // Run the update; otherwise, delete.
+      if ($job->update) {
+        $materialized_view->update($job->entity, $job->entity_id);
+      }
+      else {
+        $materialized_view->delete($job->entity, $job->entity_id);      
+      }
+    }
+  }
+}
+
+/**
+ * Allow queueing of updates so they occur after all saves are complete.
+ */
+function _materialized_view_queue($entity = NULL, $entity_id = NULL, $update = TRUE) {
+  static $jobs = array();
+  
+  // If no entity is specified, return the contents of the queue.
+  if (!$entity) {
+    return $jobs;
+  }
+  
+  // Queue the new job.
+  $job = new stdClass();
+  $job->entity = $entity;
+  $job->entity_id = $entity_id;
+  $job->update = $update;
+  $jobs[] = $job;
+}
+
+/**
+ * Update all materialized views for the given object.
+ *
+ * @param $entity
+ *   The entity type ID.
+ * @param $entity_id
+ *   The entity ID.
+ */
+function materialized_view_update_all($entity, $entity_id) {
+  _materialized_view_queue($entity, $entity_id);
+}
+
+/**
+ * Delete the given object from all materialized views.
+ *
+ * @param $entity
+ *   The entity type ID.
+ * @param $entity_id
+ *   The entity ID.
+ */
+function materialized_view_delete_all($entity, $entity_id) {
+  _materialized_view_queue($entity, $entity_id, FALSE);
+}
+
+/**
+ * Check that all materialized views are registered for indexing
+ * and have up-to-date schema.
+ */
+function materialized_view_reconcile() {
+  // Get a list of all entity types.
+  // TODO: Implement; currently blocked by Field API
+
+  // Load the list of materialized views.
+  $materialized_views = materialized_view_get();
+  
+  foreach ($materialized_views as $materialized_view) {
+    // Load the mview record from the database.
+    // TODO: Use DB-TNG.
+    $mv_row = db_fetch_array(db_query('SELECT * FROM {materialized_view} WHERE mvid = "%s"', $materialized_view->getName()));
+    
+    // Check that any schema for this MV are installed.
+    if ($mv_row) {
+      // Check that schema are present and up-to-date.
+      $schema_hash = sha1(serialize($materialized_view->getSchema()));
+      if ($schema_hash != $mv_row->schema_hash) {
+        $materialized_view->uninstallSchema();
+        $materialized_view->installSchema();
+        
+        // Update the schema hash in the database.
+        db_update('materialized_view')
+          ->fields(array(
+            'schema_hash' => $schema_hash,
+          ))
+          ->condition('mvid', $materialized_view->getName())
+          ->execute();
+
+        // Reset all indexing by clearing all rows in the index tracker for this MV.
+        db_delete('materialized_view_index')
+          ->condition('mvid', $materialized_view->getName())
+          ->execute();
+      }
+    }
+    else {
+      // Install the schema for the first time.
+      $materialized_view->installSchema();
+
+      // Update schema has in the database
+      $schema_hash = sha1(serialize($materialized_view->getSchema()));      
+      db_update('materialized_view')
+        ->fields(array(
+          'schema_hash' => $schema_hash,
+        ))
+        ->condition('mvid', $materialized_view->getName())
+        ->execute();
+    }
+    
+    // Check that all entity types are registered
+    // for indexing against this view.
+    // TODO: Implement; currently blocked by Field API
+  }  
+  
+  // Remove tables and indexing data for views that no longer have registration.
+  $res = db_query('SELECT mvid FROM {materialized_view}');
+  while ($row = db_fetch_array($res)) {
+    if (!array_key_exists($row['mvid'], $materialized_views)) {
+      $ret = array();
+      if (db_table_exists($row->mvid)) {
+        db_drop_table($ret, $row->mvid);
+      }
+    }
+  }
+}
+
+/**
+ * Retrieve the object for a materialized view by name
+ * or retrieve an array of all materialized views. 
+ *
+ * @param $name
+ *   The name of the materialized view.
+ *   Pass NULL to get all materialized views.
+ */
+function materialized_view_get($name = NULL, $reset = FALSE) {
+  static $materialized_views = NULL;
+  
+  // Check if the set of materialized views is in the static cache.
+  if (is_null($materialized_views) || $reset) {
+    $materialized_views = array();
+    $hook_results = module_invoke_all('materialized_view_info');
+    
+    // Loop through materialized views and load them into the cache,
+    // keyed by name.
+    foreach ($hook_results as $hook_result) {
+      $materialized_views[$hook_result->getName()] = $hook_result;
+    }
+  }
+  
+  // Return just one MV object if a name was specified.
+  if (!is_null($name)) {
+    return $materialized_views[$name];
+  }
+
+  return $materialized_views;
+}
+
+/**
+ * Implementation of hook_cron().
+ */
+function materialized_view_cron() {
+  // Use the materialized_view_indexing table to index objects.
+  $res = db_query('SELECT * FROM {materialized_view_index}');
+  while ($row = db_fetch_array($res)) {
+    // TODO: Implement
+  }
+}

=== added file 'modules/materialized_view/materialized_view.sources.inc'
--- modules/materialized_view/materialized_view.sources.inc	1970-01-01 00:00:00 +0000
+++ modules/materialized_view/materialized_view.sources.inc	2009-01-20 19:39:55 +0000
@@ -0,0 +1,73 @@
+<?php
+
+/*
+ * For field.module:
+ */
+
+class MVFieldColumn extends MVColumn {
+  protected $field;
+
+  public function __construct($field) {
+    $this->field = $field;
+  }
+
+  public function getValue($entity, $entity_id) {
+    // TK
+  }
+
+  public function getSchema() {
+    return _field_sql_storage_schema($field);
+  }
+
+  public function getName() {
+    // TK
+  }
+}
+
+/*
+ * For node.module:
+ */
+
+class MVNodeTitleColumn extends MVColumn {
+  public function getValue($entity, $entity_id) {
+    $node = node_load($entity_id, array(), TRUE);
+    return $node->title;
+  }
+
+  public function getSchema() {
+    $schema = drupal_get_schema_unprocessed('node', 'node');
+    return $schema['fields']['title'];
+  }
+
+  public function getName() {
+    return 'node_title';
+  }
+}
+
+class MVNodeStatusColumn extends MVColumn {
+  public function getValue($entity, $entity_id) {
+    $node = node_load($entity_id, array(), TRUE);
+    return $node->status;
+  }
+
+  public function getSchema() {
+    $schema = drupal_get_schema_unprocessed('node', 'node');
+    return $schema['fields']['status'];
+  }
+
+  public function getName() {
+    return 'node_status';
+  }
+}
+
+function materialized_view_nodeapi_insert($node) {
+  materialized_view_update_all(1, $node->nid);
+}
+
+function materialized_view_nodeapi_update($node) {
+  materialized_view_update_all(1, $node->nid);
+}
+
+function materialized_view_nodeapi_delete($node) {
+  materialized_view_delete_all(1, $node->nid);
+}

=== added file 'modules/materialized_view/materialized_view.test'
--- modules/materialized_view/materialized_view.test	1970-01-01 00:00:00 +0000
+++ modules/materialized_view/materialized_view.test	2009-01-20 19:39:54 +0000
@@ -0,0 +1,20 @@
+<?php
+
+class MVTestCase extends DrupalWebTestCase {
+  function getInfo() {
+    return array(
+      'name'  => t('Materialized View tests'),
+      'description'  => t('Test Materialized Views API.'),
+      'group' => t('Materialized View'),
+    );
+  }
+
+  function setUp() {
+    parent::setUp('field_sql_storage', 'materialized_view');
+  }
+
+  function testBasicStaticFilter() {
+
+  }
+
+}

=== modified file 'modules/node/node.info'
--- modules/node/node.info	2008-12-06 09:01:58 +0000
+++ modules/node/node.info	2009-01-20 19:39:55 +0000
@@ -9,4 +9,5 @@ files[] = content_types.inc
 files[] = node.admin.inc
 files[] = node.pages.inc
 files[] = node.install
+dependencies[] = field
 required = TRUE

=== modified file 'modules/node/node.module'
--- modules/node/node.module	2009-01-17 21:00:12 +0000
+++ modules/node/node.module	2009-01-20 19:39:55 +0000
@@ -152,6 +152,49 @@ function node_cron() {
 }
 
 /**
+ * Implementation of hook_fieldable_info().
+ */
+function node_fieldable_info() {
+  $return = array(
+    'node' => array(
+      'name' => t('Node'),
+      'id key' => 'nid',
+      'revision key' => 'vid',
+      'bundle key' => 'type',
+      // Node.module handles its own caching.
+      // 'cacheable' => FALSE,
+      // Bundles must provide human readable name so
+      // we can create help and error messages about them.
+      'bundles' => node_get_types('names'),
+    ),
+  );
+  return $return;
+}
+
+
+/**
+ * Implementation of hook_field_build_modes().
+ */
+function node_field_build_modes($obj_type) {
+  $modes = array();
+  if ($obj_type == 'node') {
+    $modes = array(
+      'teaser' => t('Teaser'),
+      'full' => t('Full node'),
+      NODE_BUILD_RSS => t('RSS'),
+      NODE_BUILD_PRINT => t('Print'),
+    );
+    if (module_exists('search')) {
+      $modes += array(
+        NODE_BUILD_SEARCH_INDEX => t('Search Index'),
+        NODE_BUILD_SEARCH_RESULT => t('Search Result'),
+      );
+    }
+  }
+  return $modes;
+}
+
+/**
  * Gather a listing of links to nodes.
  *
  * @param $result
@@ -535,6 +578,10 @@ function node_type_save($info) {
   if ($is_existing) {
     db_update('node_type')->fields($fields)->condition('type', $existing_type)->execute();
 
+    if (!empty($type->old_type) && $type->old_type != $type->type) {
+      drupal_function_exists('field_attach_rename_bundle');
+      field_attach_rename_bundle($type->old_type, $type->type);
+    }
     module_invoke_all('node_type', 'update', $type);
     return SAVED_UPDATED;
   }
@@ -542,6 +589,9 @@ function node_type_save($info) {
     $fields['orig_type'] = (string) $type->orig_type;
     db_insert('node_type')->fields($fields)->execute();
 
+    drupal_function_exists('field_attach_create_bundle');
+    field_attach_create_bundle($type->type);
+
     module_invoke_all('node_type', 'insert', $type);
     return SAVED_NEW;
   }
@@ -859,6 +909,10 @@ function node_load_multiple($nids = arra
       }
     }
 
+    // Attach fields.
+    drupal_function_exists('field_attach_load');
+    field_attach_load('node', $queried_nodes, $vid ? FIELD_LOAD_REVISION : FIELD_LOAD_CURRENT);
+
     // Call hook_nodeapi_load(), pass the node types so modules can return early
     // if not acting on types in the array.
     foreach (module_implements('nodeapi_load') as $module) {
@@ -939,6 +993,9 @@ function node_validate($node, $form = ar
     }
   }
 
+  // Validate fields
+  module_invoke('field', 'attach_validate', 'node', $node, $form);
+
   // Do node-type-specific validation checks.
   node_invoke($node, 'validate', $form);
   node_invoke_nodeapi($node, 'validate', $form);
@@ -949,10 +1006,13 @@ function node_validate($node, $form = ar
  */
 function node_submit($node) {
   global $user;
-
+  
   // Convert the node to an object, if necessary.
   $node = (object)$node;
 
+  drupal_function_exists('field_attach_presave');
+  field_attach_presave('node', $node);
+
   // Generate the teaser, but only if it hasn't been set (e.g. by a
   // module-provided 'teaser' form item).
   if (!isset($node->teaser)) {
@@ -1066,6 +1126,12 @@ function node_save(&$node) {
   // node_invoke($node, 'insert') or
   // node_invoke($node, 'update').
   node_invoke($node, $op);
+
+  // Save fields.
+  $function = "field_attach_$op";
+  drupal_function_exists($function);
+  $function('node', $node);
+
   node_invoke_nodeapi($node, $op);
 
   // Update the node access table for this node.
@@ -1199,9 +1265,13 @@ function node_build_content($node, $teas
     $node = node_prepare($node, $teaser);
   }
 
+  // Build fields content.
+  drupal_function_exists('field_attach_view');
+  $node->content += field_attach_view('node', $node, $teaser);
+
   // Allow modules to make their own additions to the node.
   node_invoke_nodeapi($node, 'view', $teaser);
-  
+
   // Allow modules to modify the structured node.
   drupal_alter('node_view', $node, $teaser);
 
@@ -2384,10 +2454,10 @@ function node_query_node_access_alter(Qu
       if (count($or->conditions())) {
         $query->condition($or);
       }
-      
+
       $query->condition("{$access_alias}.grant_$op", 1, '>=');
     }
-  } 
+  }
 }
 
 /**
@@ -3019,7 +3089,7 @@ function node_list_permissions($type) {
  */
 function node_elements() {
   $type['node_links'] = array();
-  
+
   return $type;
 }
 

=== modified file 'modules/node/node.pages.inc'
--- modules/node/node.pages.inc	2008-12-30 17:00:08 +0000
+++ modules/node/node.pages.inc	2009-01-20 19:39:56 +0000
@@ -257,7 +257,11 @@ function node_form(&$form_state, $node) 
     );
   }
   $form['#validate'][] = 'node_form_validate';
-  $form['#theme'] = array($node->type . '_node_form', 'node_form');
+  $form['#theme'] = array($node->type . '_node_form', 'node_form');
+
+  drupal_function_exists('field_attach_form');
+  field_attach_form('node', $node, $form, $form_state);
+
   return $form;
 }
 

=== modified file 'modules/node/node.tpl.php'
--- modules/node/node.tpl.php	2008-12-31 13:00:08 +0000
+++ modules/node/node.tpl.php	2009-01-20 19:39:55 +0000
@@ -20,6 +20,7 @@
  * - $terms: the themed list of taxonomy term links output from theme_links().
  * - $submitted: themed submission information output from
  *   theme_node_submitted().
+ * TODO D7 : document $FIELD_NAME_rendered variables.
  *
  * Other variables:
  * - $node: Full node object. Contains data that may not be safe.

=== modified file 'modules/simpletest/drupal_web_test_case.php'
--- modules/simpletest/drupal_web_test_case.php	2009-01-19 11:00:08 +0000
+++ modules/simpletest/drupal_web_test_case.php	2009-01-20 19:39:55 +0000
@@ -157,6 +157,10 @@ class DrupalWebTestCase {
       $status = $status ? 'pass' : 'fail';
     }
 
+    if ($status == 'fail') {
+      $DELETE_THIS_BLOCK = 1;
+    }
+
     // Increment summary result counter.
     $this->results['#' . $status]++;
 
@@ -829,7 +833,7 @@ class DrupalWebTestCase {
     // Add the specified modules to the list of modules in the default profile.
     $args = func_get_args();
     $modules = array_unique(array_merge(drupal_get_profile_modules('default', 'en'), $args));
-    drupal_install_modules($modules);
+    drupal_install_modules($modules, TRUE);
 
     // Because the schema is static cached, we need to flush
     // it between each run. If we don't, then it will contain
@@ -1996,4 +2000,58 @@ class DrupalWebTestCase {
     $match = is_array($code) ? in_array($curl_code, $code) : $curl_code == $code;
     return $this->assertTrue($match, $message ? $message : t('HTTP response expected !code, actual !curl_code', array('!code' => $code, '!curl_code' => $curl_code)), t('Browser'));
   }
+  
+  /**
+   * TODO write documentation.
+   * @param $type
+   * @param $field_name
+   * @param $settings
+   * @return unknown_type
+   */
+  protected function drupalCreateField($type, $field_name = NULL, $settings = array()) {
+    if (!isset($field_name)) {
+      $field_name = strtolower($this->randomName());
+    }
+    $field_definition = array(
+      'field_name' => $field_name,
+      'type' => $type,
+    );
+    $field_definition += $settings;
+    field_create_field($field_definition);
+    
+    $field = field_read_field($field_name);
+    $this->assertTrue($field, t('Created field @field_name of type @type.', array('@field_name' => $field_name, '@type' => $type)));
+    
+    return $field;
+  }
+  
+  /**
+   * TODO write documentation.
+   * @param $field_name
+   * @param $widget_type
+   * @param $display_type
+   * @param $bundle
+   * @return unknown_type
+   */
+  protected function drupalCreateFieldInstance($field_name, $widget_type, $formatter_type, $bundle) {
+    $instance_definition = array(
+      'field_name' => $field_name,
+      'bundle' => $bundle,
+      'widget' => array(
+        'type' => $widget_type,
+      ),
+      'display' => array(
+        'full' => array(
+          'type' => $formatter_type,
+        ),
+      ),
+    );
+    field_create_instance($instance_definition);
+    
+    $instance = field_read_instance($field_name, $bundle);
+    $this->assertTrue($instance, t('Created instance of field @field_name on bundle @bundle.', array('@field_name' => $field_name, '@bundle' => $bundle)));
+    
+    return $instance;
+  }
 }
+

=== added file 'modules/simpletest/tests/field_test.info'
--- modules/simpletest/tests/field_test.info	1970-01-01 00:00:00 +0000
+++ modules/simpletest/tests/field_test.info	2009-01-20 19:39:54 +0000
@@ -0,0 +1,9 @@
+;$Id$
+name = "Field API Test"
+description = "Support module for the Field API tests."
+core = 7.x
+package = testing
+dependencies[] = field
+files[] = field_test.module
+version = VERSION
+hidden = TRUE 
\ No newline at end of file

=== added file 'modules/simpletest/tests/field_test.module'
--- modules/simpletest/tests/field_test.module	1970-01-01 00:00:00 +0000
+++ modules/simpletest/tests/field_test.module	2009-01-20 19:39:54 +0000
@@ -0,0 +1,337 @@
+<?php
+// $Id$
+
+define('FIELD_TEST_ELEMENT_ID', 1);
+define('FIELD_TEST_BUNDLE', 'test_bundle');
+
+/**
+ * Implementation of hook_menu().
+ */
+function field_test_menu() {
+  $items = array();
+
+  $items['field_test/edit'] = array(
+    'title' => 'Edit test entity',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('field_test_edit_field_values'),
+    'access arguments' => array('access content'),
+    'type' => MENU_NORMAL_ITEM,
+  );
+
+  return $items;
+}
+
+/**
+ * Implementation of hook_theme().
+ */
+function field_test_theme() {
+  return array(
+    'field_formatter_field_test_default' => array(
+      'arguments' => array('element' => NULL),
+    ),
+    'field_formatter_field_test_multiple' => array(
+      'arguments' => array('element' => NULL),
+    ),
+  );
+}
+
+/**
+ * Define a test fieldable entity.
+ * This entity has only one instance.
+ */
+function field_test_fieldable_info() {
+  $bundles = variable_get('field_test_bundles', array('test_bundle' => 'Test Bundle'));
+  return array(
+    'field_test_entity' => array(
+      'name' => t('Test Entity'),
+      'id key' => 'ftid',
+      'revision key' => 'ftvid',
+      'cacheable' => FALSE,
+      'bundle key' => 'fttype',
+      'bundles' => $bundles,
+    ),
+    'field_test_cacheable_entity' => array(
+      'name' => t('Test Entity, cacheable'),
+      'id key' => 'ftid',
+      'revision key' => 'ftvid',
+      'cacheable' => TRUE,
+      'bundle key' => 'fttype',
+      'bundles' => $bundles,
+    ),
+  );
+}
+
+function field_test_create_bundle($bundle, $text) {
+  $bundles = variable_get('field_test_bundles', array('field_text_bundle' => 'Test Bundle'));
+  $bundles += array($bundle => $text);
+  variable_set('field_test_bundles', $bundles);
+  field_attach_create_bundle($bundle);
+}
+
+function field_test_rename_bundle($bundle_old, $bundle_new) {
+  $bundles = variable_get('field_test_bundles', array('field_text_bundle' => 'Test Bundle'));
+  $bundles[$bundle_new] = $bundles[$bundle_old];
+  unset($bundles[$bundle_old]);
+  variable_set('field_test_bundles', $bundles);
+  field_attach_rename_bundle($bundle_old, $bundle_new);
+}
+
+function field_test_delete_bundle($bundle) {
+  $bundles = variable_get('field_test_bundles', array('field_text_bundle' => 'Test Bundle'));
+  unset($bundles[$bundle]);
+  variable_set('field_test_bundles', $bundles);
+  field_attach_delete_bundle($bundle);
+}
+
+/**
+ * Implementation of hook_field_build_modes().
+ */
+function field_test_field_build_modes($obj_type) {
+  $modes = array();
+  if ($obj_type == 'field_test_entity') {
+    $modes = array(
+      'full' => t('Full node'),
+      'teaser' => t('Teaser'),
+    );
+  }
+  return $modes;
+}
+
+function field_test_entity_get_entity($id = 1, $vid = 1, $bundle = FIELD_TEST_BUNDLE) {
+  $entity = new stdClass();
+  $entity->ftid = $id;
+  $entity->ftvid = $vid;
+  $entity->fttype = $bundle;
+
+  return $entity;
+}
+
+/**
+ * Form to set the value of fields attached to our entity.
+ */
+function field_test_edit_field_values() {
+  $form = array();
+
+  // Get attached fields/widgets.
+  $fields = field_info_instances(FIELD_TEST_BUNDLE);
+
+  foreach ($fields as $field) {
+    $form[$field->name] = field_get_widget($field);
+  }
+
+  return $form;
+}
+
+/**
+ * Submit handler for field_test_set_field_values().
+ */
+function field_test_edit_field_values_submit($form, &$form_state) {
+  field_set_value($form_state);
+}
+
+/**
+ * Get fields attached to our entity.
+ */
+function field_test_get_field_values() {
+  // Load attached fields.
+  $field_values = field_get_field_values('test_entity', FIELD_TEST_ELEMENT_ID);
+
+  return $field_values;
+}
+
+
+/**
+ * Implementation of hook_field_info().
+ *
+ * This field provides a textfield which only accepts the value 1.
+ */
+function field_test_field_info() {
+  return array(
+    'test_field' => array(
+      'label' => t('Test Field'),
+      'description' => t('Stores the value 1.'),
+      'settings' => array('test_field_setting' => 'dummy test string'),
+      'instance_settings' => array('test_instance_setting' => 'dummy test string'),
+      'default_widget' => 'test_field_widget',
+      'default_formatter' => 'field_test_default',
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_field_columns().
+ */
+function field_test_field_columns($field) {
+  $columns['value'] = array(
+    'type' => 'int',
+    'size' => 'tiny',
+    'not null' => FALSE,
+  );
+  return $columns;
+}
+
+/**
+ * Implementation of hook_instance_settings().
+ */
+function field_test_field_instance_settings($field_type) {
+  return array('test_instance_setting' => 'dummy test string');
+}
+
+/**
+ * Implementation of hook_field_validate().
+ */
+function field_test_field_validate(&$obj_type, $object, $field, $instance, &$items, $form) {
+  if (is_array($items)) {
+    foreach ($items as $delta => $item) {
+      $error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
+      if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
+      if ($item['value'] != 1) {
+        form_set_error($error_element, t('%name only accepts the value 1.', array('%name' => $instance['widget']['label'])));
+      }
+    }
+  }
+
+  return $items;
+}
+
+/**
+ * Implementation of hook_field_sanitize().
+ */
+function field_test_field_sanitize($obj_type, $object, $field, $instance, &$items) {
+  foreach ($items as $delta => $item) {
+    $value = check_plain($item['value']);
+    $items[$delta]['safe'] = $value;
+  }
+}
+
+/**
+ * Implementation of hook_field_is_empty().
+ */
+function field_test_field_is_empty($item, $field) {
+  return empty($item['value']);
+}
+
+/**
+ * Implementation of hook_field_widget_info().
+ *
+ * Here we indicate that the content module will handle
+ * the default value and multiple values for these widgets.
+ *
+ * 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 field_test_field_widget_info() {
+  return array(
+    'test_field_widget' => array(
+      'label' => t('Test field'),
+      'field types' => array('test_field'),
+      'settings' => array('test_widget_setting' => 'dummy test string'),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+        'default value' => FIELD_BEHAVIOR_DEFAULT,
+      ),
+    ),
+    'test_field_widget_multiple' => array(
+      'label' => t('Test field 1'),
+      'field types' => array('test_field'),
+      'settings' => array('test_widget_setting' => 'dummy test string'),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_CUSTOM,
+        'default value' => FIELD_BEHAVIOR_DEFAULT,
+      ),
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_field_widget().
+ *
+ * Attach a single form element to the form. It will be built out and
+ * validated in the callback(s) listed in hook_elements. We build it
+ * out in the callbacks rather than here in hook_widget so it can be
+ * plugged into any module that can provide it with valid
+ * $field information.
+ *
+ * Content module will set the weight, field name and delta values
+ * for each form element. This is a change from earlier CCK versions
+ * where the widget managed its own multiple values.
+ *
+ * If there are multiple values for this field, the content module will
+ * call this function as many times as needed.
+ *
+ * @param $form
+ *   the entire form array, $form['#node'] holds node information
+ * @param $form_state
+ *   the form_state, $form_state['values'][$field['field_name']]
+ *   holds the field's form values.
+ * @param $insatnce
+ *   the insatnce array
+ * @param $items
+ *   array of default values for this field
+ * @param $delta
+ *   the order of this item in the array of subelements (0, 1, 2, etc)
+ *
+ * @return
+ *   the form item for a single element for this field
+ */
+function field_test_field_widget(&$form, &$form_state, $instance, $items, $delta = 0) {
+  $element = array(
+    '#type' => 'textfield',
+    '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
+  );
+  return $element;
+}
+
+/**
+ * Implementation of hook_field_formatter_info().
+ */
+function field_test_field_formatter_info() {
+  return array(
+    'field_test_default' => array(
+      'label' => t('Default'),
+      'field types' => array('test_field'),
+      'settings' => array(
+        'test_formatter_setting' => 'dummy test string',
+      ),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+      ),
+    ),
+    'field_test_multiple' => array(
+      'label' => t('Default'),
+      'field types' => array('test_field'),
+      'settings' => array(
+        'test_formatter_setting_multiple' => 'dummy test string',
+      ),
+      'behaviors' => array(
+        'multiple values' => FIELD_BEHAVIOR_CUSTOM,
+      ),
+    ),
+  );
+}
+
+/**
+ * Theme function for 'field_test_default' formatter.
+ */
+function theme_field_formatter_field_test_default($element) {
+  $value = $element['#item']['value'];
+  $settings = $element['#settings'];
+
+  return $settings['test_formatter_setting'] . '|' . $value;
+}
+
+/**
+ * Theme function for 'field_test_multiple' formatter.
+ */
+function theme_field_formatter_field_test_multiple($element) {
+  $settings = $element['#settings'];
+
+  $items = array();
+  foreach (element_children($element) as $key) {
+    $items[$key] = $key .':'. $element[$key]['#item']['value'];
+  }
+  $output = implode('|', $items);
+  return $settings['test_formatter_setting_multiple'] . '|' . $output;
+}
\ No newline at end of file

=== modified file 'modules/system/system.admin.inc'
--- modules/system/system.admin.inc	2009-01-19 22:00:08 +0000
+++ modules/system/system.admin.inc	2009-01-20 19:39:55 +0000
@@ -578,14 +578,7 @@ function system_modules($form_state = ar
   cache_clear_all('schema', 'cache');
   // Get current list of modules.
   $files = module_rebuild_cache();
-
-  // Remove hidden modules from display list.
-  foreach ($files as $filename => $file) {
-    if (!empty($file->info['hidden']) || !empty($file->info['required'])) {
-      unset($files[$filename]);
-    }
-  }
-
+  
   uasort($files, 'system_sort_modules_by_info_name');
 
   // If the modules form was submitted, then system_modules_submit() runs first
@@ -604,6 +597,10 @@ function system_modules($form_state = ar
 
   // Iterate through each of the modules.
   foreach ($files as $filename => $module) {
+    // Do not display hidden modules.
+    if (!empty($module->info['hidden']) || !empty($module->info['required'])) {
+      continue; 
+    }
     $extra = array();
     $extra['enabled'] = (bool) $module->status;
     // If this module requires other modules, add them to the array.

=== modified file 'modules/user/user-profile.tpl.php'
--- modules/user/user-profile.tpl.php	2008-12-06 09:01:58 +0000
+++ modules/user/user-profile.tpl.php	2009-01-20 19:39:55 +0000
@@ -38,7 +38,8 @@
  * Available variables:
  *   - $user_profile: All user profile data. Ready for print.
  *   - $profile: Keyed array of profile categories and their items or other data
- *     provided by modules.
+ *     provided by modules.
+ *   - TODO D7 : document $FIELD_NAME_rendered variables.
  *
  * @see template_preprocess_user_profile()
  */

=== modified file 'modules/user/user.module'
--- modules/user/user.module	2009-01-20 04:00:09 +0000
+++ modules/user/user.module	2009-01-20 19:39:55 +0000
@@ -83,6 +83,39 @@ function user_theme() {
   );
 }
 
+/**
+ * Implementation of hook_fieldable_info().
+ */
+function user_fieldable_info() {
+  $return = array(
+    'user' => array(
+      'name' => t('User'),
+      'id key' => 'uid',
+    ),
+  );
+  return $return;
+}
+
+/**
+ * Implementation of hook_field_build_modes().
+ */
+function user_field_build_modes($obj_type) {
+  $modes = array();
+  if ($obj_type == 'user') {
+    $modes = array(
+      'basic' => array(
+        'title' => t('Basic'),
+        'build modes' => array(
+          'full' => array(
+            'title' => t('User account'),
+          ),
+        ),
+      ),
+    );
+  }
+  return $modes;
+}
+
 function user_external_load($authname) {
   $result = db_query("SELECT uid FROM {authmap} WHERE authname = '%s'", $authname);
 
@@ -187,6 +220,12 @@ function user_load($array = array()) {
       $user->roles[$role->rid] = $role->name;
     }
 
+    // Attach fields.
+    // TODO D7 : not sure the 3rd param ($types) is needed.
+    if (drupal_function_exists('field_attach_load')) {
+      field_attach_load('user', array($user->uid => $user));
+    }
+
     if (!empty($user->picture) && ($file = file_load($user->picture))) {
       $user->picture = $file;
     }
@@ -241,6 +280,20 @@ function user_save($account, $edit = arr
     unset($edit['pass']);
   }
 
+  // Get the fields form so we can recognize the fields in the $edit
+  // form that should not go into the serialized data array.
+  $field_form = array();
+  $field_form_state = array();
+  $edit = (object) $edit;
+  if (drupal_function_exists('field_attach_form')) {
+    field_attach_form('user', $edit, $field_form, $field_form_state);
+  }
+  // Presave fields.
+  if (drupal_function_exists('field_attach_presave')) {
+    field_attach_presave('user', $edit);
+  }
+  $edit = (array) $edit;
+
   if (is_object($account) && $account->uid) {
     user_module_invoke('update', $edit, $account, $category);
     $data = unserialize(db_result(db_query('SELECT data FROM {users} WHERE uid = %d', $account->uid)));
@@ -250,9 +303,10 @@ function user_save($account, $edit = arr
       $edit['access'] = REQUEST_TIME;
     }
     foreach ($edit as $key => $value) {
-      // Fields that don't pertain to the users or user_roles
-      // automatically serialized into the users.data column.
-      if ($key != 'roles' && empty($user_fields[$key])) {
+      // Form fields that don't pertain to the users, user_roles, or
+      // Field API are automatically serialized into the users.data
+      // column.
+      if ($key != 'roles' && empty($user_fields[$key]) && empty($field_form[$key])) {
         if ($value === NULL) {
           unset($data[$key]);
         }
@@ -284,8 +338,14 @@ function user_save($account, $edit = arr
     // Save changes to the users table.
     $success = drupal_write_record('users', $edit, 'uid');
     if (!$success) {
-      // The query failed - better to abort the save than risk further data loss.
-      return FALSE;
+      // The query failed - better to abort the save than risk further
+      // data loss.
+
+      // TODO: Fields change: I think this is a bug.  If no columns in
+      // the users table are changed, drupal_write_record returns
+      // FALSE because rowCount() (rows changed) is 0.  However,
+      // non-users data may have been changed, e.g. fields.
+      // return FALSE;
     }
 
     // If the picture changed or was unset, remove the old one. This step needs
@@ -320,6 +380,12 @@ function user_save($account, $edit = arr
       }
     }
 
+    // Save Field data.
+    $obj = (object) $edit;
+    if (drupal_function_exists('field_attach_update')) {
+      field_attach_update('user', $obj);
+    }
+
     // Refresh user object.
     $user = user_load(array('uid' => $account->uid));
 
@@ -353,13 +419,21 @@ function user_save($account, $edit = arr
     // Build the initial user object.
     $user = user_load(array('uid' => $edit['uid']));
 
+    $obj = (object) $edit;
+    if (drupal_function_exists('field_attach_insert')) {
+      field_attach_insert('user', $obj);
+    }
+
     user_module_invoke('insert', $edit, $user, $category);
 
     // Note, we wait with saving the data column to prevent module-handled
     // fields from being saved there.
     $data = array();
     foreach ($edit as $key => $value) {
-      if (($key != 'roles') && (empty($user_fields[$key])) && ($value !== NULL)) {
+      // Form fields that don't pertain to the users, user_roles, or
+      // Field API are automatically serialized into the users.data
+      // column.
+      if (($key != 'roles') && (empty($user_fields[$key]) && empty($field_form[$key])) && ($value !== NULL)) {
         $data[$key] = $value;
       }
     }
@@ -1588,6 +1662,8 @@ function user_edit_form(&$form_state, $u
   _user_password_dynamic_validation();
   $admin = user_access('administer users');
 
+  $form = array();
+
   // Account information:
   $form['account'] = array('#type' => 'fieldset',
     '#title' => t('Account information'),
@@ -1823,7 +1899,17 @@ function _user_cancel($edit, $account, $
  */
 function user_build_content(&$account) {
   $edit = NULL;
+  $account->content = array();
+
+  // Build fields content.
+  // TODO D7 : figure out where exactly this needs to go
+  // TODO D7 : $page / $teaser ??
+  if (drupal_function_exists('field_attach_view')) {
+    $account->content += field_attach_view('user', $account);
+  }
+
   user_module_invoke('view', $edit, $account);
+
   // Allow modules to modify the fully-built profile.
   drupal_alter('profile', $account);
 

=== modified file 'modules/user/user.pages.inc'
--- modules/user/user.pages.inc	2009-01-08 09:00:13 +0000
+++ modules/user/user.pages.inc	2009-01-20 19:39:55 +0000
@@ -174,6 +174,11 @@ function template_preprocess_user_profil
   }
   // Collect all profiles to make it easier to print all items at once.
   $variables['user_profile'] = implode($variables['profile']);
+
+  // Add $FIELD_NAME_rendered variables for fields.
+  if (drupal_function_exists('field_attach_preprocess')) {
+    $variables += field_attach_preprocess('user', $variables['account']);
+  }
 }
 
 /**
@@ -236,6 +241,12 @@ function user_profile_form($form_state, 
   $edit = (empty($form_state['values'])) ? (array)$account : $form_state['values'];
 
   $form = _user_forms($edit, $account, $category);
+
+  // Attach field widgets.
+  if (drupal_function_exists('field_attach_form')) {
+    field_attach_form('user', (object) $edit, $form, $form_state);
+  }
+
   $form['_category'] = array('#type' => 'value', '#value' => $category);
   $form['_account'] = array('#type' => 'value', '#value' => $account);
   $form['submit'] = array('#type' => 'submit', '#value' => t('Save'), '#weight' => 30);
@@ -256,6 +267,9 @@ function user_profile_form($form_state, 
  * Validation function for the user account and profile editing form.
  */
 function user_profile_form_validate($form, &$form_state) {
+  // Validate field widgets.
+  module_invoke('field', 'attach_validate', 'user', (object) $form_state['values'], $form, $form_state);
+  
   user_module_invoke('validate', $form_state['values'], $form_state['values']['_account'], $form_state['values']['_category']);
   // Validate input to ensure that non-privileged users can't alter protected data.
   if ((!user_access('administer users') && array_intersect(array_keys($form_state['values']), array('uid', 'init', 'session'))) || (!user_access('administer permissions') && isset($form_state['values']['roles']))) {

=== added file 'scripts/generate-autoload.pl'
--- scripts/generate-autoload.pl	1970-01-01 00:00:00 +0000
+++ scripts/generate-autoload.pl	2009-01-20 19:39:55 +0000
@@ -0,0 +1,72 @@
+#!/usr/bin/perl
+#
+# Usage: perl generate-autoload.pl [ file ... ] > autoload.inc
+#
+# Generate function autoloaders for a subset of functions in all the
+# listed files.  The autoloaders are written to standard output and are
+# of the form:
+#
+# /**
+#  * PHPdoc (not required)
+#  */
+# function funcname(&$arg1, $arg2 = DEFAULT, ...) {
+#   if (drupal_function_exists('_funcname')) {
+#     return _funcname($arg1, $arg, ...);
+#   }
+#   throw new FieldException('cannot autoload function _$func');
+# }
+#
+# Autoloaders are generated for any function name that matches the
+# $func_re regexp.  The autoloaded functions are REQUIRED to have a
+# leading underscore in the name, and the autoloading functions will
+# not.
+#
+# TO DO:
+# * option for whether to/type of Exception to throw for load failure
+# * option to control @defgroup for PHPdoc
+# * option to take function name regexps on the command line
+
+use strict;
+
+# Edit this regexp to match any function names for which you want
+# autoloaders.  Do not include the leading _.
+my $func_re = qr/field_attach_.*/;
+
+my $re = qr|
+  (?:(^/\*\*\s*\n			# start PHPdoc
+  (?:^\s*\*.*\n)*			# any number of comment lines
+  ^\s*\*/\s*\n))?			# end of PHPdoc
+  ^function\s*_($func_re)(\(.*?\))	# function signature
+|xm;
+
+print "<?php
+/**
+ * DO NOT EDIT THIS FILE.  It contains autoloading functions generated
+ * automatically by generate-autoload.pl.  The function names,
+ * arguments, and PHPdoc are copied from the file that defines the
+ * underlying function. To edit them, edit the underlying file instead.
+ */
+";
+
+while (my $file = shift @ARGV) {
+    open(F, $file) || die "cannot read $file: $!";
+    my $body = join('', <F>);
+    while ($body =~ /$re/g) {
+	my($doc, $func, $func_args) = ($1, $2, $3);
+	my $pass_args = $func_args;
+	# remove &'s from passed arguments
+	$pass_args =~ s/\&//g;
+	# remove default values from passed arguments
+	$pass_args =~ s/\s*=.*?(?=[,\)])//g;
+	# $doc contains a leading and trailing newline.
+	# $pass_args is wrapped in parens
+	print "
+${doc}function $func$func_args {
+  if (drupal_function_exists('_$func')) {
+    return _$func$pass_args;
+  }
+  throw new FieldException('cannot autoload function _$func');
+}
+";
+    }
+}

=== added directory 'sites/all/modules'
