diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php index 16b5a56..5af5328 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php @@ -444,6 +444,10 @@ public function create(array $values) { $entity->{$this->uuidKey} = $uuid->generate(); } + // Modules might need to add or change the data intially held by the new + // entity object, for instance to fill-in default values. + drupal_alter(array($this->entityType . '_entity', 'entity'), $entity); + return $entity; } diff --git a/core/modules/field/field.attach.inc b/core/modules/field/field.attach.inc index 1a006ec..e22bd5c 100644 --- a/core/modules/field/field.attach.inc +++ b/core/modules/field/field.attach.inc @@ -1172,7 +1172,6 @@ function field_attach_presave($entity_type, $entity) { * it leaves unspecified. */ function field_attach_insert($entity_type, EntityInterface $entity) { - _field_invoke_default('insert', $entity_type, $entity); _field_invoke('insert', $entity_type, $entity); // Let any module insert field data before the storage engine, accumulating diff --git a/core/modules/field/field.default.inc b/core/modules/field/field.default.inc index d37d0ac..9ac14bb 100644 --- a/core/modules/field/field.default.inc +++ b/core/modules/field/field.default.inc @@ -54,39 +54,6 @@ function field_default_validate($entity_type, $entity, $field, $instance, $langc } /** - * Inserts a default value if no $entity->$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. This is the - * default field 'insert' operation. - * - * @param $entity_type - * The type of $entity. - * @param $entity - * The entity for the operation. - * @param $field - * The field structure for the operation. - * @param $instance - * The instance structure for $field in $entity's bundle. - * @param $langcode - * The language associated with $items. - * @param $items - * An array that this function will populate with default values. - */ -function field_default_insert($entity_type, $entity, $field, $instance, $langcode, &$items) { - // _field_invoke() populates $items with an empty array if the $entity has no - // entry for the field, so we check on the $entity itself. - // We also check that the current field translation is actually defined before - // assigning it a default value. This way we ensure that only the intended - // languages get a default value. Otherwise we could have default values for - // not yet open languages. - if (empty($entity) || (!isset($entity->{$field['field_name']}[$langcode]) && !property_exists($entity, $field['field_name'])) || - (isset($entity->{$field['field_name']}[$langcode]) && count($entity->{$field['field_name']}[$langcode]) == 0)) { - $items = field_get_default_value($entity_type, $entity, $field, $instance, $langcode); - } -} - -/** * Copies source field values into the entity to be prepared. * * @param $entity_type diff --git a/core/modules/field/field.module b/core/modules/field/field.module index 01b0122..60de336 100644 --- a/core/modules/field/field.module +++ b/core/modules/field/field.module @@ -4,8 +4,9 @@ * Attach custom data fields to Drupal entities. */ -use Drupal\Core\Template\Attribute; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\Query\QueryFactory; +use Drupal\Core\Template\Attribute; /* * Load all public Field API functions. Drupal currently has no @@ -387,6 +388,42 @@ function field_data_type_info() { } /** + * Implements hook_entity_alter(). + */ +function field_entity_alter(EntityInterface $entity) { + foreach ($entity->getTranslationLanguages() as $langcode => $language) { + field_populate_default_values($entity, $langcode); + } +} + +/** + * Inserts a default value if no $entity->$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. This is the + * default field 'insert' operation. + * + * @param $entity + * The entity for the operation. + * @param $langcode + * (optional) The field language to fill-in with the default value. Defaults + * to the entity language. + */ +function field_populate_default_values(EntityInterface $entity, $langcode = NULL) { + $entity_type = $entity->entityType(); + $langcode = $langcode ?: $entity->language()->langcode; + + foreach (field_info_instances($entity_type, $entity->bundle()) as $field_name => $instance) { + $field = field_info_field($field_name); + $field_langcode = field_is_translatable($entity_type, $field) ? $langcode : LANGUAGE_NOT_SPECIFIED; + if (empty($entity->{$field_name}) || !array_key_exists($field_langcode, $entity->{$field_name})) { + $items = field_get_default_value($entity_type, $entity, $field, $instance, $field_langcode); + $entity->{$field_name}[$field_langcode] = $items; + } + } +} + +/** * Implements hook_entity_field_info() to define all configured fields. */ function field_entity_field_info($entity_type) { diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldAttachStorageTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldAttachStorageTest.php index a4be944..a5e8e07 100644 --- a/core/modules/field/lib/Drupal/field/Tests/FieldAttachStorageTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/FieldAttachStorageTest.php @@ -340,9 +340,12 @@ function testFieldAttachSaveMissingDataDefaultValue() { $this->instance['default_value_function'] = 'field_test_default_value'; field_update_instance($this->instance); + // Verify that fields are populated with default values. $entity_type = 'test_entity'; $entity_init = field_test_create_entity(); $langcode = LANGUAGE_NOT_SPECIFIED; + $default = field_test_default_value($entity_type, $entity_init, $this->field, $this->instance); + $this->assertEqual($entity_init->{$this->field_name}[$langcode], $default, 'Default field value correctly populated.'); // Insert: Field is NULL. $entity = clone($entity_init); @@ -350,6 +353,7 @@ function testFieldAttachSaveMissingDataDefaultValue() { field_attach_insert($entity_type, $entity); $entity = clone($entity_init); + $entity->{$this->field_name} = array(); field_attach_load($entity_type, array($entity->ftid => $entity)); $this->assertTrue(empty($entity->{$this->field_name}[$langcode]), 'Insert: NULL field results in no value saved'); @@ -359,9 +363,14 @@ function testFieldAttachSaveMissingDataDefaultValue() { field_attach_insert($entity_type, $entity); $entity = clone($entity_init); + $entity->{$this->field_name} = array(); 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}[$langcode], $values, 'Insert: missing field results in default value saved'); + $this->assertEqual($entity->{$this->field_name}[$langcode], $default, 'Insert: missing field results in default value saved'); + + // Verify that prepopulated field values are not overwritten by defaults. + $value = array(array('value' => $default[0]['value'] - mt_rand(1, 127))); + $entity = entity_create('test_entity', array('fttype' => $entity_init->bundle(), $this->field_name => array($langcode => $value))); + $this->assertEqual($entity->{$this->field_name}[$langcode], $value, 'Prepopulated field value correctly maintained.'); } /** diff --git a/core/modules/field/lib/Drupal/field/Tests/FormTest.php b/core/modules/field/lib/Drupal/field/Tests/FormTest.php index b11638f..65fd25b 100644 --- a/core/modules/field/lib/Drupal/field/Tests/FormTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/FormTest.php @@ -69,7 +69,6 @@ function testFieldFormSingle() { $this->assertText($token_description, 'Token replacement for description is displayed'); $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', 'Widget is displayed'); $this->assertNoField("{$this->field_name}[$langcode][1][value]", 'No extraneous widget is displayed'); - // TODO : check that the widget is populated with default value ? // Check that hook_field_widget_form_alter() does not believe this is the // default value form. @@ -113,7 +112,34 @@ function testFieldFormSingle() { entity_get_controller('test_entity')->resetCache(array($id)); $entity = field_test_entity_test_load($id); $this->assertIdentical($entity->{$this->field_name}, array(), 'Field was emptied'); + } + /** + * Tests field widget default values on entity forms. + */ + function testFieldFormDefaultValue() { + $this->field = $this->field_single; + $this->field_name = $this->field['field_name']; + $this->instance['field_name'] = $this->field_name; + $default = rand(1, 127); + $this->instance['default_value'] = array(array('value' => $default)); + field_create_field($this->field); + field_create_instance($this->instance); + $langcode = LANGUAGE_NOT_SPECIFIED; + + // Display creation form. + $this->drupalGet('test-entity/add/test_bundle'); + // Test that the default value is displayed correctly. + $this->assertFieldByXpath("//input[@name='{$this->field_name}[$langcode][0][value]' and @value='$default']"); + + // Try to submit an empty value. + $edit = array("{$this->field_name}[$langcode][0][value]" => ''); + $this->drupalPost(NULL, $edit, t('Save')); + preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match); + $id = $match[1]; + $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created.'); + $entity = field_test_entity_test_load($id); + $this->assertTrue(empty($entity->{$this->field_name}), 'Field is now empty.'); } function testFieldFormSingleRequired() { diff --git a/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php b/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php index 8f813c0..1c324f5 100644 --- a/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/TranslationTest.php @@ -242,6 +242,52 @@ function testTranslatableFieldSaveLoad() { } $this->assertTrue($result, format_string('%language translation correctly handled.', array('%language' => $langcode))); } + + // Test default values. + $field_name_default = drupal_strtolower($this->randomName() . '_field_name'); + $field = $this->field; + $field['field_name'] = $field_name_default; + $instance = $this->instance; + $instance['field_name'] = $field_name_default; + $default = rand(1, 127); + $instance['default_value'] = array(array('value' => $default)); + field_create_field($field); + field_create_instance($instance); + $translation_langcodes = array_slice($available_langcodes, 0, 2); + asort($translation_langcodes); + $translation_langcodes = array_values($translation_langcodes); + + $eid++; + $evid++; + $values = array('eid' => $eid, 'evid' => $evid, 'fttype' => $instance['bundle'], 'langcode' => $translation_langcodes[0]); + foreach ($translation_langcodes as $langcode) { + $values[$this->field_name][$langcode] = $this->_generateTestFieldValues($this->field['cardinality']); + } + $entity = entity_create($entity_type, $values); + + ksort($entity->{$field_name_default}); + $field_langcodes = array_keys($entity->{$field_name_default}); + $this->assertEqual($translation_langcodes, $field_langcodes, 'Missing translations did not get a default value.'); + + foreach ($entity->{$field_name_default} as $langcode => $items) { + $this->assertEqual($items, $instance['default_value'], format_string('Default value correctly populated for language %language.', array('%language' => $langcode))); + } + + // Check that explicit empty values are not overridden with default values. + foreach (array(NULL, array()) as $empty_items) { + $eid++; + $evid++; + $values = array('eid' => $eid, 'evid' => $evid, 'fttype' => $instance['bundle'], 'langcode' => $translation_langcodes[0]); + foreach ($translation_langcodes as $langcode) { + $values[$this->field_name][$langcode] = $this->_generateTestFieldValues($this->field['cardinality']); + $values[$field_name_default][$langcode] = $empty_items; + } + $entity = entity_create($entity_type, $values); + + foreach ($entity->{$field_name_default} as $langcode => $items) { + $this->assertEqual($items, $empty_items, format_string('Empty value correctly populated for language %language.', array('%language' => $langcode))); + } + } } /** diff --git a/core/modules/field/tests/modules/field_test/field_test.entity.inc b/core/modules/field/tests/modules/field_test/field_test.entity.inc index 517f5cb..bcfd7a0 100644 --- a/core/modules/field/tests/modules/field_test/field_test.entity.inc +++ b/core/modules/field/tests/modules/field_test/field_test.entity.inc @@ -121,7 +121,7 @@ function field_test_delete_bundle($bundle) { * Creates a basic test_entity entity. */ function field_test_create_entity($id = 1, $vid = 1, $bundle = 'test_bundle', $label = '') { - $entity = entity_create('test_entity', array()); + $entity = entity_create('test_entity', array('fttype' => $bundle)); // Only set id and vid properties if they don't come as NULL (creation form). if (isset($id)) { $entity->ftid = $id; @@ -131,7 +131,6 @@ function field_test_create_entity($id = 1, $vid = 1, $bundle = 'test_bundle', $l // Flag to make sure that the provided vid is used for a new revision. $entity->use_provided_revision_id = $vid; } - $entity->fttype = $bundle; $label = !empty($label) ? $label : $bundle . ' label'; $entity->ftlabel = $label;