Commit 4831e2ee authored by alexpott's avatar alexpott

Issue #2226267 by fago, xjm, Berdir: Improve default value handling of fields to be consistent.

parent 81d8d75a
......@@ -7,7 +7,7 @@
namespace Drupal\Core\Field;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Field\TypedData\FieldItemDataDefinition;
use Drupal\Core\TypedData\ListDataDefinition;
use Drupal\field\FieldException;
......@@ -378,8 +378,56 @@ public function isDisplayConfigurable($display_context) {
/**
* {@inheritdoc}
*/
public function getDefaultValue(EntityInterface $entity) {
return $this->getSetting('default_value');
public function getDefaultValue(ContentEntityInterface $entity) {
// Allow custom default values function.
if (isset($this->definition['default_value_callback'])) {
$value = call_user_func($this->definition['default_value_callback'], $entity, $this);
}
else {
$value = isset($this->definition['default_value']) ? $this->definition['default_value'] : NULL;
}
// Allow the field type to process default values.
$field_item_list_class = $this->getClass();
return $field_item_list_class::processDefaultValue($value, $entity, $this);
}
/**
* Sets a custom default value callback.
*
* If set, the callback overrides any set default value.
*
* @param string|array $callback
* The callback to invoke for getting the default value. The callback will
* be invoked with the following arguments:
* - \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity being created.
* - \Drupal\Core\Field\FieldDefinitionInterface $definition
* The field definition.
* It should return the default value as documented by
* \Drupal\Core\Field\FieldDefinitionInterface::getDefaultValue().
*
* @return $this
*/
public function setDefaultValueCallback($callback) {
$this->definition['default_value_callback'] = $callback;
return $this;
}
/**
* Sets a default value.
*
* Note that if a default value callback is set, it will take precedence over
* any value set here.
*
* @param mixed $value
* The default value in the format as returned by
* \Drupal\Core\Field\FieldDefinitionInterface::getDefaultValue().
*
* @return $this
*/
public function setDefaultValue($value) {
$this->definition['default_value'] = $value;
return $this;
}
/**
......
......@@ -7,7 +7,7 @@
namespace Drupal\Core\Field;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\TypedData\ListDataDefinitionInterface;
/**
......@@ -114,19 +114,19 @@ public function isRequired();
/**
* Returns the default value for the field in a newly created entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity being created.
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity for which the default value is generated.
*
* @return mixed
* The default value for the field, as accepted by
* Drupal\field\Plugin\Core\Entity\FieldConfig::setValue(). This can be
* either:
* \Drupal\field\Plugin\Core\Entity\FieldItemListInterface::setValue(). This
* can be either:
* - a literal, in which case it will be assigned to the first property of
* the first item.
* - a numerically indexed array of items, each item being a property/value
* array.
* - NULL or array() for no default value.
*/
public function getDefaultValue(EntityInterface $entity);
public function getDefaultValue(ContentEntityInterface $entity);
}
......@@ -7,6 +7,7 @@
namespace Drupal\Core\Field;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TypedData\DataDefinitionInterface;
use Drupal\Core\TypedData\TypedDataInterface;
......@@ -202,7 +203,7 @@ public function defaultAccess($operation = 'view', AccountInterface $account = N
* {@inheritdoc}
*/
public function applyDefaultValue($notify = TRUE) {
$value = $this->getDefaultValue();
$value = $this->getFieldDefinition()->getDefaultValue($this->getEntity());
// NULL or array() mean "no default value", but 0, '0' and the empty string
// are valid default values.
......@@ -216,16 +217,6 @@ public function applyDefaultValue($notify = TRUE) {
return $this;
}
/**
* Returns the default value for the field.
*
* @return array
* The default value for the field.
*/
protected function getDefaultValue() {
return $this->getFieldDefinition()->getDefaultValue($this->getEntity());
}
/**
* {@inheritdoc}
*/
......@@ -347,6 +338,13 @@ public function defaultValuesFormSubmit(array $element, array &$form, array &$fo
return $this->getValue();
}
/**
* {@inheritdoc}
*/
public static function processDefaultValue($default_value, ContentEntityInterface $entity, FieldDefinitionInterface $definition) {
return $default_value;
}
/**
* Returns the widget object used in default value form.
*
......
......@@ -7,6 +7,7 @@
namespace Drupal\Core\Field;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Access\AccessibleInterface;
use Drupal\Core\TypedData\ListInterface;
......@@ -228,4 +229,30 @@ public function defaultValuesFormValidate(array $element, array &$form, array &$
*/
public function defaultValuesFormSubmit(array $element, array &$form, array &$form_state);
/**
* Processes the default value before being applied.
*
* Defined or configured default values of a field might need some processing
* in order to be a valid value for the field type; e.g., a date field could
* process the defined value of 'NOW' to a valid date.
*
* @param mixed
* The default value as defined for the field.
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity for which the default value is generated.
* @param \Drupal\Core\Field\FieldDefinitionInterface $definition
* The definition of the field.
*
* @return mixed
* The default value for the field, as accepted by
* \Drupal\field\Plugin\Core\Entity\FieldItemListInterface::setValue(). This
* can be either:
* - a literal, in which case it will be assigned to the first property of
* the first item.
* - a numerically indexed array of items, each item being a property/value
* array.
* - NULL or array() for no default value.
*/
public static function processDefaultValue($default_value, ContentEntityInterface $entity, FieldDefinitionInterface $definition);
}
......@@ -2,7 +2,7 @@
/**
* @file
* Contains \Drupal\Core\Entity\Plugin\Field\FieldType\StringItem.
* Contains \Drupal\Core\Field\Plugin\Field\FieldType\StringItem.
*/
namespace Drupal\Core\Field\Plugin\Field\FieldType;
......
......@@ -176,12 +176,12 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['checked'] = FieldDefinition::create('timestamp')
->setLabel(t('Checked'))
->setDescription(t('Last time feed was checked for new items, as Unix timestamp.'))
->setSetting('default_value', 0);
->setDefaultValue(0);
$fields['queued'] = FieldDefinition::create('timestamp')
->setLabel(t('Queued'))
->setDescription(t('Time when this feed was queued for refresh, 0 if not queued.'))
->setSetting('default_value', 0);
->setDefaultValue(0);
$fields['link'] = FieldDefinition::create('uri')
->setLabel(t('Link'))
......
......@@ -233,18 +233,14 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['uid'] = FieldDefinition::create('entity_reference')
->setLabel(t('User ID'))
->setDescription(t('The user ID of the comment author.'))
->setSettings(array(
'target_type' => 'user',
'default_value' => 0,
));
->setSetting('target_type', 'user')
->setDefaultValue(0);
$fields['name'] = FieldDefinition::create('string')
->setLabel(t('Name'))
->setDescription(t("The comment author's name."))
->setSettings(array(
'default_value' => '',
'max_length' => 60,
))
->setSetting('max_length', 60)
->setDefaultValue('')
->addConstraint('CommentName', array());
$fields['mail'] = FieldDefinition::create('email')
......@@ -274,7 +270,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['status'] = FieldDefinition::create('boolean')
->setLabel(t('Publishing status'))
->setDescription(t('A boolean indicating whether the comment is published.'))
->setSetting('default_value', TRUE);
->setDefaultValue(TRUE);
$fields['thread'] = FieldDefinition::create('string')
->setLabel(t('Thread place'))
......
......@@ -8,6 +8,8 @@
namespace Drupal\datetime\Plugin\Field\FieldType;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemList;
/**
......@@ -61,14 +63,14 @@ public function defaultValuesFormSubmit(array $element, array &$form, array &$fo
/**
* {@inheritdoc}
*/
public function getDefaultValue() {
$default_value = parent::getDefaultValue();
public static function processDefaultValue($default_value, ContentEntityInterface $entity, FieldDefinitionInterface $definition) {
$default_value = parent::processDefaultValue($default_value, $entity, $definition);
if (isset($default_value[0]['default_date']) && $default_value[0]['default_date'] == static::DEFAULT_VALUE_NOW) {
// A default value should be in the format and timezone used for date
// storage.
$date = new DrupalDateTime('now', DATETIME_STORAGE_TIMEZONE);
$storage_format = $this->getFieldDefinition()->getSetting('datetime_type') == DateTimeItem::DATETIME_TYPE_DATE ? DATETIME_DATE_STORAGE_FORMAT: DATETIME_DATETIME_STORAGE_FORMAT;
$storage_format = $definition->getSetting('datetime_type') == DateTimeItem::DATETIME_TYPE_DATE ? DATETIME_DATE_STORAGE_FORMAT: DATETIME_DATETIME_STORAGE_FORMAT;
$value = $date->format($storage_format);
// We only provide a default value for the first item, as do all fields.
// Otherwise, there is no way to clear out unwanted values on multiple value
......
......@@ -8,6 +8,8 @@
namespace Drupal\entity_reference\Plugin\Field\FieldType;
use Drupal\Core\Field\EntityReferenceFieldItemList;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
/**
* Represents a configurable entity_reference entity field.
......@@ -17,8 +19,8 @@ class ConfigurableEntityReferenceFieldItemList extends EntityReferenceFieldItemL
/**
* {@inheritdoc}
*/
protected function getDefaultValue() {
$default_value = parent::getDefaultValue();
public static function processDefaultValue($default_value, ContentEntityInterface $entity, FieldDefinitionInterface $definition) {
$default_value = parent::processDefaultValue($default_value, $entity, $definition);
if ($default_value) {
// Convert UUIDs to numeric IDs.
......@@ -33,7 +35,7 @@ protected function getDefaultValue() {
}
}
if ($uuids) {
$target_type = $this->getSetting('target_type');
$target_type = $definition->getSetting('target_type');
$entity_ids = \Drupal::entityQuery($target_type)
->condition('uuid', $uuids, 'IN')
->execute();
......
......@@ -9,7 +9,7 @@
use Drupal\Component\Utility\String;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\FieldDefinition;
use Drupal\Core\Field\TypedData\FieldItemDataDefinition;
......@@ -154,14 +154,10 @@ class FieldInstanceConfig extends ConfigEntityBase implements FieldInstanceConfi
* The name of a callback function that returns default values.
*
* The function will be called with the following arguments:
* - \Drupal\Core\Entity\EntityInterface $entity
* - \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity being created.
* - \Drupal\field\Entity\FieldConfig $field
* The field object.
* - \Drupal\field\Entity\FieldInstanceConfig $instance
* The field instance object.
* - string $langcode
* The language of the entity being created.
* - \Drupal\Core\Field\FieldDefinitionInterface $definition
* The field definition.
* It should return an array of default values, in the same format as the
* $default_value property.
*
......@@ -588,14 +584,17 @@ public function isMultiple() {
/**
* {@inheritdoc}
*/
public function getDefaultValue(EntityInterface $entity) {
if (!empty($this->default_value_function)) {
$function = $this->default_value_function;
return $function($entity, $this->getField(), $this, $entity->language()->id);
public function getDefaultValue(ContentEntityInterface $entity) {
// Allow custom default values function.
if ($function = $this->default_value_function) {
$value = call_user_func($function, $entity, $this);
}
elseif (!empty($this->default_value)) {
return $this->default_value;
else {
$value = $this->default_value;
}
// Allow the field type to process default values.
$field_item_list_class = $this->getClass();
return $field_item_list_class::processDefaultValue($value, $entity, $this);
}
/**
......
......@@ -220,7 +220,7 @@ function testFieldAttachSaveEmptyDataDefaultValue() {
// Verify that fields are populated with default values.
$entity_init = entity_create($entity_type, array('id' => 1, 'revision_id' => 1));
$default = field_test_default_value($entity_init, $this->field, $this->instance);
$default = field_test_default_value($entity_init, $this->instance);
$this->assertEqual($entity_init->{$this->field_name}->getValue(), $default, 'Default field value correctly populated.');
// Insert: Field is NULL.
......
......@@ -5,7 +5,7 @@
* Defines a field type and its formatters and widgets.
*/
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Session\AccountInterface;
......@@ -31,7 +31,7 @@ function field_test_field_config_update_forbid(FieldConfigInterface $field, Fiel
/**
* Sample 'default value' callback.
*/
function field_test_default_value(EntityInterface $entity, $field, $instance) {
function field_test_default_value(ContentEntityInterface $entity, FieldDefinitionInterface $definition) {
return array(array('value' => 99));
}
......
......@@ -357,7 +357,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setRequired(TRUE)
->setTranslatable(TRUE)
->setRevisionable(TRUE)
->setSetting('default_value', '')
->setDefaultValue('')
->setSetting('max_length', 255)
->setDisplayOptions('view', array(
'label' => 'hidden',
......@@ -453,11 +453,11 @@ public static function bundleFieldDefinitions(EntityTypeInterface $entity_type,
$options = $node_type->getModuleSettings('node')['options'];
$fields['status'] = clone $base_field_definitions['status'];
$fields['status']->setSetting('default_value', !empty($options['status']) ? NODE_PUBLISHED : NODE_NOT_PUBLISHED);
$fields['status']->setDefaultValue(!empty($options['status']) ? NODE_PUBLISHED : NODE_NOT_PUBLISHED);
$fields['promote'] = clone $base_field_definitions['promote'];
$fields['promote']->setSetting('default_value', !empty($options['promote']) ? NODE_PROMOTED : NODE_NOT_PROMOTED);
$fields['promote']->setDefaultValue(!empty($options['promote']) ? NODE_PROMOTED : NODE_NOT_PROMOTED);
$fields['sticky'] = clone $base_field_definitions['sticky'];
$fields['sticky']->setSetting('default_value', !empty($options['sticky']) ? NODE_STICKY : NODE_NOT_STICKY);
$fields['sticky']->setDefaultValue(!empty($options['sticky']) ? NODE_STICKY : NODE_NOT_STICKY);
return $fields;
}
......
......@@ -8,7 +8,6 @@
namespace Drupal\serialization\Tests;
use Drupal\Core\Language\LanguageInterface;
use Symfony\Component\Serializer\Serializer;
use Drupal\Component\Utility\String;
/**
......
......@@ -174,7 +174,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setDescription(t('The name of the shortcut.'))
->setRequired(TRUE)
->setTranslatable(TRUE)
->setSetting('default_value', '')
->setDefaultValue('')
->setSetting('max_length', 255)
->setDisplayOptions('form', array(
'type' => 'string',
......
......@@ -59,4 +59,15 @@ protected function assertDefaultValues($entity_type) {
$this->assertTrue(Uuid::isValid($entity->uuid->value), String::format('%entity_type: Default UUID', array('%entity_type' => $entity_type)));
$this->assertEqual($entity->name->getValue(), array(0 => array('value' => NULL)), 'Field has one empty value by default.');
}
/**
* Tests custom default value callbacks.
*/
public function testDefaultValueCallback() {
$entity = $this->entityManager->getStorage('entity_test_default_value')->create();
// The description field has a default value callback for testing, see
// entity_test_field_default_value().
$this->assertEqual($entity->description->value, 'description_' . $entity->language()->id);
}
}
......@@ -6,13 +6,12 @@
*/
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\entity\Entity\EntityFormDisplay;
use Drupal\field\FieldConfigInterface;
use Drupal\field\FieldInstanceConfigInterface;
/**
* Filter that limits test entity list to revisable ones.
......@@ -419,17 +418,19 @@ function entity_test_entity_test_mul_translation_delete(EntityInterface $transla
/**
* Field default value callback.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity the field belongs to.
* @param \Drupal\field\FieldConfigInterface $field
* The field for which default values should be provided.
* @param \Drupal\field\FieldInstanceConfigInterface $instance
* The field instance for which default values should be provided.
* @param string $langcode
* The field language code to fill-in with the default value.
*/
function entity_test_field_default_value(EntityInterface $entity, FieldConfigInterface $field, FieldInstanceConfigInterface $instance, $langcode) {
return array(array('value' => $field->getName() . '_' . $langcode));
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity being created.
* @param \Drupal\Core\Field\FieldDefinitionInterface $definition
* The field definition.
*
* @return array
* An array of default values, in the same format as the $default_value
* property.
*
* @see \Drupal\field\Entity\FieldInstanceConfig::$default_value
*/
function entity_test_field_default_value(ContentEntityInterface $entity, FieldDefinitionInterface $definition) {
return array(array('value' => $definition->getName() . '_' . $entity->language()->id));
}
/**
......
<?php
/**
* @file
* Contains \Drupal\entity_test\Entity\EntityTestDefaultValue.
*/
namespace Drupal\entity_test\Entity;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\FieldDefinition;
/**
* Defines a test entity class for testing default values.
*
* @ContentEntityType(
* id = "entity_test_default_value",
* label = @Translation("Test entity for default values"),
* base_table = "entity_test_default_value",
* fieldable = TRUE,
* entity_keys = {
* "id" = "id",
* "uuid" = "uuid",
* "bundle" = "type"
* }
* )
*/
class EntityTestDefaultValue extends EntityTest {
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields = parent::baseFieldDefinitions($entity_type);
$fields['description'] = FieldDefinition::create('string')
->setLabel(t('Some custom description'))
->setDefaultValueCallback('entity_test_field_default_value');
return $fields;
}
}
......@@ -156,7 +156,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['weight'] = FieldDefinition::create('integer')
->setLabel(t('Weight'))
->setDescription(t('The weight of this term in relation to other terms.'))
->setSetting('default_value', 0);
->setDefaultValue(0);
// @todo Convert this to an entity_reference field, see
// https://drupal.org/node/1915056
......@@ -165,7 +165,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setDescription(t('The parents of this term.'))
->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED)
// Save new terms with no parents by default.
->setSetting('default_value', 0)
->setDefaultValue(0)
->setSetting('unsigned', TRUE)
->addConstraint('TermParent', array());
......
......@@ -8,6 +8,8 @@
namespace Drupal\taxonomy\Plugin\Field\FieldType;
use Drupal\Core\Field\EntityReferenceFieldItemList;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
/**
* Represents a configurable taxonomy_term_reference entity field item list.
......@@ -17,10 +19,31 @@ class TaxonomyTermReferenceFieldItemList extends EntityReferenceFieldItemList {
/**
* {@inheritdoc}
*/
protected function getDefaultValue() {
$default_value = parent::getDefaultValue();
public function defaultValuesFormSubmit(array $element, array &$form, array &$form_state) {
$default_value = parent::defaultValuesFormSubmit($element, $form, $form_state);
// Convert numeric IDs to UUIDs to ensure config deployability.
$ids = array();
foreach ($default_value as $delta => $properties) {
$ids[] = $properties['target_id'];
}
$entities = \Drupal::entityManager()
->getStorage('taxonomy_term')
->loadMultiple($ids);
foreach ($default_value as $delta => $properties) {
unset($default_value[$delta]['target_id']);
$default_value[$delta]['target_uuid'] = $entities[$properties['target_id']]->uuid();
}
return $default_value;
}
/**
* {@inheritdoc}
*/
public static function processDefaultValue($default_value, ContentEntityInterface $entity, FieldDefinitionInterface $definition) {
$default_value = parent::processDefaultValue($default_value, $entity, $definition);
if ($default_value) {
// Convert UUIDs to numeric IDs.
$uuids = array();
foreach ($default_value as $delta => $properties) {
......@@ -50,29 +73,7 @@ protected function getDefaultValue() {
// Ensure we return consecutive deltas, in case we removed unknown UUIDs.
$default_value = array_values($default_value);
}
return $default_value;
}
/**
* {@inheritdoc}
*/
public function defaultValuesFormSubmit(array $element, array &$form, array &$form_state) {
$default_value = parent::defaultValuesFormSubmit($element, $form, $form_state);
// Convert numeric IDs to UUIDs to ensure config deployability.
$ids = array();
foreach ($default_value as $delta => $properties) {
$ids[] = $properties['target_id'];
}
$entities = \Drupal::entityManager()
->getStorage('taxonomy_term')
->loadMultiple($ids);
foreach ($default_value as $delta => $properties) {
unset($default_value[$delta]['target_id']);
$default_value[$delta]['target_uuid'] = $entities[$properties['target_id']]->uuid();
}
return $default_value;
}
......
......@@ -470,7 +470,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['name'] = FieldDefinition::create('string')
->setLabel(t('Name'))
->setDescription(t('The name of this user.'))
->setSetting('default_value', '')
->setDefaultValue('')
->setPropertyConstraints('value', array(
// No Length constraint here because the UserName constraint also covers
// that.
......@@ -485,7 +485,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['mail'] = FieldDefinition::create('email')
->setLabel(t('Email'))
->setDescription(t('The email of this user.'))
->setSetting('default_value', '')
->setDefaultValue('')
->setPropertyConstraints('value', array('UserMailUnique' => array()));
// @todo Convert to a text field in https://drupal.org/node/1548204.
......@@ -504,7 +504,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['status'] = FieldDefinition::create('boolean')
->setLabel(t('User status'))
->setDescription(t('Whether the user is active or blocked.'))
->setSetting('default_value', FALSE);
->setDefaultValue(FALSE);
$fields['created'] = FieldDefinition::create('created')
->setLabel(t('Created'))
......@@ -513,17 +513,17 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['access'] = FieldDefinition::create('timestamp')
->setLabel(t('Last access'))
->setDescription(t('The time that the user last accessed the site.'))
->setSetting