Issue #218755 by jstoller, Gábor Hojtsy, stevector, mradcliffe, agentrickard, catch, Crell: Added Support revisions in different states.

8.0.x
webchick 2012-09-06 13:32:19 -07:00
parent 5f4440fa7d
commit e203b73b62
11 changed files with 107 additions and 42 deletions

View File

@ -4,6 +4,9 @@ Drupal 8.0, xxxx-xx-xx (development version)
- Improved entity system. - Improved entity system.
* Added support for saving and deleting entities through the controller. * Added support for saving and deleting entities through the controller.
* Entities are now classed objects, implementing EntityInterface. * Entities are now classed objects, implementing EntityInterface.
* 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.
- Replaced the core routing system with one built on the Symfony2 framework. - Replaced the core routing system with one built on the Symfony2 framework.
- Configuration: - Configuration:
* Added a centralized file-based configuration system. * Added a centralized file-based configuration system.

View File

@ -318,8 +318,8 @@ class DatabaseStorageController implements EntityStorageControllerInterface {
$query->fields('revision', $entity_revision_fields); $query->fields('revision', $entity_revision_fields);
// Compare revision id of the base and revision table, if equal then this // Compare revision id of the base and revision table, if equal then this
// is the current revision. // is the default revision.
$query->addExpression('base.' . $this->revisionKey . ' = revision.' . $this->revisionKey, 'isCurrentRevision'); $query->addExpression('base.' . $this->revisionKey . ' = revision.' . $this->revisionKey, 'isDefaultRevision');
} }
$query->fields('base', $entity_fields); $query->fields('base', $entity_fields);

View File

@ -42,11 +42,11 @@ class Entity implements EntityInterface {
protected $enforceIsNew; protected $enforceIsNew;
/** /**
* Indicates whether this is the current revision. * Indicates whether this is the default revision.
* *
* @var bool * @var bool
*/ */
protected $isCurrentRevision = TRUE; protected $isDefaultRevision = TRUE;
/** /**
* Constructs a new entity object. * Constructs a new entity object.
@ -277,12 +277,12 @@ class Entity implements EntityInterface {
} }
/** /**
* Implements Drupal\entity\EntityInterface::isCurrentRevision(). * Implements Drupal\entity\EntityInterface::isDefaultRevision().
*/ */
public function isCurrentRevision($new_value = NULL) { public function isDefaultRevision($new_value = NULL) {
$return = $this->isCurrentRevision; $return = $this->isDefaultRevision;
if (isset($new_value)) { if (isset($new_value)) {
$this->isCurrentRevision = (bool) $new_value; $this->isDefaultRevision = (bool) $new_value;
} }
return $return; return $return;
} }

View File

@ -209,14 +209,14 @@ interface EntityInterface {
public function getRevisionId(); public function getRevisionId();
/** /**
* Checks if this entity is the current revision. * Checks if this entity is the default revision.
* *
* @param bool $new_value * @param bool $new_value
* (optional) A Boolean to (re)set the isCurrentRevision flag. * (optional) A Boolean to (re)set the isDefaultRevision flag.
* *
* @return bool * @return bool
* TRUE if the entity is the current revision, FALSE otherwise. If * TRUE if the entity is the default revision, FALSE otherwise. If
* $new_value was passed, the previous value is returned. * $new_value was passed, the previous value is returned.
*/ */
public function isCurrentRevision($new_value = NULL); public function isDefaultRevision($new_value = NULL);
} }

View File

@ -400,11 +400,15 @@ function field_sql_storage_field_storage_write($entity_type, $entity, $op, $fiel
// Delete all language codes if $entity->$field_name is empty. // Delete all language codes if $entity->$field_name is empty.
$langcodes = !empty($entity->$field_name) ? $field_langcodes : $all_langcodes; $langcodes = !empty($entity->$field_name) ? $field_langcodes : $all_langcodes;
if ($langcodes) { if ($langcodes) {
db_delete($table_name) // Only overwrite the field's base table if saving the default revision
->condition('entity_type', $entity_type) // of an entity.
->condition('entity_id', $id) if ($entity->isDefaultRevision()) {
->condition('langcode', $langcodes, 'IN') db_delete($table_name)
->execute(); ->condition('entity_type', $entity_type)
->condition('entity_id', $id)
->condition('langcode', $langcodes, 'IN')
->execute();
}
db_delete($revision_name) db_delete($revision_name)
->condition('entity_type', $entity_type) ->condition('entity_type', $entity_type)
->condition('entity_id', $id) ->condition('entity_id', $id)
@ -453,7 +457,11 @@ function field_sql_storage_field_storage_write($entity_type, $entity, $op, $fiel
// Execute the query if we have values to insert. // Execute the query if we have values to insert.
if ($do_insert) { if ($do_insert) {
$query->execute(); // Only overwrite the field's base table if saving the default revision
// of an entity.
if ($entity->isDefaultRevision()) {
$query->execute();
}
$revision_query->execute(); $revision_query->execute();
} }
} }

View File

@ -29,6 +29,16 @@ class Node extends Entity implements ContentEntityInterface {
*/ */
public $vid; public $vid;
/**
* Indicates whether this is the default node revision.
*
* The default revision of a node is the one loaded when no specific revision
* has been specified. Only default revisions are saved to the node table.
*
* @var boolean
*/
public $isDefaultRevision = TRUE;
/** /**
* The node UUID. * The node UUID.
* *

View File

@ -100,7 +100,16 @@ class NodeStorageController extends DatabaseStorageController {
} }
else { else {
$op = 'update'; $op = 'update';
$return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey); // 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) { if ($this->revisionKey) {
@ -139,19 +148,19 @@ class NodeStorageController extends DatabaseStorageController {
if (empty($entity->{$this->revisionKey}) || !empty($entity->revision)) { if (empty($entity->{$this->revisionKey}) || !empty($entity->revision)) {
drupal_write_record($this->revisionTable, $record); drupal_write_record($this->revisionTable, $record);
db_update($this->entityInfo['base table']) // Only update the base node table if this revision is the default.
->fields(array($this->revisionKey => $record->{$this->revisionKey})) if ($entity->isDefaultRevision()) {
->condition($this->idKey, $entity->{$this->idKey}) db_update($this->entityInfo['base table'])
->execute(); ->fields(array($this->revisionKey => $record->{$this->revisionKey}))
->condition($this->idKey, $entity->{$this->idKey})
->execute();
}
} }
else { else {
drupal_write_record($this->revisionTable, $record, $this->revisionKey); drupal_write_record($this->revisionTable, $record, $this->revisionKey);
} }
// Make sure to update the new revision key for the entity. // Make sure to update the new revision key for the entity.
$entity->{$this->revisionKey} = $record->{$this->revisionKey}; $entity->{$this->revisionKey} = $record->{$this->revisionKey};
// Mark this revision as the current one.
$entity->isCurrentRevision(TRUE);
} }
/** /**
@ -263,9 +272,13 @@ class NodeStorageController extends DatabaseStorageController {
* Overrides Drupal\entity\DatabaseStorageController::postSave(). * Overrides Drupal\entity\DatabaseStorageController::postSave().
*/ */
function postSave(EntityInterface $node, $update) { function postSave(EntityInterface $node, $update) {
node_access_acquire_grants($node, $update); // Update the node access table for this node, but only if it is the
// default revision. There's no need to delete existing records if the node
// is new.
if ($node->isDefaultRevision()) {
node_access_acquire_grants($node, $update);
}
} }
/** /**
* Overrides Drupal\entity\DatabaseStorageController::preDelete(). * Overrides Drupal\entity\DatabaseStorageController::preDelete().
*/ */

View File

@ -24,13 +24,14 @@ class NodeRevisionsTest extends NodeTestBase {
// Create and login user. // Create and login user.
$web_user = $this->drupalCreateUser(array('view revisions', 'revert revisions', 'edit any page content', $web_user = $this->drupalCreateUser(array('view revisions', 'revert revisions', 'edit any page content',
'delete revisions', 'delete any page content')); 'delete revisions', 'delete any page content', 'administer nodes'));
$this->drupalLogin($web_user); $this->drupalLogin($web_user);
// Create initial node. // Create initial node.
$node = $this->drupalCreateNode(); $node = $this->drupalCreateNode();
$settings = get_object_vars($node); $settings = get_object_vars($node);
$settings['revision'] = 1; $settings['revision'] = 1;
$settings['isDefaultRevision'] = TRUE;
$nodes = array(); $nodes = array();
$logs = array(); $logs = array();
@ -47,6 +48,7 @@ class NodeRevisionsTest extends NodeTestBase {
$this->drupalCreateNode($settings); $this->drupalCreateNode($settings);
$node = node_load($node->nid); // Make sure we get revision information. $node = node_load($node->nid); // Make sure we get revision information.
$settings = get_object_vars($node); $settings = get_object_vars($node);
$settings['isDefaultRevision'] = TRUE;
$nodes[] = $node; $nodes[] = $node;
} }
@ -75,8 +77,8 @@ class NodeRevisionsTest extends NodeTestBase {
$this->assertText($log, t('Log message found.')); $this->assertText($log, t('Log message found.'));
} }
// Confirm that this is the current revision. // Confirm that this is the default revision.
$this->assertTrue($node->isCurrentRevision(), 'Third node revision is the current one.'); $this->assertTrue($node->isDefaultRevision(), 'Third node revision is the default one.');
// Confirm that revisions revert properly. // Confirm that revisions revert properly.
$this->drupalPost("node/$node->nid/revisions/{$nodes[1]->vid}/revert", array(), t('Revert')); $this->drupalPost("node/$node->nid/revisions/{$nodes[1]->vid}/revert", array(), t('Revert'));
@ -86,9 +88,9 @@ class NodeRevisionsTest extends NodeTestBase {
$reverted_node = node_load($node->nid); $reverted_node = node_load($node->nid);
$this->assertTrue(($nodes[1]->body[LANGUAGE_NOT_SPECIFIED][0]['value'] == $reverted_node->body[LANGUAGE_NOT_SPECIFIED][0]['value']), t('Node reverted correctly.')); $this->assertTrue(($nodes[1]->body[LANGUAGE_NOT_SPECIFIED][0]['value'] == $reverted_node->body[LANGUAGE_NOT_SPECIFIED][0]['value']), t('Node reverted correctly.'));
// Confirm that this is not the current version. // Confirm that this is not the default version.
$node = node_revision_load($node->vid); $node = node_revision_load($node->vid);
$this->assertFalse($node->isCurrentRevision(), 'Third node revision is not the current one.'); $this->assertFalse($node->isDefaultRevision(), 'Third node revision is not the default one.');
// Confirm revisions delete properly. // Confirm revisions delete properly.
$this->drupalPost("node/$node->nid/revisions/{$nodes[1]->vid}/delete", array(), t('Delete')); $this->drupalPost("node/$node->nid/revisions/{$nodes[1]->vid}/delete", array(), t('Delete'));
@ -112,6 +114,33 @@ class NodeRevisionsTest extends NodeTestBase {
'%title' => $nodes[2]->label(), '%title' => $nodes[2]->label(),
'%revision-date' => format_date($old_revision_date), '%revision-date' => format_date($old_revision_date),
))); )));
// Make a new revision and set it to not be default.
// This will create a new revision that is not "front facing".
$new_node_revision = clone $node;
$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->isDefaultRevision = FALSE;
node_save($new_node_revision);
$this->drupalGet("node/$node->nid");
$this->assertNoText($new_body, t('Revision body text is not present on default version of node.'));
// Verify that the new body text is present on the revision.
$this->drupalGet("node/$node->nid/revisions/" . $new_node_revision->vid . "/view");
$this->assertText($new_body, t('Revision body text is present when loading specific revision.'));
// Verify that the non-default revision vid is greater than the default
// revision vid.
$default_revision = db_select('node', 'n')
->fields('n', array('vid'))
->condition('nid', $node->nid)
->execute()
->fetchCol();
$default_revision_vid = $default_revision[0];
$this->assertTrue($new_node_revision->vid > $default_revision_vid, 'Revision vid is greater than default revision vid.');
} }
/** /**

View File

@ -1129,8 +1129,8 @@ function node_delete_multiple($nids) {
*/ */
function node_revision_delete($revision_id) { function node_revision_delete($revision_id) {
if ($revision = node_revision_load($revision_id)) { if ($revision = node_revision_load($revision_id)) {
// Prevent deleting the current revision. // Prevent deleting the default revision.
if ($revision->isCurrentRevision()) { if ($revision->isDefaultRevision()) {
return FALSE; return FALSE;
} }
@ -1795,20 +1795,20 @@ function _node_revision_access(Node $node, $op = 'view', $account = NULL, $langc
} }
// There should be at least two revisions. If the vid of the given node // There should be at least two revisions. If the vid of the given node
// and the vid of the current revision differ, then we already have two // and the vid of the default revision differ, then we already have two
// different revisions so there is no need for a separate database check. // different revisions so there is no need for a separate database check.
// Also, if you try to revert to or delete the current revision, that's // Also, if you try to revert to or delete the default revision, that's
// not good. // not good.
if ($node->isCurrentRevision() && (db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid', array(':nid' => $node->nid))->fetchField() == 1 || $op == 'update' || $op == 'delete')) { if ($node->isDefaultRevision() && (db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid', array(':nid' => $node->nid))->fetchField() == 1 || $op == 'update' || $op == 'delete')) {
$access[$cid] = FALSE; $access[$cid] = FALSE;
} }
elseif (user_access('administer nodes', $account)) { elseif (user_access('administer nodes', $account)) {
$access[$cid] = TRUE; $access[$cid] = TRUE;
} }
else { else {
// First check the access to the current revision and finally, if the // First check the access to the default revision and finally, if the
// node passed in is not the current revision then access to that, too. // node passed in is not the default revision then access to that, too.
$access[$cid] = node_access($op, node_load($node->nid), $account, $langcode) && ($node->isCurrentRevision() || node_access($op, $node, $account, $langcode)); $access[$cid] = node_access($op, node_load($node->nid), $account, $langcode) && ($node->isDefaultRevision() || node_access($op, $node, $account, $langcode));
} }
} }

View File

@ -291,6 +291,8 @@ function node_revision_revert_confirm($form, $form_state, $node_revision) {
function node_revision_revert_confirm_submit($form, &$form_state) { function node_revision_revert_confirm_submit($form, &$form_state) {
$node_revision = $form['#node_revision']; $node_revision = $form['#node_revision'];
$node_revision->revision = 1; $node_revision->revision = 1;
// Make this the new default revision for the node.
$node_revision->isDefaultRevision = TRUE;
// The revision timestamp will be updated when the revision is saved. Keep the // The revision timestamp will be updated when the revision is saved. Keep the
// original one for the confirmation message. // original one for the confirmation message.

View File

@ -1665,7 +1665,7 @@ function taxonomy_build_node_index($node) {
} }
} }
// We only maintain the taxonomy index for published nodes. // We only maintain the taxonomy index for published nodes.
if ($status) { if ($status && $node->isDefaultRevision()) {
// Collect a unique list of all the term IDs from all node fields. // Collect a unique list of all the term IDs from all node fields.
$tid_all = array(); $tid_all = array();
foreach (field_info_instances('node', $node->type) as $instance) { foreach (field_info_instances('node', $node->type) as $instance) {