Skip to content

Commit

Permalink
Issue #2248983 by hchonov, amateescu, paranojik, timmillwood, tstoeck…
Browse files Browse the repository at this point in the history
…ler, catch, Berdir, alexpott: Define the revision metadata base fields in the entity annotation in order for the storage to create them only in the revision table
  • Loading branch information
Nathaniel Catchpole committed Feb 23, 2017
1 parent 801f6ca commit 4b325ae
Show file tree
Hide file tree
Showing 23 changed files with 1,650 additions and 95 deletions.
47 changes: 47 additions & 0 deletions core/lib/Drupal/Core/Entity/ContentEntityType.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
*/
class ContentEntityType extends EntityType implements ContentEntityTypeInterface {

/**
* An array of entity revision metadata keys.
*
* @var array
*/
protected $revision_metadata_keys = [];

/**
* {@inheritdoc}
*/
Expand Down Expand Up @@ -41,4 +48,44 @@ protected function checkStorageClass($class) {
}
}

/**
* {@inheritdoc}
*/
public function getRevisionMetadataKeys($include_backwards_compatibility_field_names = TRUE) {
// Provide backwards compatibility in case the revision metadata keys are
// not defined in the entity annotation.
if (!$this->revision_metadata_keys && $include_backwards_compatibility_field_names) {
$base_fields = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions($this->id());
if ((isset($base_fields['revision_uid']) && $revision_user = 'revision_uid') || (isset($base_fields['revision_user']) && $revision_user = 'revision_user')) {
@trigger_error('The revision_user revision metadata key is not set.', E_USER_DEPRECATED);
$this->revision_metadata_keys['revision_user'] = $revision_user;
}
if ((isset($base_fields['revision_timestamp']) && $revision_timestamp = 'revision_timestamp') || (isset($base_fields['revision_created'])) && $revision_timestamp = 'revision_created') {
@trigger_error('The revision_created revision metadata key is not set.', E_USER_DEPRECATED);
$this->revision_metadata_keys['revision_created'] = $revision_timestamp;
}
if ((isset($base_fields['revision_log']) && $revision_log = 'revision_log') || (isset($base_fields['revision_log_message']) && $revision_log = 'revision_log_message')) {
@trigger_error('The revision_log_message revision metadata key is not set.', E_USER_DEPRECATED);
$this->revision_metadata_keys['revision_log_message'] = $revision_log;
}
}
return $this->revision_metadata_keys;
}

/**
* {@inheritdoc}
*/
public function getRevisionMetadataKey($key) {
$keys = $this->getRevisionMetadataKeys();
return isset($keys[$key]) ? $keys[$key] : FALSE;
}

/**
* {@inheritdoc}
*/
public function hasRevisionMetadataKey($key) {
$keys = $this->getRevisionMetadataKeys();
return isset($keys[$key]);
}

}
46 changes: 46 additions & 0 deletions core/lib/Drupal/Core/Entity/ContentEntityTypeInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,50 @@
* Provides an interface for a content entity type and its metadata.
*/
interface ContentEntityTypeInterface extends EntityTypeInterface {

/**
* Gets an array of entity revision metadata keys.
*
* @param bool $include_backwards_compatibility_field_names
* (optional and deprecated) Whether to provide the revision keys on a
* best-effort basis by looking at the base fields defined by the entity
* type. Note that this parameter will be removed in Drupal 9.0.0. Defaults
* to TRUE.
*
* @return array
* An array describing how the Field API can extract revision metadata
* information of this entity type:
* - revision_log_message: The name of the property that contains description
* of the changes that were made in the current revision.
* - revision_user: The name of the property that contains the user ID of
* the author of the current revision.
* - revision_created: The name of the property that contains the timestamp
* of the current revision.
*/
public function getRevisionMetadataKeys($include_backwards_compatibility_field_names = TRUE);

/**
* Gets a specific entity revision metadata key.
*
* @param string $key
* The name of the entity revision metadata key to return.
*
* @return string|bool
* The entity revision metadata key, or FALSE if it does not exist.
*
* @see self::getRevisionMetadataKeys()
*/
public function getRevisionMetadataKey($key);

/**
* Indicates if a given entity revision metadata key exists.
*
* @param string $key
* The name of the entity revision metadata key to check.
*
* @return bool
* TRUE if a given entity revision metadata key exists, FALSE otherwise.
*/
public function hasRevisionMetadataKey($key);

}
48 changes: 37 additions & 11 deletions core/lib/Drupal/Core/Entity/RevisionLogEntityTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@ trait RevisionLogEntityTrait {
* @see \Drupal\Core\Entity\FieldableEntityInterface::baseFieldDefinitions()
*/
public static function revisionLogBaseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['revision_created'] = BaseFieldDefinition::create('created')
$fields[static::getRevisionMetadataKey($entity_type, 'revision_created')] = BaseFieldDefinition::create('created')
->setLabel(t('Revision create time'))
->setDescription(t('The time that the current revision was created.'))
->setRevisionable(TRUE);

$fields['revision_user'] = BaseFieldDefinition::create('entity_reference')
$fields[static::getRevisionMetadataKey($entity_type, 'revision_user')] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Revision user'))
->setDescription(t('The user ID of the author of the current revision.'))
->setSetting('target_type', 'user')
->setRevisionable(TRUE);

$fields['revision_log_message'] = BaseFieldDefinition::create('string_long')
$fields[static::getRevisionMetadataKey($entity_type, 'revision_log_message')] = BaseFieldDefinition::create('string_long')
->setLabel(t('Revision log message'))
->setDescription(t('Briefly describe the changes you have made.'))
->setRevisionable(TRUE)
Expand All @@ -56,60 +56,86 @@ public static function revisionLogBaseFieldDefinitions(EntityTypeInterface $enti
* Implements \Drupal\Core\Entity\RevisionLogInterface::getRevisionCreationTime().
*/
public function getRevisionCreationTime() {
return $this->revision_created->value;
return $this->{static::getRevisionMetadataKey($this->getEntityType(), 'revision_created')}->value;
}

/**
* Implements \Drupal\Core\Entity\RevisionLogInterface::setRevisionCreationTime().
*/
public function setRevisionCreationTime($timestamp) {
$this->revision_created->value = $timestamp;
$this->{static::getRevisionMetadataKey($this->getEntityType(), 'revision_created')}->value = $timestamp;
return $this;
}

/**
* Implements \Drupal\Core\Entity\RevisionLogInterface::getRevisionUser().
*/
public function getRevisionUser() {
return $this->revision_user->entity;
return $this->{static::getRevisionMetadataKey($this->getEntityType(), 'revision_user')}->entity;
}

/**
* Implements \Drupal\Core\Entity\RevisionLogInterface::setRevisionUser().
*/
public function setRevisionUser(UserInterface $account) {
$this->revision_user->entity = $account;
$this->{static::getRevisionMetadataKey($this->getEntityType(), 'revision_user')}->entity = $account;
return $this;
}

/**
* Implements \Drupal\Core\Entity\RevisionLogInterface::getRevisionUserId().
*/
public function getRevisionUserId() {
return $this->revision_user->target_id;
return $this->{static::getRevisionMetadataKey($this->getEntityType(), 'revision_user')}->target_id;
}

/**
* Implements \Drupal\Core\Entity\RevisionLogInterface::setRevisionUserId().
*/
public function setRevisionUserId($user_id) {
$this->revision_user->target_id = $user_id;
$this->{static::getRevisionMetadataKey($this->getEntityType(), 'revision_user')}->target_id = $user_id;
return $this;
}

/**
* Implements \Drupal\Core\Entity\RevisionLogInterface::getRevisionLogMessage().
*/
public function getRevisionLogMessage() {
return $this->revision_log_message->value;
return $this->{static::getRevisionMetadataKey($this->getEntityType(), 'revision_log_message')}->value;
}

/**
* Implements \Drupal\Core\Entity\RevisionLogInterface::setRevisionLogMessage().
*/
public function setRevisionLogMessage($revision_log_message) {
$this->revision_log_message->value = $revision_log_message;
$this->{static::getRevisionMetadataKey($this->getEntityType(), 'revision_log_message')}->value = $revision_log_message;
return $this;
}

/**
* Gets the name of a revision metadata field.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* A content entity type definition.
* @param string $key
* The revision metadata key to get, must be one of 'revision_created',
* 'revision_user' or 'revision_log_message'.
*
* @return string
* The name of the field for the specified $key.
*/
protected static function getRevisionMetadataKey(EntityTypeInterface $entity_type, $key) {
// We need to prevent ContentEntityType::getRevisionMetadataKey() from
// providing fallback as that requires fetching the entity type's field
// definition leading to an infinite recursion.
/** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
$revision_metadata_keys = $entity_type->getRevisionMetadataKeys(FALSE) + [
'revision_created' => 'revision_created',
'revision_user' => 'revision_user',
'revision_log_message' => 'revision_log_message',
];

return $revision_metadata_keys[$key];
}

}
14 changes: 4 additions & 10 deletions core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -294,17 +294,11 @@ public function getTableMapping(array $storage_definitions = NULL) {
// Make sure the key fields come first in the list of fields.
$all_fields = array_merge($key_fields, array_diff($all_fields, $key_fields));

// Nodes have all three of these fields, while custom blocks only have
// log.
// @todo Provide automatic definitions for revision metadata fields in
// https://www.drupal.org/node/2248983.
$revision_metadata_fields = array_intersect(array(
'revision_timestamp',
'revision_uid',
'revision_log',
), $all_fields);

// If the entity is revisionable, gather the fields that need to be put
// in the revision table.
$revisionable = $this->entityType->isRevisionable();
$revision_metadata_fields = $revisionable ? array_values($this->entityType->getRevisionMetadataKeys()) : [];

$translatable = $this->entityType->isTranslatable();
if (!$revisionable && !$translatable) {
// The base layout stores all the base field values in the base table.
Expand Down
5 changes: 5 additions & 0 deletions core/modules/block_content/src/Entity/BlockContent.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
* "langcode" = "langcode",
* "uuid" = "uuid"
* },
* revision_metadata_keys = {
* "revision_user" = "revision_user",
* "revision_created" = "revision_created",
* "revision_log_message" = "revision_log"
* },
* bundle_entity_type = "block_content_type",
* field_ui_base_route = "entity.block_content_type.edit_form",
* render_cache = FALSE,
Expand Down
5 changes: 5 additions & 0 deletions core/modules/node/src/Entity/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@
* "published" = "status",
* "uid" = "uid",
* },
* revision_metadata_keys = {
* "revision_user" = "revision_uid",
* "revision_created" = "revision_timestamp",
* "revision_log_message" = "revision_log"
* },
* bundle_entity_type = "node_type",
* field_ui_base_route = "entity.node_type.edit_form",
* common_reference_target = TRUE,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php

namespace Drupal\system\Tests\Entity\Update;

use Drupal\system\Tests\Update\UpdatePathTestBase;
use Drupal\views\Entity\View;

/**
* Tests the upgrade path for moving the revision metadata fields.
*
* @group Update
*/
class MoveRevisionMetadataFieldsUpdateTest extends UpdatePathTestBase {

/**
* {@inheritdoc}
*/
public function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../tests/fixtures/update/drupal-8.2.0.bare.standard_with_entity_test_revlog_enabled.php.gz',
__DIR__ . '/../../../../tests/fixtures/update/drupal-8.entity-data-revision-metadata-fields-2248983.php',
__DIR__ . '/../../../../tests/fixtures/update/drupal-8.views-revision-metadata-fields-2248983.php',
];
}

/**
* Tests that the revision metadata fields are moved correctly.
*/
public function testSystemUpdate3000() {
$this->runUpdates();

foreach (['entity_test_revlog', 'entity_test_mul_revlog'] as $entity_type_id) {
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
$storage = \Drupal::entityTypeManager()->getStorage($entity_type_id);
/** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
$entity_type = $storage->getEntityType();
$revision_metadata_field_names = $entity_type->getRevisionMetadataKeys();

$database_schema = \Drupal::database()->schema();

// Test that the revision metadata fields are present only in the
// revision table.
foreach ($revision_metadata_field_names as $revision_metadata_field_name) {
if ($entity_type->isTranslatable()) {
$this->assertFalse($database_schema->fieldExists($entity_type->getDataTable(), $revision_metadata_field_name));
$this->assertFalse($database_schema->fieldExists($entity_type->getRevisionDataTable(), $revision_metadata_field_name));
}
else {
$this->assertFalse($database_schema->fieldExists($entity_type->getBaseTable(), $revision_metadata_field_name));
}
$this->assertTrue($database_schema->fieldExists($entity_type->getRevisionTable(), $revision_metadata_field_name));
}

// Test that the revision metadata values have been transferred correctly
// and that the moved fields are accessible.
/** @var \Drupal\Core\Entity\RevisionLogInterface $entity_rev_first */
$entity_rev_first = $storage->loadRevision(1);
$this->assertEqual($entity_rev_first->getRevisionUserId(), '1');
$this->assertEqual($entity_rev_first->getRevisionLogMessage(), 'first revision');
$this->assertEqual($entity_rev_first->getRevisionCreationTime(), '1476268517');

/** @var \Drupal\Core\Entity\RevisionLogInterface $entity_rev_second */
$entity_rev_second = $storage->loadRevision(2);
$this->assertEqual($entity_rev_second->getRevisionUserId(), '1');
$this->assertEqual($entity_rev_second->getRevisionLogMessage(), 'second revision');
$this->assertEqual($entity_rev_second->getRevisionCreationTime(), '1476268518');


// Test that the views using revision metadata fields are updated
// properly.
$view = View::load($entity_type_id . '_for_2248983');
$displays = $view->get('display');
foreach ($displays as $display => $display_data) {
foreach ($display_data['display_options']['fields'] as $property_data) {
if (in_array($property_data['field'], $revision_metadata_field_names)) {
$this->assertEqual($property_data['table'], $entity_type->getRevisionTable());
}
}
}
}
}

}
Loading

0 comments on commit 4b325ae

Please sign in to comment.