Commit 9fd65b66 authored by Dries's avatar Dries

- Patch #392696 by yched et al: save default values on insert.

parent 81c436fb
......@@ -183,10 +183,9 @@ function _field_invoke($op, $obj_type, &$object, &$a = NULL, &$b = NULL, $defaul
$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 @@ function _field_attach_presave($obj_type, &$object) {
}
/**
* 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 @@ function _field_attach_update($obj_type, &$object) {
// 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) {
......
......@@ -185,13 +185,20 @@ function field_attach_presave($obj_type, &$object) {
}
/**
* 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.
*/
......
......@@ -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 @@ function field_default_extract_form_values($obj_type, $object, $field, $instance
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});
}
}
......@@ -48,6 +39,24 @@ function field_default_submit($obj_type, &$object, $field, $instance, &$items, $
$items = field_set_empty($field, $items);
}
/**
* 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.
......
......@@ -22,9 +22,8 @@ function field_default_form($obj_type, $object, $field, $instance, $items, &$for
$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;
}
......
......@@ -116,39 +116,86 @@ class FieldAttachTestCase extends DrupalWebTestCase {
*/
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;
field_attach_load($entity_type, array(0 => $entity));
$this->assertEqual($entity->{$this->field_name}[0]['value'], $value, 'Field missing data survives');
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
$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);
field_attach_load($entity_type, array(0 => $entity));
$this->assertTrue($entity->{$this->field_name} == NULL, 'NULL field update');
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
$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() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment