diff --git a/core/CHANGELOG.txt b/core/CHANGELOG.txt index e8eec3cb678186c3715b84ad92276412bfbf8f23..85556b8b11c9db074c03c37d3fc25d81ef0d931f 100644 --- a/core/CHANGELOG.txt +++ b/core/CHANGELOG.txt @@ -7,6 +7,7 @@ Drupal 8.0, xxxx-xx-xx (development version) * Drupal now understands the concept of a "default" revision, tracked independently from the latest revision, allowing for the creation of drafts while the current revision stays published. + * All entity types, not just nodes, now have support for revisions. - Replaced the core routing system with one built on the Symfony2 framework. - Configuration: * Added a centralized file-based configuration system. diff --git a/core/includes/entity.inc b/core/includes/entity.inc index 75c3386c4f24e4fd4e5674295860fa94734f5e8d..cb0efe018cfff3a466bc19cc266a977088e20433 100644 --- a/core/includes/entity.inc +++ b/core/includes/entity.inc @@ -149,6 +149,18 @@ function entity_revision_load($entity_type, $revision_id) { return entity_get_controller($entity_type)->loadRevision($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. * diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php index e82eb7f2549f49cf7727c5774bfb63095028737e..07849c65a7988b0cf1b4ac4b4c0d456c10bd1bdc 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php @@ -121,6 +121,13 @@ public function loadRevision($revision_id) { return FALSE; } + /** + * Implements Drupal\Core\Entity\EntityStorageControllerInterface::deleteRevision(). + */ + public function deleteRevision($revision_id) { + return NULL; + } + /** * Implements Drupal\Core\Entity\EntityStorageControllerInterface::loadByProperties(). */ diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php index 1fa59ec527e92f81b170f4fcf99e428969bc408a..63425029902ce79752e54b5abc67c1e53de4ff80 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php @@ -242,6 +242,23 @@ public function loadRevision($revision_id) { return reset($queried_entities); } + /** + * 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(). */ @@ -446,6 +463,13 @@ public function delete($ids) { db_delete($this->entityInfo['base table']) ->condition($this->idKey, $ids, 'IN') ->execute(); + + if ($this->revisionKey) { + db_delete($this->revisionTable) + ->condition($this->idKey, $ids, 'IN') + ->execute(); + } + // Reset the cache as soon as the changes have been applied. $this->resetCache($ids); @@ -478,13 +502,26 @@ public function save(EntityInterface $entity) { $this->invokeHook('presave', $entity); if (!$entity->isNew()) { - $return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey); + 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); + } // Reset general caches, but keep caches specific to certain entities. $this->resetCache(array()); @@ -506,6 +543,43 @@ public function save(EntityInterface $entity) { } } + /** + * Saves an entity revision. + * + * @param Drupal\Core\Entity\EntityInterface $entity + * The entity object. + */ + 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, 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 ($entity->isNewRevision()) { + drupal_write_record($this->revisionTable, $record); + if ($entity->isDefaultRevision()) { + db_update($this->entityInfo['base table']) + ->fields(array($this->revisionKey => $record[$this->revisionKey])) + ->condition($this->idKey, $entity->id()) + ->execute(); + } + $entity->setNewRevision(FALSE); + } + else { + drupal_write_record($this->revisionTable, $record, $this->revisionKey); + } + // Make sure to update the new revision key for the entity. + $entity->{$this->revisionKey} = $record[$this->revisionKey]; + } + /** * Acts on an entity before the presave hook is invoked. * @@ -539,6 +613,16 @@ protected function preDelete($entities) { } */ protected function postDelete($entities) { } + /** + * Act on a revision before being saved. + * + * @param array $record + * The revision array. + * @param Drupal\Core\Entity\EntityInterface $entity + * The entity object. + */ + protected function preSaveRevision(array &$record, EntityInterface $entity) { } + /** * Invokes a hook on behalf of the entity. * @@ -548,7 +632,13 @@ protected function postDelete($entities) { } * 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 325ce6d320a1763af8afc66797f44344831ae385..3d65b5e11c39fa9d4081ce19292e0f19f7d4662b 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -42,6 +42,13 @@ class Entity implements IteratorAggregate, EntityInterface { */ protected $enforceIsNew; + /** + * Boolean indicating whether a new revision should be created on save. + * + * @var bool + */ + protected $newRevision = FALSE; + /** * Indicates whether this is the default revision. * @@ -81,6 +88,14 @@ public function isNew() { return !empty($this->enforceIsNew) || !$this->id(); } + /** + * Implements EntityInterface::isNewRevision(). + */ + public function isNewRevision() { + $info = $this->entityInfo(); + return $this->newRevision || (!empty($info['entity keys']['revision']) && !$this->getRevisionId()); + } + /** * Implements EntityInterface::enforceIsNew(). */ @@ -88,6 +103,13 @@ public function enforceIsNew($value = TRUE) { $this->enforceIsNew = $value; } + /** + * Implements EntityInterface::setNewRevision(). + */ + public function setNewRevision($value = TRUE) { + $this->newRevision = $value; + } + /** * Implements EntityInterface::entityType(). */ diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php index a6d21bb580a66489a7b7b8f51e68d59949e7313c..7d2151c0212187f38cb0aec91d4a974a3da9ed61 100644 --- a/core/lib/Drupal/Core/Entity/EntityInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityInterface.php @@ -63,6 +63,26 @@ public function uuid(); */ public function isNew(); + /** + * Returns whether a new revision should be created on save. + * + * @return bool + * TRUE if a new revision should be created. + * + * @see Drupal\Core\Entity\EntityInterface::setNewRevision() + */ + public function isNewRevision(); + + /** + * Enforces an entity to be saved as a new revision. + * + * @param bool $value + * (optional) Whether a new revision should be saved. + * + * @see Drupal\Core\Entity\EntityInterface::isNewRevision() + */ + public function setNewRevision($value = TRUE); + /** * Enforces an entity to be new. * diff --git a/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php index b0267e5ce5ff0888af07002c026efab90f62e3eb..153cd80df794f26cd600954bec25dcfed0b73574 100644 --- a/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php @@ -59,6 +59,16 @@ public function load(array $ids = NULL); */ 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. * diff --git a/core/modules/book/book.admin.inc b/core/modules/book/book.admin.inc index d4d6f0bfd11fbf03669d0ab4c6e43d4266197c85..fbd8dda1d6364d88896feb50ae119129fb1d6ffd 100644 --- a/core/modules/book/book.admin.inc +++ b/core/modules/book/book.admin.inc @@ -162,7 +162,7 @@ function book_admin_edit_submit($form, &$form_state) { $langcode = LANGUAGE_NOT_SPECIFIED; $node->title = $values['title']; $node->book['link_title'] = $values['title']; - $node->revision = 1; + $node->setNewRevision(); $node->log = t('Title changed from %original to %current.', array('%original' => $node->title, '%current' => $values['title'])); $node->save(); diff --git a/core/modules/book/book.module b/core/modules/book/book.module index fd4fac9f2c22332219206e8b03855668b53b4fed..7c43e28a4e2468f950b66fdf86bcf61ee2615475 100644 --- a/core/modules/book/book.module +++ b/core/modules/book/book.module @@ -927,7 +927,7 @@ function book_page_alter(&$page) { function book_node_presave(Node $node) { // Always save a revision for non-administrators. if (!empty($node->book['bid']) && !user_access('administer nodes')) { - $node->revision = 1; + $node->setNewRevision(); } // Make sure a new node gets a new menu link. if (empty($node->nid)) { diff --git a/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php b/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php index 84b6f063d2d8bf38e701cd15b57c93ec0b7047d9..8211ce81a1c16552524b82ede6880acf30179674 100644 --- a/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php +++ b/core/modules/field/lib/Drupal/field/Tests/BulkDeleteTest.php @@ -48,13 +48,15 @@ public static function getInfo() { protected function convertToPartialEntities($entities, $field_name) { $partial_entities = array(); foreach ($entities as $id => $entity) { - // Re-create the entity with only the required keys, remove label as that - // is not present when using _field_create_entity_from_ids(). - $partial_entities[$id] = field_test_create_entity($entity->ftid, $entity->ftvid, $entity->fttype, $entity->ftlabel); - // Remove the label and set enforceIsNew to NULL to make sure that the - // entity classes match the actual arguments. - unset($partial_entities[$id]->ftlabel); - $partial_entities[$id]->enforceIsNew(NULL); + // Re-create the entity to match what is expected + // _field_create_entity_from_ids(). + $ids = (object) array( + 'entity_id' => $entity->ftid, + 'revision_id' => $entity->ftvid, + 'bundle' => $entity->fttype, + 'entity_type' => 'test_entity', + ); + $partial_entities[$id] = _field_create_entity_from_ids($ids); $partial_entities[$id]->$field_name = $entity->$field_name; } return $partial_entities; diff --git a/core/modules/field/modules/options/lib/Drupal/options/Tests/OptionsSelectDynamicValuesTest.php b/core/modules/field/modules/options/lib/Drupal/options/Tests/OptionsSelectDynamicValuesTest.php index 0a612e87e2ef9d92e50b35000f372665a71a9e9e..4344d214387992a60dba421fcdae43f428c5af5d 100644 --- a/core/modules/field/modules/options/lib/Drupal/options/Tests/OptionsSelectDynamicValuesTest.php +++ b/core/modules/field/modules/options/lib/Drupal/options/Tests/OptionsSelectDynamicValuesTest.php @@ -24,8 +24,8 @@ public static function getInfo() { */ function testSelectListDynamic() { // Create an entity. - $this->entity->is_new = TRUE; - field_test_entity_save($this->entity); + $this->entity->save(); + // Create a web user. $web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content')); $this->drupalLogin($web_user); diff --git a/core/modules/field/modules/options/tests/options_test.module b/core/modules/field/modules/options/tests/options_test.module index 604205b0e882da290f850cbac26a2a36adcb6529..2f92a6659df3084b299bbe851af56c0b3aed830c 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_merge(array($entity->ftlabel), array($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 7acb4bb8f2f47b2845687638c0526917da1a7851..431ac112e5be5e46734f97d21520d41fbfd2abb3 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 @@ -237,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; @@ -244,6 +246,7 @@ function field_test_create_entity($id = 1, $vid = 1, $bundle = 'test_bundle', $l $entity->ftlabel = $label; // Make sure the entity will saved even if a primary key is provided. $entity->enforceIsNew(); + $entity->setNewRevision(); return $entity; } @@ -293,7 +296,6 @@ function field_test_entity_edit(TestEntity $entity) { return entity_get_form($entity); } - /** * Form combining two separate entities. */ diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntity.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntity.php index 18cfff54a36ab18985c22154a94bff5bf6f6fa20..25898c8bf40309c8bf1061b9849bcc746821fe83 100644 --- a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntity.php +++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntity.php @@ -35,6 +35,13 @@ class TestEntity extends Entity { */ public $fttype; + /** + * Label property + * + * @var string + */ + public $ftlabel; + /** * Overrides Drupal\Core\Entity\Entity::id(). */ diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityController.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityController.php index 16f66df28dab1bb4f6b38e67a5bf45ec7a4d4ec4..ab62a9a86f25f53b19253fb7c5a89c811b330d1a 100644 --- a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityController.php +++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityController.php @@ -16,36 +16,12 @@ class TestEntityController extends DatabaseStorageController { /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::preSave(). + * Overrides Drupal\Core\Entity\DatabaseStorageController::preSaveRevision(). */ - public function preSave(EntityInterface $entity) { - // Prepare for a new revision. - if (!$entity->isNew() && !empty($entity->revision)) { - $entity->old_ftvid = $entity->ftvid; - $entity->ftvid = NULL; - } - } - - /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::postSave(). - */ - public function postSave(EntityInterface $entity, $update) { - // Only the test_entity entity type has revisions. - if ($entity->entityType() == 'test_entity') { - $update_entity = TRUE; - if (!$update || !empty($entity->revision)) { - drupal_write_record('test_entity_revision', $entity); - } - else { - drupal_write_record('test_entity_revision', $entity, 'ftvid'); - $update_entity = FALSE; - } - if ($update_entity) { - db_update('test_entity') - ->fields(array('ftvid' => $entity->ftvid)) - ->condition('ftid', $entity->ftid) - ->execute(); - } + public function preSaveRevision(array &$record, EntityInterface $entity) { + // Allow for predefined revision ids. + if (!empty($record['use_provided_revision_id'])) { + $record['ftvid'] = $record['use_provided_revision_id']; } } diff --git a/core/modules/file/file.field.inc b/core/modules/file/file.field.inc index cbc22465ec0c612f628953ead31a9d4a75919b49..187d57c91d993d869e9970ac97edbd8e2e507c84 100644 --- a/core/modules/file/file.field.inc +++ b/core/modules/file/file.field.inc @@ -231,7 +231,7 @@ function file_field_insert($entity_type, $entity, $field, $instance, $langcode, function file_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) { // On new revisions, all files are considered to be a new usage and no // deletion of previous file usages are necessary. - if (!empty($entity->revision)) { + if (!empty($entity->original) && $entity->getRevisionId() != $entity->original->getRevisionId()) { foreach ($items as $item) { file_usage_add(file_load($item['fid']), 'file', $entity_type, $entity->id()); } diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module index f36549137a8f73a09b77f412c031f4f18da78ad8..af2daa985b979a30b1a961ead552a2b6d7ec3981 100644 --- a/core/modules/forum/forum.module +++ b/core/modules/forum/forum.module @@ -346,7 +346,9 @@ function forum_node_presave(Node $node) { */ function forum_node_update(Node $node) { if (_forum_node_check_node_type($node)) { - if (empty($node->revision) && db_query('SELECT tid FROM {forum} WHERE nid=:nid', array(':nid' => $node->nid))->fetchField()) { + // If this is not a new revision and does exist, update the forum record, + // otherwise insert a new one. + if ($node->getRevisionId() == $node->original->getRevisionId() && db_query('SELECT tid FROM {forum} WHERE nid=:nid', array(':nid' => $node->nid))->fetchField()) { if (!empty($node->forum_tid)) { db_update('forum') ->fields(array('tid' => $node->forum_tid)) diff --git a/core/modules/node/lib/Drupal/node/NodeFormController.php b/core/modules/node/lib/Drupal/node/NodeFormController.php index b9bf7eb709f68de4b5135df2e80f88267d3e8940..dedf805bd40c863078c0ca73997cf95f1b7d6398 100644 --- a/core/modules/node/lib/Drupal/node/NodeFormController.php +++ b/core/modules/node/lib/Drupal/node/NodeFormController.php @@ -44,7 +44,7 @@ protected function prepareEntity(EntityInterface $node) { $node->log = NULL; } // Always use the default revision setting. - $node->revision = in_array('revision', $node_options); + $node->setNewRevision(in_array('revision', $node_options)); node_invoke($node, 'prepare'); module_invoke_all('node_prepare', $node); @@ -117,8 +117,8 @@ public function form(array $form, array &$form_state, EntityInterface $node) { '#type' => 'fieldset', '#title' => t('Revision information'), '#collapsible' => TRUE, - // Collapsed by default when "Create new revision" is unchecked - '#collapsed' => !$node->revision, + // Collapsed by default when "Create new revision" is unchecked. + '#collapsed' => !$node->isNewRevision(), '#group' => 'additional_settings', '#attributes' => array( 'class' => array('node-form-revision-information'), @@ -127,20 +127,20 @@ public function form(array $form, array &$form_state, EntityInterface $node) { 'js' => array(drupal_get_path('module', 'node') . '/node.js'), ), '#weight' => 20, - '#access' => $node->revision || user_access('administer nodes'), + '#access' => $node->isNewRevision() || user_access('administer nodes'), ); $form['revision_information']['revision'] = array( '#type' => 'checkbox', '#title' => t('Create new revision'), - '#default_value' => $node->revision, + '#default_value' => $node->isNewRevision(), '#access' => user_access('administer nodes'), ); // Check the revision log checkbox when the log textarea is filled in. // This must not happen if "Create new revision" is enabled by default, // since the state would auto-disable the checkbox otherwise. - if (!$node->revision) { + if (!$node->isNewRevision()) { $form['revision_information']['revision']['#states'] = array( 'checked' => array( 'textarea[name="log"]' => array('empty' => FALSE), @@ -321,6 +321,11 @@ public function submit(array $form, array &$form_state) { // Build the node object from the submitted values. $node = parent::submit($form, $form_state); + // Save as a new revision if requested to do so. + if (!empty($form_state['values']['revision'])) { + $node->setNewRevision(); + } + node_submit($node); foreach (module_implements('node_submit') as $module) { $function = $module . '_node_submit'; diff --git a/core/modules/node/lib/Drupal/node/NodeStorageController.php b/core/modules/node/lib/Drupal/node/NodeStorageController.php index 02e694e18672de11274601ccf1f37a61a866360f..3993d24a935900f4ee8f5c00643ed1fb5578e189 100644 --- a/core/modules/node/lib/Drupal/node/NodeStorageController.php +++ b/core/modules/node/lib/Drupal/node/NodeStorageController.php @@ -9,8 +9,6 @@ use Drupal\Core\Entity\DatabaseStorageController; use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Entity\EntityStorageException; -use Exception; /** * Controller class for nodes. @@ -34,135 +32,6 @@ public function create(array $values) { return $node; } - /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::delete(). - */ - public function delete($ids) { - $entities = $ids ? $this->load($ids) : FALSE; - if (!$entities) { - // If no IDs or invalid IDs were passed, do nothing. - return; - } - $transaction = db_transaction(); - - try { - $this->preDelete($entities); - foreach ($entities as $id => $entity) { - $this->invokeHook('predelete', $entity); - } - $ids = array_keys($entities); - - db_delete($this->entityInfo['base table']) - ->condition($this->idKey, $ids, 'IN') - ->execute(); - - if ($this->revisionKey) { - db_delete($this->revisionTable) - ->condition($this->idKey, $ids, 'IN') - ->execute(); - } - - // Reset the cache as soon as the changes have been applied. - $this->resetCache($ids); - - $this->postDelete($entities); - foreach ($entities as $id => $entity) { - $this->invokeHook('delete', $entity); - } - // Ignore slave server temporarily. - db_ignore_slave(); - } - catch (Exception $e) { - $transaction->rollback(); - watchdog_exception($this->entityType, $e); - throw new EntityStorageException($e->getMessage, $e->getCode, $e); - } - } - - /** - * Overrides Drupal\Core\Entity\DatabaseStorageController::save(). - */ - public function save(EntityInterface $entity) { - $transaction = db_transaction(); - try { - // Load the stored entity, if any. - if (!$entity->isNew() && !isset($entity->original)) { - $entity->original = entity_load_unchanged($this->entityType, $entity->id()); - } - - $this->preSave($entity); - $this->invokeHook('presave', $entity); - - if ($entity->isNew()) { - $op = 'insert'; - $return = drupal_write_record($this->entityInfo['base table'], $entity); - $entity->enforceIsNew(FALSE); - } - else { - $op = 'update'; - // Update the base node table, but only if this revision is marked as - // the default. - 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); - } - - // Reset general caches, but keep caches specific to certain entities. - $this->resetCache($op == 'update' ? array($entity->{$this->idKey}): array()); - - $this->postSave($entity, $op == 'update'); - $this->invokeHook($op, $entity); - - // Ignore slave server temporarily. - db_ignore_slave(); - unset($entity->original); - - return $return; - } - catch (Exception $e) { - $transaction->rollback(); - watchdog_exception($this->entityType, $e); - throw new EntityStorageException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * Saves a node revision. - * - * @param Drupal\Core\Entity\EntityInterface $node - * The node entity. - */ - protected function saveRevision(EntityInterface $entity) { - $record = clone $entity; - $record->uid = $entity->revision_uid; - $record->timestamp = $entity->revision_timestamp; - - if (empty($entity->{$this->revisionKey}) || !empty($entity->revision)) { - drupal_write_record($this->revisionTable, $record); - // Only update the base node table if this revision is the default. - if ($entity->isDefaultRevision()) { - db_update($this->entityInfo['base table']) - ->fields(array($this->revisionKey => $record->{$this->revisionKey})) - ->condition($this->idKey, $entity->{$this->idKey}) - ->execute(); - } - } - else { - drupal_write_record($this->revisionTable, $record, $this->revisionKey); - } - // Make sure to update the new revision key for the entity. - $entity->{$this->revisionKey} = $record->{$this->revisionKey}; - } - /** * Overrides Drupal\Core\Entity\DatabaseStorageController::attachLoad(). */ @@ -218,39 +87,6 @@ protected function invokeHook($hook, EntityInterface $node) { } parent::invokeHook($hook, $node); - - if ($hook == 'presave') { - if ($node->isNew() || !empty($node->revision)) { - // When inserting either a new node or a new node revision, $node->log - // must be set because {node_revision}.log is a text column and therefore - // cannot have a default value. However, it might not be set at this - // point (for example, if the user submitting a node form does not have - // permission to create revisions), so we ensure that it is at least an - // empty string in that case. - // @todo: Make the {node_revision}.log column nullable so that we can - // remove this check. - if (!isset($node->log)) { - $node->log = ''; - } - } - elseif (!isset($node->log) || $node->log === '') { - // If we are updating an existing node without adding a new revision, we - // need to make sure $node->log is unset whenever it is empty. As long as - // $node->log is unset, drupal_write_record() will not attempt to update - // the existing database column when re-saving the revision; therefore, - // this code allows us to avoid clobbering an existing log entry with an - // empty one. - unset($node->log); - } - - // When saving a new node revision, unset any existing $node->vid 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 node hook implementations. - if (!$node->isNew() && !empty($node->revision) && $node->vid) { - $node->old_vid = $node->vid; - $node->vid = NULL; - } - } } /** @@ -259,14 +95,39 @@ protected function invokeHook($hook, EntityInterface $node) { protected function preSave(EntityInterface $node) { // Before saving the node, set changed and revision times. $node->changed = REQUEST_TIME; + } - if ($this->revisionKey && !empty($node->revision)) { - $node->revision_timestamp = REQUEST_TIME; - - if (!isset($node->revision_uid)) { - $node->revision_uid = $GLOBALS['user']->uid; + /** + * Overrides Drupal\Core\Entity\DatabaseStorageController::preSaveRevision(). + */ + protected function preSaveRevision(array &$record, EntityInterface $entity) { + if ($entity->isNewRevision()) { + // When inserting either a new node or a new node revision, $node->log + // must be set because {node_revision}.log is a text column and therefore + // cannot have a default value. However, it might not be set at this + // point (for example, if the user submitting a node form does not have + // permission to create revisions), so we ensure that it is at least an + // empty string in that case. + // @todo: Make the {node_revision}.log column nullable so that we can + // remove this check. + if (!isset($record['log'])) { + $record['log'] = ''; } } + elseif (!isset($record['log']) || $record['log'] === '') { + // If we are updating an existing node without adding a new revision, we + // need to make sure $node->log is unset whenever it is empty. As long as + // $node->log is unset, drupal_write_record() will not attempt to update + // the existing database column when re-saving the revision; therefore, + // this code allows us to avoid clobbering an existing log entry with an + // empty one. + unset($record['log']); + } + + if ($entity->isNewRevision()) { + $record['timestamp'] = REQUEST_TIME; + $record['uid'] = isset($record['revision_uid']) ? $record['revision_uid'] : $GLOBALS['user']->uid; + } } /** diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeRevisionPermissionsTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionPermissionsTest.php index 9a5bb56c33ad0076c7d44884d8a9d563c1a4fbd8..7af34292833b04a31253223f1ea944aaba03eba9 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeRevisionPermissionsTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionPermissionsTest.php @@ -39,7 +39,7 @@ function setUp() { for ($i = 0; $i < 3; $i++) { // Create a revision for the same nid and settings with a random log. $revision = clone $node; - $revision->revision = 1; + $revision->setNewRevision(); $revision->log = $this->randomName(32); node_save($revision); $this->node_revisions[] = $revision; diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php index 5a40510fd7f750f77068a4515486b76528222d1c..7404685ffb8faacd28db4742f94df535eab1957f 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php @@ -121,7 +121,7 @@ function testRevisions() { $new_body = $this->randomName(); $new_node_revision->body[LANGUAGE_NOT_SPECIFIED][0]['value'] = $new_body; // Save this as a non-default revision. - $new_node_revision->revision = TRUE; + $new_node_revision->setNewRevision(); $new_node_revision->isDefaultRevision = FALSE; node_save($new_node_revision); @@ -160,7 +160,7 @@ function testNodeRevisionWithoutLogMessage() { $node = clone $node; $node->title = $new_title; $node->log = ''; - $node->revision = FALSE; + $node->setNewRevision(FALSE); $node->save(); $this->drupalGet('node/' . $node->nid); @@ -177,7 +177,7 @@ function testNodeRevisionWithoutLogMessage() { $node = clone $node; $node->title = $new_title; - $node->revision = TRUE; + $node->setNewRevision(); $node->log = NULL; $node->save(); diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 2dd1b57ca145b2ea505be8a940e5f429344ad0f1..8d5803d2be5d343b814a6f8927b9f53806c8320d 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -1082,13 +1082,13 @@ function node_revision_load($vid = NULL) { /** * Prepares a node for saving by populating the author and creation date. * - * @param object $node + * @param Drupal\node\Node $node * A node object. * - * @return object + * @return Drupal\node\Node * An updated node object. */ -function node_submit($node) { +function node_submit(Node $node) { global $user; // A user might assign the node author by entering a user name in the node @@ -1103,7 +1103,7 @@ function node_submit($node) { } // If a new revision is created, save the current user as revision author. - if (!empty($node->revision)) { + if ($node->isNewRevision()) { $node->revision_uid = $user->uid; } @@ -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 570ae88b17dca6fd116f84932e56559fe29b16a5..041dd35262f7701905f8faa7641d4c31142647f8 100644 --- a/core/modules/node/node.pages.inc +++ b/core/modules/node/node.pages.inc @@ -333,9 +333,9 @@ function node_revision_revert_confirm($form, $form_state, $node_revision) { */ function node_revision_revert_confirm_submit($form, &$form_state) { $node_revision = $form['#node_revision']; - $node_revision->revision = 1; + $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/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index 400d8851ea6b764b86671d5507c8b977423aac88..8ad58efee688c06404ac7f84d2135e433259e03a 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -206,7 +206,6 @@ protected function drupalCreateNode($settings = array()) { 'status' => 1, 'sticky' => 0, 'type' => 'page', - 'revisions' => NULL, 'langcode' => LANGUAGE_NOT_SPECIFIED, ); @@ -235,6 +234,9 @@ protected function drupalCreateNode($settings = array()) { $settings['body'][$settings['langcode']][0] += $body; $node = entity_create('node', $settings); + if (!empty($settings['revision'])) { + $node->setNewRevision(); + } $node->save(); // Small hack to link revisions to our test user. 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 305c2989242ed54e69246216be3ca4f648bfacdd..fa0f4b29b901a0d3c3b8d9a9a4464bbb4d472b31 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldQueryTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldQueryTest.php @@ -148,19 +148,12 @@ function setUp() { // Add two revisions to an entity. for ($i = 100; $i < 102; $i++) { - $entity = field_test_create_entity(4); $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; - - drupal_write_record('test_entity', $entity, 'ftid'); - drupal_write_record('test_entity_revision', $entity); - - db_update('test_entity') - ->fields(array('ftvid' => $entity->ftvid)) - ->condition('ftid', $entity->ftid) - ->execute(); - - field_attach_update('test_entity', $entity); + $entity->setNewRevision(); + $entity->save(); } }