Commit 37afef00 authored by webchick's avatar webchick

Issue #1818556 by das-peter, plach, fago, Berdir, vijaycs85: Change notice:...

Issue #1818556 by das-peter, plach, fago, Berdir, vijaycs85: Change notice: Convert nodes to the new Entity Field API.
parent 6bcbd0ae
...@@ -96,6 +96,8 @@ public function __construct($entityType) { ...@@ -96,6 +96,8 @@ public function __construct($entityType) {
*/ */
public function create(array $values) { public function create(array $values) {
// We have to determine the bundle first. // We have to determine the bundle first.
// @todo Throw an exception if no bundle is passed and we have a bundle key
// defined.
$bundle = $this->bundleKey ? $values[$this->bundleKey] : FALSE; $bundle = $this->bundleKey ? $values[$this->bundleKey] : FALSE;
$entity = new $this->entityClass(array(), $this->entityType, $bundle); $entity = new $this->entityClass(array(), $this->entityType, $bundle);
...@@ -154,7 +156,7 @@ protected function attachLoad(&$queried_entities, $load_revision = FALSE) { ...@@ -154,7 +156,7 @@ protected function attachLoad(&$queried_entities, $load_revision = FALSE) {
// Map the loaded stdclass records into entity objects and according fields. // Map the loaded stdclass records into entity objects and according fields.
$queried_entities = $this->mapFromStorageRecords($queried_entities, $load_revision); $queried_entities = $this->mapFromStorageRecords($queried_entities, $load_revision);
// Attach fields. // Activate backward-compatibility mode to attach fields.
if ($this->entityInfo['fieldable']) { if ($this->entityInfo['fieldable']) {
// Prepare BC compatible entities before passing them to the field API. // Prepare BC compatible entities before passing them to the field API.
$bc_entities = array(); $bc_entities = array();
...@@ -251,7 +253,10 @@ protected function attachPropertyData(array &$entities, $load_revision = FALSE) ...@@ -251,7 +253,10 @@ protected function attachPropertyData(array &$entities, $load_revision = FALSE)
foreach ($field_definition as $name => $definition) { foreach ($field_definition as $name => $definition) {
// Set translatable properties only. // Set translatable properties only.
if (isset($data_fields[$name]) && !empty($definition['translatable'])) { if (isset($data_fields[$name]) && !empty($definition['translatable'])) {
$translation->{$name}->value = $values[$name]; // @todo Figure out how to determine which property has to be set.
// Currently it's guessing, and guessing is evil!
$property_definition = $translation->{$name}->getPropertyDefinitions();
$translation->{$name}->{key($property_definition)} = $values[$name];
} }
// Avoid initializing configurable fields before loading them. // Avoid initializing configurable fields before loading them.
elseif (!empty($definition['configurable'])) { elseif (!empty($definition['configurable'])) {
...@@ -270,6 +275,12 @@ protected function attachPropertyData(array &$entities, $load_revision = FALSE) ...@@ -270,6 +275,12 @@ protected function attachPropertyData(array &$entities, $load_revision = FALSE)
public function save(EntityInterface $entity) { public function save(EntityInterface $entity) {
$transaction = db_transaction(); $transaction = db_transaction();
try { try {
// Ensure we are dealing with the actual entity.
$entity = $entity->getOriginalEntity();
// Sync the changes made in the fields array to the internal values array.
$entity->updateOriginalValues();
// Load the stored entity, if any. // Load the stored entity, if any.
if (!$entity->isNew() && !isset($entity->original)) { if (!$entity->isNew() && !isset($entity->original)) {
$entity->original = entity_load_unchanged($this->entityType, $entity->id()); $entity->original = entity_load_unchanged($this->entityType, $entity->id());
...@@ -279,7 +290,7 @@ public function save(EntityInterface $entity) { ...@@ -279,7 +290,7 @@ public function save(EntityInterface $entity) {
$this->invokeHook('presave', $entity); $this->invokeHook('presave', $entity);
// Create the storage record to be saved. // Create the storage record to be saved.
$record = $this->maptoStorageRecord($entity); $record = $this->mapToStorageRecord($entity);
if (!$entity->isNew()) { if (!$entity->isNew()) {
if ($entity->isDefaultRevision()) { if ($entity->isDefaultRevision()) {
...@@ -446,7 +457,9 @@ protected function mapToStorageRecord(EntityInterface $entity) { ...@@ -446,7 +457,9 @@ protected function mapToStorageRecord(EntityInterface $entity) {
protected function mapToRevisionStorageRecord(EntityInterface $entity) { protected function mapToRevisionStorageRecord(EntityInterface $entity) {
$record = new \stdClass(); $record = new \stdClass();
foreach ($this->entityInfo['schema_fields_sql']['revision_table'] as $name) { foreach ($this->entityInfo['schema_fields_sql']['revision_table'] as $name) {
$record->$name = $entity->$name->value; if (isset($entity->$name->value)) {
$record->$name = $entity->$name->value;
}
} }
return $record; return $record;
} }
...@@ -489,6 +502,11 @@ public function delete(array $entities) { ...@@ -489,6 +502,11 @@ public function delete(array $entities) {
$transaction = db_transaction(); $transaction = db_transaction();
try { try {
// Ensure we are dealing with the actual entities.
foreach ($entities as $id => $entity) {
$entities[$id] = $entity->getOriginalEntity();
}
$this->preDelete($entities); $this->preDelete($entities);
foreach ($entities as $id => $entity) { foreach ($entities as $id => $entity) {
$this->invokeHook('predelete', $entity); $this->invokeHook('predelete', $entity);
......
...@@ -45,14 +45,24 @@ class EntityBCDecorator implements IteratorAggregate, EntityInterface { ...@@ -45,14 +45,24 @@ class EntityBCDecorator implements IteratorAggregate, EntityInterface {
*/ */
protected $decorated; protected $decorated;
/**
* Local cache for field definitions.
*
* @var array
*/
protected $definitions;
/** /**
* Constructs a Drupal\Core\Entity\EntityCompatibilityDecorator object. * Constructs a Drupal\Core\Entity\EntityCompatibilityDecorator object.
* *
* @param \Drupal\Core\Entity\EntityInterface $decorated * @param \Drupal\Core\Entity\EntityInterface $decorated
* The decorated entity. * The decorated entity.
* @param array &$definitions
* An array of field definitions.
*/ */
function __construct(EntityNG $decorated) { function __construct(EntityNG $decorated, array &$definitions) {
$this->decorated = $decorated; $this->decorated = $decorated;
$this->definitions = &$definitions;
} }
/** /**
...@@ -75,6 +85,11 @@ public function getBCEntity() { ...@@ -75,6 +85,11 @@ public function getBCEntity() {
* Directly accesses the plain field values, as done in Drupal 7. * Directly accesses the plain field values, as done in Drupal 7.
*/ */
public function &__get($name) { public function &__get($name) {
// Directly return the original property.
if ($name == 'original') {
return $this->decorated->values[$name];
}
// We access the protected 'values' and 'fields' properties of the decorated // We access the protected 'values' and 'fields' properties of the decorated
// entity via the magic getter - which returns them by reference for us. We // entity via the magic getter - which returns them by reference for us. We
// do so, as providing references to these arrays would make $entity->values // do so, as providing references to these arrays would make $entity->values
...@@ -88,7 +103,10 @@ public function &__get($name) { ...@@ -88,7 +103,10 @@ public function &__get($name) {
// the field objects managed by the entity, thus we need to ensure // the field objects managed by the entity, thus we need to ensure
// $this->decorated->values reflects the latest values first. // $this->decorated->values reflects the latest values first.
foreach ($this->decorated->fields[$name] as $langcode => $field) { foreach ($this->decorated->fields[$name] as $langcode => $field) {
$this->decorated->values[$name][$langcode] = $field->getValue(); // Only set if it's not empty, otherwise there can be ghost values.
if (!$field->isEmpty()) {
$this->decorated->values[$name][$langcode] = $field->getValue();
}
} }
// The returned values might be changed by reference, so we need to remove // The returned values might be changed by reference, so we need to remove
// the field object to avoid the field object and the value getting out of // the field object to avoid the field object and the value getting out of
...@@ -96,21 +114,39 @@ public function &__get($name) { ...@@ -96,21 +114,39 @@ public function &__get($name) {
// receive the possibly updated value. // receive the possibly updated value.
unset($this->decorated->fields[$name]); unset($this->decorated->fields[$name]);
} }
// Allow accessing field values in entity default language other than // When accessing values for entity properties that have been converted to
// LANGUAGE_DEFAULT by mapping the values to LANGUAGE_DEFAULT. This is // an entity field, provide direct access to the plain value. This makes it
// necessary as EntityNG does key values in default language always with // possible to use the BC-decorator with properties; e.g., $node->title.
// LANGUAGE_DEFAULT while field API expects them to be keyed by langcode. if (isset($this->definitions[$name]) && empty($this->definitions[$name]['configurable'])) {
$langcode = $this->decorated->language()->langcode; if (!isset($this->decorated->values[$name][LANGUAGE_DEFAULT])) {
if ($langcode != LANGUAGE_DEFAULT && isset($this->decorated->values[$name]) && is_array($this->decorated->values[$name])) { $this->decorated->values[$name][LANGUAGE_DEFAULT][0]['value'] = NULL;
if (isset($this->decorated->values[$name][LANGUAGE_DEFAULT]) && !isset($this->decorated->values[$name][$langcode])) { }
$this->decorated->values[$name][$langcode] = &$this->decorated->values[$name][LANGUAGE_DEFAULT]; if (is_array($this->decorated->values[$name][LANGUAGE_DEFAULT])) {
// This will work with all defined properties that have a single value.
// We need to ensure the key doesn't matter. Mostly it's 'value' but
// e.g. EntityReferenceItem uses target_id.
if (isset($this->decorated->values[$name][LANGUAGE_DEFAULT][0]) && count($this->decorated->values[$name][LANGUAGE_DEFAULT][0]) == 1) {
return $this->decorated->values[$name][LANGUAGE_DEFAULT][0][key($this->decorated->values[$name][LANGUAGE_DEFAULT][0])];
}
} }
return $this->decorated->values[$name][LANGUAGE_DEFAULT];
} }
else {
if (!isset($this->decorated->values[$name])) { // Allow accessing field values in an entity default language other than
$this->decorated->values[$name] = NULL; // LANGUAGE_DEFAULT by mapping the values to LANGUAGE_DEFAULT. This is
// necessary as EntityNG always keys default language values with
// LANGUAGE_DEFAULT while field API expects them to be keyed by langcode.
$langcode = $this->decorated->language()->langcode;
if ($langcode != LANGUAGE_DEFAULT && isset($this->decorated->values[$name]) && is_array($this->decorated->values[$name])) {
if (isset($this->decorated->values[$name][LANGUAGE_DEFAULT]) && !isset($this->decorated->values[$name][$langcode])) {
$this->decorated->values[$name][$langcode] = &$this->decorated->values[$name][LANGUAGE_DEFAULT];
}
}
if (!isset($this->decorated->values[$name])) {
$this->decorated->values[$name] = NULL;
}
return $this->decorated->values[$name];
} }
return $this->decorated->values[$name];
} }
/** /**
...@@ -119,20 +155,29 @@ public function &__get($name) { ...@@ -119,20 +155,29 @@ public function &__get($name) {
* Directly writes to the plain field values, as done by Drupal 7. * Directly writes to the plain field values, as done by Drupal 7.
*/ */
public function __set($name, $value) { public function __set($name, $value) {
if (is_array($value) && $definition = $this->decorated->getPropertyDefinition($name)) { $defined = isset($this->definitions[$name]);
// If field API sets a value with a langcode in entity language, move it // When updating values for entity properties that have been converted to
// to LANGUAGE_DEFAULT. // an entity field, directly write to the plain value. This makes it
// This is necessary as EntityNG does key values in default language always // possible to use the BC-decorator with properties; e.g., $node->title.
// with LANGUAGE_DEFAULT while field API expects them to be keyed by if ($defined && empty($this->definitions[$name]['configurable'])) {
// langcode. $this->decorated->values[$name][LANGUAGE_DEFAULT] = $value;
foreach ($value as $langcode => $data) { }
if ($langcode != LANGUAGE_DEFAULT && $langcode == $this->decorated->language()->langcode) { else {
$value[LANGUAGE_DEFAULT] = $data; if ($defined && is_array($value)) {
unset($value[$langcode]); // If field API sets a value with a langcode in entity language, move it
// to LANGUAGE_DEFAULT.
// This is necessary as EntityNG always keys default language values
// with LANGUAGE_DEFAULT while field API expects them to be keyed by
// langcode.
foreach ($value as $langcode => $data) {
if ($langcode != LANGUAGE_DEFAULT && $langcode == $this->decorated->language()->langcode) {
$value[LANGUAGE_DEFAULT] = $data;
unset($value[$langcode]);
}
} }
} }
$this->decorated->values[$name] = $value;
} }
$this->decorated->values[$name] = $value;
// Remove the field object to avoid the field object and the value getting // Remove the field object to avoid the field object and the value getting
// out of sync. That way, the next field object instantiated by EntityNG // out of sync. That way, the next field object instantiated by EntityNG
// will hold the updated value. // will hold the updated value.
...@@ -151,8 +196,9 @@ public function __isset($name) { ...@@ -151,8 +196,9 @@ public function __isset($name) {
* Implements the magic method for unset(). * Implements the magic method for unset().
*/ */
public function __unset($name) { public function __unset($name) {
// Set the value to NULL.
$value = &$this->__get($name); $value = &$this->__get($name);
$value = array(); $value = NULL;
} }
/** /**
...@@ -173,6 +219,10 @@ public function access($operation = 'view', \Drupal\user\Plugin\Core\Entity\User ...@@ -173,6 +219,10 @@ public function access($operation = 'view', \Drupal\user\Plugin\Core\Entity\User
* Forwards the call to the decorated entity. * Forwards the call to the decorated entity.
*/ */
public function get($property_name) { public function get($property_name) {
// Ensure this works with not yet defined fields.
if (!isset($this->definitions[$property_name])) {
return $this->__get($property_name);
}
return $this->decorated->get($property_name); return $this->decorated->get($property_name);
} }
...@@ -180,6 +230,10 @@ public function get($property_name) { ...@@ -180,6 +230,10 @@ public function get($property_name) {
* Forwards the call to the decorated entity. * Forwards the call to the decorated entity.
*/ */
public function set($property_name, $value) { public function set($property_name, $value) {
// Ensure this works with not yet defined fields.
if (!isset($this->definitions[$property_name])) {
return $this->__set($property_name, $value);
}
return $this->decorated->set($property_name, $value); return $this->decorated->set($property_name, $value);
} }
......
...@@ -81,6 +81,11 @@ public function __construct(array $values, $entity_type, $bundle = FALSE) { ...@@ -81,6 +81,11 @@ public function __construct(array $values, $entity_type, $bundle = FALSE) {
$this->entityType = $entity_type; $this->entityType = $entity_type;
$this->bundle = $bundle ? $bundle : $this->entityType; $this->bundle = $bundle ? $bundle : $this->entityType;
foreach ($values as $key => $value) { foreach ($values as $key => $value) {
// If the key matches an existing property set the value to the property
// to ensure non converted properties have the correct value.
if (property_exists($this, $key) && isset($value[LANGUAGE_DEFAULT])) {
$this->$key = $value[LANGUAGE_DEFAULT];
}
$this->values[$key] = $value; $this->values[$key] = $value;
} }
$this->init(); $this->init();
...@@ -269,7 +274,7 @@ public function language() { ...@@ -269,7 +274,7 @@ public function language() {
if ($this->getPropertyDefinition('langcode')) { if ($this->getPropertyDefinition('langcode')) {
$language = $this->get('langcode')->language; $language = $this->get('langcode')->language;
} }
if (!isset($language)) { if (empty($language)) {
// Make sure we return a proper language object. // Make sure we return a proper language object.
$language = new Language(array('langcode' => LANGUAGE_NOT_SPECIFIED)); $language = new Language(array('langcode' => LANGUAGE_NOT_SPECIFIED));
} }
...@@ -367,7 +372,9 @@ public function translations() { ...@@ -367,7 +372,9 @@ public function translations() {
*/ */
public function getBCEntity() { public function getBCEntity() {
if (!isset($this->bcEntity)) { if (!isset($this->bcEntity)) {
$this->bcEntity = new EntityBCDecorator($this); // Initialize field definitions so that we can pass them by reference.
$this->getPropertyDefinitions();
$this->bcEntity = new EntityBCDecorator($this, $this->fieldDefinitions);
} }
return $this->bcEntity; return $this->bcEntity;
} }
...@@ -483,6 +490,12 @@ public function createDuplicate() { ...@@ -483,6 +490,12 @@ public function createDuplicate() {
$uuid = new Uuid(); $uuid = new Uuid();
$duplicate->{$entity_info['entity_keys']['uuid']}->value = $uuid->generate(); $duplicate->{$entity_info['entity_keys']['uuid']}->value = $uuid->generate();
} }
// Check whether the entity type supports revisions and initialize it if so.
if (!empty($entity_info['entity_keys']['revision'])) {
$duplicate->{$entity_info['entity_keys']['revision']}->value = NULL;
}
return $duplicate; return $duplicate;
} }
......
...@@ -36,6 +36,15 @@ abstract class FieldItemBase extends ContextAwareTypedData implements IteratorAg ...@@ -36,6 +36,15 @@ abstract class FieldItemBase extends ContextAwareTypedData implements IteratorAg
*/ */
protected $properties = array(); protected $properties = array();
/**
* Holds any non-property values that get set on the object.
*
* @todo: Remove or refactor once EntityNG conversion is complete.
*
* @var array
*/
protected $extraValues = array();
/** /**
* Overrides ContextAwareTypedData::__construct(). * Overrides ContextAwareTypedData::__construct().
*/ */
...@@ -68,7 +77,7 @@ public function getValue() { ...@@ -68,7 +77,7 @@ public function getValue() {
foreach ($this->getProperties() as $name => $property) { foreach ($this->getProperties() as $name => $property) {
$values[$name] = $property->getValue(); $values[$name] = $property->getValue();
} }
return $values; return $values + $this->extraValues;
} }
/** /**
...@@ -92,9 +101,11 @@ public function setValue($values) { ...@@ -92,9 +101,11 @@ public function setValue($values) {
else { else {
$property->setValue(NULL); $property->setValue(NULL);
} }
unset($values[$name]);
} }
// @todo: Throw an exception for invalid values once conversion is // @todo: Throw an exception for invalid values once conversion is
// totally completed. // totally completed.
$this->extraValues = $values;
} }
/** /**
...@@ -129,7 +140,13 @@ public function set($property_name, $value) { ...@@ -129,7 +140,13 @@ public function set($property_name, $value) {
* Implements \Drupal\Core\Entity\Field\FieldItemInterface::__get(). * Implements \Drupal\Core\Entity\Field\FieldItemInterface::__get().
*/ */
public function __get($name) { public function __get($name) {
return $this->get($name)->getValue(); if (isset($this->properties[$name])) {
return $this->properties[$name]->getValue();
}
// The property is unknown, so try to get it from the extra values.
elseif (isset($this->extraValues[$name])) {
return $this->extraValues[$name];
}
} }
/** /**
...@@ -140,7 +157,13 @@ public function __set($name, $value) { ...@@ -140,7 +157,13 @@ public function __set($name, $value) {
if ($value instanceof TypedDataInterface) { if ($value instanceof TypedDataInterface) {
$value = $value->getValue(); $value = $value->getValue();
} }
$this->get($name)->setValue($value); if (isset($this->properties[$name])) {
$this->properties[$name]->setValue($value);
}
else {
// The property is unknown, so set it to the extra values.
$this->extraValues[$name] = $value;
}
} }
/** /**
......
...@@ -85,6 +85,21 @@ public function setValue($values) { ...@@ -85,6 +85,21 @@ public function setValue($values) {
} }
} }
/**
* Overrides \Drupal\Core\Entity\Field\FieldItemBase::__set().
*/
public function __set($name, $value) {
parent::__set($name, $value);
}
/**
* Overrides \Drupal\Core\Entity\Field\FieldItemBase::__get().
*/
public function __get($name) {
$name = ($name == 'value') ? 'target_id' : $name;
return parent::__get($name);
}
/** /**
* Overrides \Drupal\Core\Entity\Field\FieldItemBase::get(). * Overrides \Drupal\Core\Entity\Field\FieldItemBase::get().
*/ */
...@@ -92,4 +107,13 @@ public function get($property_name) { ...@@ -92,4 +107,13 @@ public function get($property_name) {
$property_name = ($property_name == 'value') ? 'target_id' : $property_name; $property_name = ($property_name == 'value') ? 'target_id' : $property_name;
return parent::get($property_name); return parent::get($property_name);
} }
/**
* Implements \Drupal\Core\Entity\Field\FieldItemInterface::__isset().
*/
public function __isset($property_name) {
$property_name = ($property_name == 'value') ? 'target_id' : $property_name;
return parent::__isset($property_name);
}
} }
...@@ -53,6 +53,13 @@ class EntityWrapper extends ContextAwareTypedData implements IteratorAggregate, ...@@ -53,6 +53,13 @@ class EntityWrapper extends ContextAwareTypedData implements IteratorAggregate,
*/ */
protected $id; protected $id;
/**
* If set, a new entity to create and reference.
*
* @var \Drupal\Core\Entity\EntityInterface
*/
protected $newEntity;
/** /**
* Overrides ContextAwareTypedData::__construct(). * Overrides ContextAwareTypedData::__construct().
*/ */
...@@ -65,6 +72,9 @@ public function __construct(array $definition, $name = NULL, ContextAwareInterfa ...@@ -65,6 +72,9 @@ public function __construct(array $definition, $name = NULL, ContextAwareInterfa
* Overrides \Drupal\Core\TypedData\TypedData::getValue(). * Overrides \Drupal\Core\TypedData\TypedData::getValue().
*/ */
public function getValue() { public function getValue() {
if (isset($this->newEntity)) {
return $this->newEntity;
}
$source = $this->getIdSource(); $source = $this->getIdSource();
$id = $source ? $source->getValue() : $this->id; $id = $source ? $source->getValue() : $this->id;
return $id ? entity_load($this->entityType, $id) : NULL; return $id ? entity_load($this->entityType, $id) : NULL;
...@@ -85,10 +95,17 @@ protected function getIdSource() { ...@@ -85,10 +95,17 @@ protected function getIdSource() {
* Both the entity ID and the entity object may be passed as value. * Both the entity ID and the entity object may be passed as value.
*/ */
public function setValue($value) { public function setValue($value) {
// Support passing in the entity object. // Support passing in the entity object. If it's not yet saved we have
if ($value instanceof EntityInterface) { // to store the whole entity such that it could be saved later on.
if ($value instanceof EntityInterface && $value->isNew()) {
$this->newEntity = $value;
$this->entityType = $value->entityType();
$value = FALSE;
}
elseif ($value instanceof EntityInterface) {
$this->entityType = $value->entityType(); $this->entityType = $value->entityType();
$value = $value->id(); $value = $value->id();
unset($this->newEntity);
} }
elseif (isset($value) && !(is_scalar($value) && !empty($this->definition['constraints']['EntityType']))) { elseif (isset($value) && !(is_scalar($value) && !empty($this->definition['constraints']['EntityType']))) {