diff options
-rw-r--r-- | modules/field/field.attach.inc | 32 | ||||
-rw-r--r-- | modules/field/field.autoload.inc | 11 | ||||
-rw-r--r-- | modules/field/field.default.inc | 33 | ||||
-rw-r--r-- | modules/field/field.form.inc | 5 | ||||
-rw-r--r-- | modules/field/field.test | 81 |
5 files changed, 115 insertions, 47 deletions
diff --git a/modules/field/field.attach.inc b/modules/field/field.attach.inc index 72f1d4f1b..b95d083d0 100644 --- a/modules/field/field.attach.inc +++ b/modules/field/field.attach.inc @@ -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) { diff --git a/modules/field/field.autoload.inc b/modules/field/field.autoload.inc index 9d848f65e..9ebe204f2 100644 --- a/modules/field/field.autoload.inc +++ b/modules/field/field.autoload.inc @@ -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. */ diff --git a/modules/field/field.default.inc b/modules/field/field.default.inc index 98d7cc371..74d853ade 100644 --- a/modules/field/field.default.inc +++ b/modules/field/field.default.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}); } } @@ -49,6 +40,24 @@ function field_default_submit($obj_type, &$object, $field, $instance, &$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. * i.e. print drupal_render($object->content['field_foo']); diff --git a/modules/field/field.form.inc b/modules/field/field.form.inc index b331e1fba..234eb69c2 100644 --- a/modules/field/field.form.inc +++ b/modules/field/field.form.inc @@ -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; } diff --git a/modules/field/field.test b/modules/field/field.test index 917654721..64a13f323 100644 --- a/modules/field/field.test +++ b/modules/field/field.test @@ -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() { |