diff --git a/core/includes/entity.api.php b/core/includes/entity.api.php index 808d5d5..9c9d123 100644 --- a/core/includes/entity.api.php +++ b/core/includes/entity.api.php @@ -345,19 +345,6 @@ function hook_entity_delete(Drupal\Core\Entity\EntityInterface $entity) { } /** - * Respond to deletion of a entity revision. - * - * @param Drupal\Core\Entity\EntityInterface $entity_revision - * The node revision (node object) that is being deleted. - */ -function hook_entity_revision_delete(Drupal\Core\Entity\EntityInterface $entity_revision) { - db_delete('mytable') - ->condition('entity_type', $entity_revision->entityType()) - ->condition('entity_revision_id', $entity_revision->getRevisionId()) - ->execute(); -} - -/** * Alter or execute an Drupal\Core\Entity\EntityFieldQuery. * * @param Drupal\Core\Entity\EntityFieldQuery $query diff --git a/core/includes/entity.inc b/core/includes/entity.inc index 75c3386..cb0efe0 100644 --- a/core/includes/entity.inc +++ b/core/includes/entity.inc @@ -150,6 +150,18 @@ function entity_revision_load($entity_type, $revision_id) { } /** + * Deletes a node revision. + * + * @param string $entity_type + * The entity type to load, e.g. node or user. + * @param $revision_id + * The revision ID to delete. + */ +function entity_revision_delete($entity_type, $revision_id) { + entity_get_controller($entity_type)->deleteRevision($revision_id); +} + +/** * Loads an entity by UUID. * * Note that some entity types may not support UUIDs. diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php index e82eb7f..07849c6 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php @@ -122,6 +122,13 @@ class ConfigStorageController implements EntityStorageControllerInterface { } /** + * Implements Drupal\Core\Entity\EntityStorageControllerInterface::deleteRevision(). + */ + public function deleteRevision($revision_id) { + return NULL; + } + + /** * Implements Drupal\Core\Entity\EntityStorageControllerInterface::loadByProperties(). */ public function loadByProperties(array $values = array()) { diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php index 0a700a4..6342502 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php @@ -243,6 +243,23 @@ class DatabaseStorageController implements EntityStorageControllerInterface { } /** + * Implements Drupal\Core\Entity\EntityStorageControllerInterface::deleteRevision(). + */ + public function deleteRevision($revision_id) { + if ($revision = $this->loadRevision($revision_id)) { + // Prevent deletion if this is the default revision. + if ($revision->isDefaultRevision()) { + throw new EntityStorageException('Default revision can not be deleted'); + } + + db_delete($this->revisionTable) + ->condition($this->revisionKey, $revision->getRevisionId()) + ->execute(); + $this->invokeHook('revision_delete', $revision); + } + } + + /** * Implements Drupal\Core\Entity\EntityStorageControllerInterface::loadByProperties(). */ public function loadByProperties(array $values = array()) { @@ -484,10 +501,26 @@ class DatabaseStorageController implements EntityStorageControllerInterface { $this->preSave($entity); $this->invokeHook('presave', $entity); - if ($entity->isNew()) { + if (!$entity->isNew()) { + if ($entity->isDefaultRevision()) { + $return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey); + } + else { + // @todo, should a different value be returned when saving an entity + // with $isDefaultRevision = FALSE? + $return = FALSE; + } + if ($this->revisionKey) { + $this->saveRevision($entity); + } + $this->resetCache(array($entity->id())); + $this->postSave($entity, TRUE); + $this->invokeHook('update', $entity); + } + else { $return = drupal_write_record($this->entityInfo['base table'], $entity); if ($this->revisionKey) { - $this->saveRevision($entity, FALSE); + $this->saveRevision($entity); } // Reset general caches, but keep caches specific to certain entities. $this->resetCache(array()); @@ -496,15 +529,6 @@ class DatabaseStorageController implements EntityStorageControllerInterface { $this->postSave($entity, FALSE); $this->invokeHook('insert', $entity); } - else { - $return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey); - if ($this->revisionKey) { - $this->saveRevision($entity); - } - $this->resetCache(array($entity->id())); - $this->postSave($entity, TRUE); - $this->invokeHook('update', $entity); - } // Ignore slave server temporarily. db_ignore_slave(); @@ -520,30 +544,26 @@ class DatabaseStorageController implements EntityStorageControllerInterface { } /** - * Saves a node revision. + * Saves an entity revision. * - * @param Drupal\entity\EntityInterface $node - * The node entity. - * @param boolean $update - * (Optional) TRUE if the entity has been updated, or FALSE if it has been - * inserted. + * @param Drupal\Core\Entity\EntityInterface $entity + * The entity object. */ - protected function saveRevision(EntityInterface $entity, $update = TRUE) { - // Convert the entity into a array as it might not have the same properties + protected function saveRevision(EntityInterface $entity) { + // Convert the entity into an array as it might not have the same properties // as the entity, it is just a raw structure. $record = (array) $entity; - // When saving a new revision for an existing entity, set any existing - // revision ID to NULL so as to ensure that a new revision will actually be - // created. The old revision id is available to hook implementations through - // $entity->original. - if ($update && $entity->isNewRevision() && $record[$this->revisionKey]) { + // When saving a new revision, set any existing revision ID to NULL so as to + // ensure that a new revision will actually be created, then store the old + // revision ID in a separate property for use by hook implementations. + if ($entity->isNewRevision() && $record[$this->revisionKey]) { $record[$this->revisionKey] = NULL; } $this->preSaveRevision($record, $entity); - if (!$update || $entity->isNewRevision()) { + if ($entity->isNewRevision()) { drupal_write_record($this->revisionTable, $record); if ($entity->isDefaultRevision()) { db_update($this->entityInfo['base table']) @@ -598,7 +618,7 @@ class DatabaseStorageController implements EntityStorageControllerInterface { * * @param array $record * The revision array. - * @param Drupal\entity\EntityInterface $entity + * @param Drupal\Core\Entity\EntityInterface $entity * The entity object. */ protected function preSaveRevision(array &$record, EntityInterface $entity) { } @@ -612,7 +632,13 @@ class DatabaseStorageController implements EntityStorageControllerInterface { * The entity object. */ protected function invokeHook($hook, EntityInterface $entity) { - if (!empty($this->entityInfo['fieldable']) && function_exists($function = 'field_attach_' . $hook)) { + $function = 'field_attach_' . $hook; + // @todo: field_attach_delete_revision() is named the wrong way round, + // consider renaming it. + if ($function == 'field_attach_revision_delete') { + $function = 'field_attach_delete_revision'; + } + if (!empty($this->entityInfo['fieldable']) && function_exists($function)) { $function($this->entityType, $entity); } // Invoke the hook. diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php index 64dc336..c23479d 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -104,7 +104,7 @@ class Entity implements IteratorAggregate, EntityInterface { } /** - * Implements EntityInterface::enforceIsNewRevision(). + * Implements EntityInterface::setNewRevision(). */ public function setNewRevision($value = TRUE) { $this->newRevision = $value; diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php index f33066f..7d2151c 100644 --- a/core/lib/Drupal/Core/Entity/EntityInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityInterface.php @@ -64,12 +64,12 @@ public function isNew(); /** - * Returns whether a new revisions should be created on save. + * Returns whether a new revision should be created on save. * - * @return + * @return bool * TRUE if a new revision should be created. * - * @see Drupal\entity\EntityInterface::newRevision() + * @see Drupal\Core\Entity\EntityInterface::setNewRevision() */ public function isNewRevision(); @@ -79,7 +79,7 @@ * @param bool $value * (optional) Whether a new revision should be saved. * - * @see Drupal\entity\EntityInterface::isNewRevision() + * @see Drupal\Core\Entity\EntityInterface::isNewRevision() */ public function setNewRevision($value = TRUE); diff --git a/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php index b0267e5..153cd80 100644 --- a/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php @@ -60,6 +60,16 @@ public function loadRevision($revision_id); /** + * Delete a specific entity revision. + * + * A revision can only be deleted if it's not the currently active one. + * + * @param int $revision_id + * The revision id. + */ + public function deleteRevision($revision_id); + + /** * Load entities by their property values. * * @param array $values diff --git a/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php b/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php index 6aef079..8211ce8 100644 --- a/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php @@ -49,7 +49,7 @@ class BulkDeleteTest extends FieldTestBase { $partial_entities = array(); foreach ($entities as $id => $entity) { // Re-create the entity to match what is expected - // _field_create_entity_from_ids(). + // _field_create_entity_from_ids(). $ids = (object) array( 'entity_id' => $entity->ftid, 'revision_id' => $entity->ftvid, diff --git a/core/modules/field/modules/options/tests/options_test.module b/core/modules/field/modules/options/tests/options_test.module index e21b376..2f92a66 100644 --- a/core/modules/field/modules/options/tests/options_test.module +++ b/core/modules/field/modules/options/tests/options_test.module @@ -28,5 +28,10 @@ function options_test_allowed_values_callback($field, $instance, $entity_type, $ function options_test_dynamic_values_callback($field, $instance, $entity_type, $entity, &$cacheable) { $cacheable = FALSE; // We need the values of the entity as keys. - return drupal_map_assoc(array($entity->ftlabel, $entity->id(), $entity->getRevisionId(), $entity->bundle())); + return drupal_map_assoc(array( + $entity->ftlabel, + $entity->id(), + $entity->getRevisionId(), + $entity->bundle(), + )); } 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 9ff94d1..431ac11 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 @@ -28,6 +28,7 @@ function field_test_entity_info() { 'test_entity' => array( 'name' => t('Test Entity'), 'entity class' => 'Drupal\field_test\TestEntity', + 'controller class' => 'Drupal\field_test\TestEntityController', 'form controller class' => array( 'default' => 'Drupal\field_test\TestEntityFormController', ), @@ -47,6 +48,7 @@ function field_test_entity_info() { 'test_cacheable_entity' => array( 'name' => t('Test Entity, cacheable'), 'entity class' => 'Drupal\field_test\TestEntity', + 'controller class' => 'Drupal\field_test\TestEntityController', 'fieldable' => TRUE, 'field cache' => TRUE, 'entity keys' => array( @@ -59,6 +61,7 @@ function field_test_entity_info() { ), 'test_entity_bundle_key' => array( 'entity class' => 'Drupal\field_test\TestEntity', + 'controller class' => 'Drupal\field_test\TestEntityController', 'name' => t('Test Entity with a bundle key.'), 'base table' => 'test_entity_bundle_key', 'fieldable' => TRUE, @@ -74,6 +77,7 @@ function field_test_entity_info() { 'test_entity_bundle' => array( 'name' => t('Test Entity with a specified bundle.'), 'entity class' => 'Drupal\field_test\TestEntity', + 'controller class' => 'Drupal\field_test\TestEntityController', 'base table' => 'test_entity_bundle', 'fieldable' => TRUE, 'field cache' => FALSE, @@ -88,6 +92,7 @@ function field_test_entity_info() { 'test_entity_no_label' => array( 'name' => t('Test entity without label'), 'entity class' => 'Drupal\field_test\TestEntity', + 'controller class' => 'Drupal\field_test\TestEntityController', 'fieldable' => TRUE, 'field cache' => FALSE, 'base table' => 'test_entity', @@ -103,6 +108,7 @@ function field_test_entity_info() { 'test_entity_label' => array( 'name' => t('Test entity label'), 'entity class' => 'Drupal\field_test\TestEntity', + 'controller class' => 'Drupal\field_test\TestEntityController', 'fieldable' => TRUE, 'field cache' => FALSE, 'base table' => 'test_entity', @@ -119,6 +125,7 @@ function field_test_entity_info() { 'test_entity_label_callback' => array( 'name' => t('Test entity label callback'), 'entity class' => 'Drupal\field_test\TestEntity', + 'controller class' => 'Drupal\field_test\TestEntityController', 'fieldable' => TRUE, 'field cache' => FALSE, 'base table' => 'test_entity', @@ -230,6 +237,8 @@ function field_test_create_entity($id = 1, $vid = 1, $bundle = 'test_bundle', $l } if (isset($vid)) { $entity->ftvid = $vid; + // Flag to make sure that the provided vid is used for a new revision. + $entity->use_provided_revision_id = $vid; } $entity->fttype = $bundle; @@ -288,17 +297,6 @@ function field_test_entity_edit(TestEntity $entity) { } /** - * Updates the form state's entity by processing this submission's values. - * - * @return Drupal\field_test\TestEntity - */ -function field_test_entity_form_submit_build_test_entity($form, &$form_state) { - $entity = $form_state['test_entity']; - entity_form_submit_build_entity('test_entity', $entity, $form, $form_state); - return $entity; -} - -/** * Form combining two separate entities. */ function field_test_entity_nested_form($form, &$form_state, $entity_1, $entity_2) { diff --git a/core/modules/node/lib/Drupal/node/NodeFormController.php b/core/modules/node/lib/Drupal/node/NodeFormController.php index ca413a7..dedf805 100644 --- a/core/modules/node/lib/Drupal/node/NodeFormController.php +++ b/core/modules/node/lib/Drupal/node/NodeFormController.php @@ -117,7 +117,7 @@ class NodeFormController extends EntityFormController { '#type' => 'fieldset', '#title' => t('Revision information'), '#collapsible' => TRUE, - // Collapsed by default when "Create new revision" is unchecked + // Collapsed by default when "Create new revision" is unchecked. '#collapsed' => !$node->isNewRevision(), '#group' => 'additional_settings', '#attributes' => array( diff --git a/core/modules/node/lib/Drupal/node/NodeStorageController.php b/core/modules/node/lib/Drupal/node/NodeStorageController.php index 482d534..3993d24 100644 --- a/core/modules/node/lib/Drupal/node/NodeStorageController.php +++ b/core/modules/node/lib/Drupal/node/NodeStorageController.php @@ -98,7 +98,7 @@ class NodeStorageController extends DatabaseStorageController { } /** - * Overrides Drupal\entity\DatabaseStorageController::preSaveRevision(). + * Overrides Drupal\Core\Entity\DatabaseStorageController::preSaveRevision(). */ protected function preSaveRevision(array &$record, EntityInterface $entity) { if ($entity->isNewRevision()) { diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php index 811c586..7404685 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php @@ -48,7 +48,7 @@ function setUp() { $this->drupalCreateNode($settings); $node = node_load($node->nid); // Make sure we get revision information. $settings = get_object_vars($node); - $settings['revision'] = 1; + $settings['isDefaultRevision'] = TRUE; $nodes[] = $node; } @@ -141,11 +141,6 @@ function testRevisions() { ->fetchCol(); $default_revision_vid = $default_revision[0]; $this->assertTrue($new_node_revision->vid > $default_revision_vid, 'Revision vid is greater than default revision vid.'); - - $fixed_revision = $this->drupalCreateNode(array('vid' => 9999)); - - $loaded_revision = node_revision_load(9999); - $this->assertEqual($loaded_revision->nid, $fixed_revision->nid); } /** diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 68e4845..2d6ed08 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -1082,10 +1082,10 @@ function node_revision_load($vid = NULL) { /** * Prepares a node for saving by populating the author and creation date. * - * @param Drupal\node\Node $node + * @param object $node * A node object. * - * @return Drupal\node\Node + * @return object * An updated node object. */ function node_submit(Node $node) { @@ -1157,21 +1157,7 @@ function node_delete_multiple($nids) { * TRUE if the revision deletion was successful; otherwise, FALSE. */ function node_revision_delete($revision_id) { - if ($revision = node_revision_load($revision_id)) { - // Prevent deleting the default revision. - if ($revision->isDefaultRevision()) { - return FALSE; - } - - db_delete('node_revision') - ->condition('nid', $revision->nid) - ->condition('vid', $revision->vid) - ->execute(); - module_invoke_all('node_revision_delete', $revision); - field_attach_delete_revision('node', $revision); - return TRUE; - } - return FALSE; + entity_revision_delete('node', $revision_id); } /** diff --git a/core/modules/node/node.pages.inc b/core/modules/node/node.pages.inc index 1100f64..6e238ad 100644 --- a/core/modules/node/node.pages.inc +++ b/core/modules/node/node.pages.inc @@ -325,7 +325,7 @@ function node_revision_revert_confirm_submit($form, &$form_state) { $node_revision = $form['#node_revision']; $node_revision->setNewRevision(); // Make this the new default revision for the node. - $node_revision->isDefaultRevision = TRUE; + $node_revision->isDefaultRevision(TRUE); // The revision timestamp will be updated when the revision is saved. Keep the // original one for the confirmation message. diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldQueryTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldQueryTest.php index ca99a37..fa0f4b2 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldQueryTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldQueryTest.php @@ -25,21 +25,6 @@ class EntityFieldQueryTest extends WebTestBase { */ public static $modules = array('node', 'field_test', 'entity_query_access_test', 'node_access_test'); - /** - * The first revision id. - * - * @var integer - */ - protected $revision_id1; - - /** - * The second revision id - * - * @var integer - */ - protected $revision_id2; - - public static function getInfo() { return array( 'name' => 'Entity query', @@ -125,6 +110,8 @@ function setUp() { field_create_instance($instances[1]); $this->instances = $instances; + // Write entity base table if there is one. + $entities = array(); // Create entities which have a 'bundle key' defined. for ($i = 1; $i < 7; $i++) { @@ -160,11 +147,13 @@ function setUp() { } // Add two revisions to an entity. - for ($i = 1; $i <= 2; $i++) { - $entity->{$this->field_names[0]}[LANGUAGE_NOT_SPECIFIED][0]['value'] = 100 + $i; + for ($i = 100; $i < 102; $i++) { + $entity->ftvid = $i; + // Flag to make sure that the provided vid is used for a new revision. + $entity->use_provided_revision_id = $i; + $entity->{$this->field_names[0]}[LANGUAGE_NOT_SPECIFIED][0]['value'] = $i; $entity->setNewRevision(); $entity->save(); - $this->{'revision_id' . $i} = $entity->getRevisionId(); } } @@ -259,18 +248,18 @@ function testEntityFieldQuery() { $query = new EntityFieldQuery(); $query ->entityCondition('entity_type', 'test_entity') - ->fieldCondition($this->fields[0], 'value', 101, '>=') + ->fieldCondition($this->fields[0], 'value', 100, '>=') ->age(FIELD_LOAD_REVISION); $this->assertEntityFieldQuery($query, array( - array('test_entity', $this->revision_id1), - array('test_entity', $this->revision_id2), + array('test_entity', 100), + array('test_entity', 101), ), 'Test revision age.'); // Test that fields attached to the non-revision supporting entity // 'test_entity_bundle_key' are reachable in FIELD_LOAD_REVISION. $query = new EntityFieldQuery(); $query - ->fieldCondition($this->fields[0], 'value', 101, '<') + ->fieldCondition($this->fields[0], 'value', 100, '<') ->age(FIELD_LOAD_REVISION); $this->assertEntityFieldQuery($query, array( array('test_entity_bundle_key', 1), @@ -839,7 +828,7 @@ function testEntityFieldQuery() { ), 'Test the "not in" operation on a property.'); $query = new EntityFieldQuery(); - $query->fieldCondition($this->fields[0], 'value', array(3, 4, 101, 102), 'NOT IN'); + $query->fieldCondition($this->fields[0], 'value', array(3, 4, 100, 101), 'NOT IN'); $this->assertEntityFieldQuery($query, array( array('test_entity_bundle_key', 1), array('test_entity_bundle_key', 2),