### Eclipse Workspace Patch 1.0
#P drupal_test_7
Index: modules/field/field.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.test,v
retrieving revision 1.15
diff -u -r1.15 field.test
--- modules/field/field.test	30 Apr 2009 15:17:13 -0000	1.15
+++ modules/field/field.test	30 Apr 2009 19:37:14 -0000
@@ -116,39 +116,86 @@
    */
   function testFieldAttachSaveMissingData() {
     $entity_type = 'test_entity';
-    $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+    $entity_init = field_test_create_stub_entity();
 
     // Insert: Field is missing.
+    $entity = clone($entity_init);
     field_attach_insert($entity_type, $entity);
-    field_attach_load($entity_type, array(0 => $entity));
-    $this->assertEqual(count($entity->{$this->field_name}), 0, 'Missing field results in no inserts');
+
+    $entity = clone($entity_init);
+    field_attach_load($entity_type, array($entity->ftid => $entity));
+    $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: missing field results in no value saved'));
 
     // Insert: Field is NULL.
+    field_cache_clear();
+    $entity = clone($entity_init);
     $entity->{$this->field_name} = NULL;
     field_attach_insert($entity_type, $entity);
-    field_attach_load($entity_type, array(0 => $entity));
-    $this->assertTrue($entity->{$this->field_name} == NULL, 'NULL field results in no inserts');
 
-    // Add some real data
-    $value = 5;
-    $entity->{$this->field_name} = array(0 => array('value' => $value));
+    $entity = clone($entity_init);
+    field_attach_load($entity_type, array($entity->ftid => $entity));
+    $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: NULL field results in no value saved'));
+
+    // Add some real data.
+    field_cache_clear();
+    $entity = clone($entity_init);
+    $values = array(0 => array('value' => mt_rand(1, 127)));
+    $entity->{$this->field_name} = $values;
     field_attach_insert($entity_type, $entity);
-    $entity->{$this->field_name} = NULL;
-    field_attach_load($entity_type, array(0 => $entity));
-    $this->assertTrue($entity->{$this->field_name}[0]['value'] == $value, 'Field data saved');
+
+    $entity = clone($entity_init);
+    field_attach_load($entity_type, array($entity->ftid => $entity));
+    $this->assertEqual($entity->{$this->field_name}, $values, t('Field data saved'));
 
     // Update: Field is missing. Data should survive.
-    unset($entity->{$this->field_name});
+    field_cache_clear();
+    $entity = clone($entity_init);
     field_attach_update($entity_type, $entity);
-    $entity->{$this->field_name} = NULL;
+
+    $entity = clone($entity_init);
     field_attach_load($entity_type, array(0 => $entity));
-    $this->assertEqual($entity->{$this->field_name}[0]['value'], $value, 'Field missing data survives');
+    $this->assertEqual($entity->{$this->field_name}, $values, t('Update: missing field leaves existing values in place'));
 
     // Update: Field is NULL. Data should be wiped.
+    field_cache_clear();
+    $entity = clone($entity_init);
     $entity->{$this->field_name} = NULL;
     field_attach_update($entity_type, $entity);
+
+    $entity = clone($entity_init);
     field_attach_load($entity_type, array(0 => $entity));
-    $this->assertTrue($entity->{$this->field_name} == NULL, 'NULL field update');
+    $this->assertTrue(empty($entity->{$this->field_name}), t('Update: NULL field removes existing values'));
+  }
+
+  /**
+   * Test insert with missing or NULL fields, with default value.
+   */
+  function testFieldAttachSaveMissingDataDefaultValue() {
+    // Add a default value.
+    $this->instance['default_value_function'] = 'field_test_default_value';
+    field_update_instance($this->instance);
+
+    $entity_type = 'test_entity';
+    $entity_init = field_test_create_stub_entity();
+
+    // Insert: Field is NULL.
+    $entity = clone($entity_init);
+    $entity->{$this->field_name} = NULL;
+    field_attach_insert($entity_type, $entity);
+
+    $entity = clone($entity_init);
+    field_attach_load($entity_type, array($entity->ftid => $entity));
+    $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: NULL field results in no value saved'));
+
+    // Insert: Field is missing.
+    field_cache_clear();
+    $entity = clone($entity_init);
+    field_attach_insert($entity_type, $entity);
+
+    $entity = clone($entity_init);
+    field_attach_load($entity_type, array($entity->ftid => $entity));
+    $values = field_test_default_value($entity_type, $entity, $this->field, $this->instance);
+    $this->assertEqual($entity->{$this->field_name}, $values, t('Insert: missing field results in default value saved'));
   }
 
   function testFieldAttachViewAndPreprocess() {
Index: modules/field/field.attach.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.attach.inc,v
retrieving revision 1.12
diff -u -r1.12 field.attach.inc
--- modules/field/field.attach.inc	30 Apr 2009 14:41:41 -0000	1.12
+++ modules/field/field.attach.inc	30 Apr 2009 19:37:12 -0000
@@ -183,10 +183,9 @@
         $return[] = $result;
       }
     }
-    // Put back the altered items in the object, if the field was present to
-    // begin with (avoid replacing missing field with empty array(), those are
-    // not semantically equivalent on update).
-    if (isset($object->$field_name)) {
+    // Populate $items back in the field values, but avoid replacing missing
+    // fields with an empty array (those are not equivalent on update).
+    if ($items !== array() || property_exists($object, $field_name)) {
       $object->$field_name = $items;
     }
   }
@@ -500,28 +499,35 @@
 }
 
 /**
- * Save field data for a new object. The passed in object must
- * already contain its id and (if applicable) revision id attributes.
+ * Save field data for a new object.
+ *
+ * The passed in object must already contain its id and (if applicable)
+ * revision id attributes.
+ * Default values (if any) will be saved for fields not present in the
+ * $object.
  *
  * @param $obj_type
  *   The type of $object; e.g. 'node' or 'user'.
  * @param $object
  *   The object with fields to save.
+ * @return
+ *   Default values (if any) will be added to the $object parameter for fields
+ *   it leaves unspecified.
  */
 function _field_attach_insert($obj_type, &$object) {
-
+  _field_invoke_default('insert', $obj_type, $object);
   _field_invoke('insert', $obj_type, $object);
 
   // Let other modules act on inserting the object, accumulating saved
   // fields along the way.
-  $saved = array();
+  $skip_fields = array();
   foreach (module_implements('field_attach_pre_insert') as $module) {
     $function = $module . '_field_attach_pre_insert';
-    $function($obj_type, $object, $saved);
+    $function($obj_type, $object, $skip_fields);
   }
 
   // Field storage module saves any remaining unsaved fields.
-  module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_write', $obj_type, $object, FIELD_STORAGE_INSERT, $saved);
+  module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_write', $obj_type, $object, FIELD_STORAGE_INSERT, $skip_fields);
 
   list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
   if ($cacheable) {
@@ -543,14 +549,14 @@
 
   // Let other modules act on updating the object, accumulating saved
   // fields along the way.
-  $saved = array();
+  $skip_fields = array();
   foreach (module_implements('field_attach_pre_update') as $module) {
     $function = $module . '_field_attach_pre_update';
-    $function($obj_type, $object, $saved);
+    $function($obj_type, $object, $skip_fields);
   }
 
   // Field storage module saves any remaining unsaved fields.
-  module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_write', $obj_type, $object, FIELD_STORAGE_UPDATE, $saved);
+  module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_write', $obj_type, $object, FIELD_STORAGE_UPDATE, $skip_fields);
 
   list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
   if ($cacheable) {
Index: modules/field/field.default.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.default.inc,v
retrieving revision 1.5
diff -u -r1.5 field.default.inc
--- modules/field/field.default.inc	26 Mar 2009 13:31:24 -0000	1.5
+++ modules/field/field.default.inc	30 Apr 2009 19:37:13 -0000
@@ -3,11 +3,12 @@
 
 /**
  * @file
- * Default 'implementations' of hook_field_*().
+ * Default 'implementations' of hook_field_*(): common field housekeeping.
  *
- * 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.
+ * They are called through the _field_invoke_default() iterator, generally in
+ * the corresponding field_attach_[operation]() function.
  */
 
 function field_default_extract_form_values($obj_type, $object, $field, $instance, &$items, $form, &$form_state) {
@@ -15,18 +16,8 @@
 
   if (isset($form_state['values'][$field_name])) {
     $items = $form_state['values'][$field_name];
-
     // Remove the 'value' of the 'add more' button.
     unset($items[$field_name . '_add_more']);
-
-    // _field_invoke() does not add back items for fields not present in the
-    // original $object, so we add them manually.
-    $object->{$field_name} = $items;
-  }
-  else {
-    // The form did not include this field, for instance because of access
-    // rules: make sure any existing value for the field stays unchanged.
-    unset($object->{$field_name});
   }
 }
 
@@ -49,6 +40,24 @@
 }
 
 /**
+ * Default field 'insert' operation.
+ *
+ * Insert default value if no $object->$field_name entry was provided.
+ * This can happen with programmatic saves, or on form-based creation where
+ * the current user doesn't have 'edit' permission for the field.
+ */
+function field_default_insert($obj_type, &$object, $field, $instance, &$items) {
+  // _field_invoke() populates $items with an empty array if the $object has no
+  // entry for the field, so we check on the $object itself.
+  if (!property_exists($object, $field['field_name']) && !empty($instance['default_value_function'])) {
+    $function = $instance['default_value_function'];
+    if (drupal_function_exists($function)) {
+      $items = $function($obj_type, $object, $field, $instance);
+    }
+  }
+}
+
+/**
  * 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']);
Index: modules/field/field.form.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.form.inc,v
retrieving revision 1.6
diff -u -r1.6 field.form.inc
--- modules/field/field.form.inc	26 Mar 2009 13:31:24 -0000	1.6
+++ modules/field/field.form.inc	30 Apr 2009 19:37:13 -0000
@@ -22,9 +22,8 @@
   $field_name = $field['field_name'];
 
   // If the field is not accessible, don't add anything. The field value will
-  // be left unchanged on update, or considered empty on insert.
-  // TODO : if/when field_attach_insert() takes care of default values,
-  // unaccessible fields will automatically get the default value on insert.
+  // be left unchanged on update, or considered empty on insert (default value
+  // will be inserted if applicable).
   if (!field_access('edit', $field)) {
     return $addition;
   }
Index: modules/field/field.autoload.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.autoload.inc,v
retrieving revision 1.6
diff -u -r1.6 field.autoload.inc
--- modules/field/field.autoload.inc	26 Mar 2009 13:31:24 -0000	1.6
+++ modules/field/field.autoload.inc	30 Apr 2009 19:37:13 -0000
@@ -185,13 +185,20 @@
 }
 
 /**
- * Save field data for a new object. The passed in object must
- * already contain its id and (if applicable) revision id attributes.
+ * Save field data for a new object.
+ *
+ * The passed in object must already contain its id and (if applicable)
+ * revision id attributes.
+ * Default values (if any) will be inserted for fields not present in the
+ * $object.
  *
  * @param $obj_type
  *   The type of $object; e.g. 'node' or 'user'.
  * @param $object
  *   The object with fields to save.
+ * @return
+ *   Default values (if any) will be added to the $object parameter for fields
+ *   it leaves unspecified.
  *
  * This function is an autoloader for _field_attach_insert() in modules/field/field.attach.inc.
  */
Index: modules/simpletest/tests/field_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/field_test.module,v
retrieving revision 1.4
diff -u -r1.4 field_test.module
--- modules/simpletest/tests/field_test.module	28 Apr 2009 05:24:41 -0000	1.4
+++ modules/simpletest/tests/field_test.module	30 Apr 2009 19:37:14 -0000
@@ -523,4 +523,11 @@
   }
   $output = implode('|', $items);
   return $settings['test_formatter_setting_multiple'] . '|' . $output;
+}
+
+/**
+ * Sample function to test default value assignment.
+ */
+function field_test_default_value($obj_type, $object, $field, $instance) {
+  return array(array('value' => 99));
 }
\ No newline at end of file
