Issue #2891215 by plach, timmillwood, hchonov, amateescu, catch, gabesullice: Add a way to track whether a revision was default when originally created

merge-requests/1654/head
effulgentsia 2018-01-12 15:29:04 -08:00
parent 7e3532dbbf
commit 49c6761b56
16 changed files with 305 additions and 7 deletions

View File

@ -331,6 +331,21 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
return $this->isNew() || $return;
}
/**
* {@inheritdoc}
*/
public function wasDefaultRevision() {
/** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
$entity_type = $this->getEntityType();
if (!$entity_type->isRevisionable()) {
return TRUE;
}
$revision_default_key = $entity_type->getRevisionMetadataKey('revision_default');
$value = $this->isNew() || $this->get($revision_default_key)->value;
return $value;
}
/**
* {@inheritdoc}
*/

View File

@ -506,6 +506,15 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Con
}
$this->populateAffectedRevisionTranslations($entity);
// Populate the "revision_default" flag. We skip this when we are resaving
// the revision because this is only allowed for default revisions, and
// these cannot be made non-default.
if ($this->entityType->isRevisionable() && $entity->isNewRevision()) {
$revision_default_key = $this->entityType->getRevisionMetadataKey('revision_default');
$entity->set($revision_default_key, $entity->isDefaultRevision());
}
$this->doSaveFieldItems($entity);
return $return;

View File

@ -19,10 +19,15 @@ class ContentEntityType extends EntityType implements ContentEntityTypeInterface
*/
public function __construct($definition) {
parent::__construct($definition);
$this->handlers += [
'storage' => 'Drupal\Core\Entity\Sql\SqlContentEntityStorage',
'view_builder' => 'Drupal\Core\Entity\EntityViewBuilder',
];
$this->revision_metadata_keys += [
'revision_default' => 'revision_default',
];
}
/**

View File

@ -190,8 +190,10 @@ class EntityFieldManager implements EntityFieldManagerInterface {
* flagged as translatable.
*/
protected function buildBaseFieldDefinitions($entity_type_id) {
/** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
$entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
$class = $entity_type->getClass();
/** @var string[] $keys */
$keys = array_filter($entity_type->getKeys());
// Fail with an exception for non-fieldable entity types.
@ -221,6 +223,18 @@ class EntityFieldManager implements EntityFieldManagerInterface {
}
// Make sure that revisionable entity types are correctly defined.
if ($entity_type->isRevisionable()) {
$field_name = $entity_type->getRevisionMetadataKey('revision_default');
$base_field_definitions[$field_name] = BaseFieldDefinition::create('boolean')
->setLabel($this->t('Default revision'))
->setDescription($this->t('A flag indicating whether this was a default revision when it was saved.'))
->setStorageRequired(TRUE)
->setTranslatable(FALSE)
->setRevisionable(TRUE);
}
// Make sure that revisionable and translatable entity types are correctly
// defined.
if ($entity_type->isRevisionable() && $entity_type->isTranslatable()) {
// The 'revision_translation_affected' field should always be defined.
// This field has been added unconditionally in Drupal 8.4.0 and it is

View File

@ -51,6 +51,14 @@ interface RevisionableInterface {
*/
public function isDefaultRevision($new_value = NULL);
/**
* Checks whether the entity object was a default revision when it was saved.
*
* @return bool
* TRUE if the entity object was a revision, FALSE otherwise.
*/
public function wasDefaultRevision();
/**
* Checks if this entity is the latest revision.
*

View File

@ -245,6 +245,7 @@ class SqlContentEntityStorageSchemaConverter {
$original_base_table = $original_entity_type->getBaseTable();
$revision_id_key = $temporary_entity_type->getKey('revision');
$revision_default_key = $temporary_entity_type->getRevisionMetadataKey('revision_default');
$revision_translation_affected_key = $temporary_entity_type->getKey('revision_translation_affected');
// If 'progress' is not set, then this will be the first run of the batch.
@ -288,6 +289,10 @@ class SqlContentEntityStorageSchemaConverter {
// Set the revision ID to be same as the entity ID.
$entity->set($revision_id_key, $entity_id);
// We had no revisions so far, so the existing data belongs to the
// default revision now.
$entity->set($revision_default_key, TRUE);
// Set the 'revision_translation_affected' flag to TRUE to match the
// previous API return value: if the field was not defined the value
// returned was always TRUE.
@ -380,6 +385,23 @@ class SqlContentEntityStorageSchemaConverter {
}
$updated_storage_definitions[$entity_type->getKey('revision')] = $revision_field;
// Add the default revision flag field.
$field_name = $entity_type->getRevisionMetadataKey('revision_default');
$storage_definition = BaseFieldDefinition::create('boolean')
->setName($field_name)
->setTargetEntityTypeId($entity_type->id())
->setTargetBundle(NULL)
->setLabel(t('Default revision'))
->setDescription(t('A flag indicating whether this was a default revision when it was saved.'))
->setStorageRequired(TRUE)
->setTranslatable(FALSE)
->setRevisionable(TRUE);
if ($update_cached_definitions) {
$this->entityDefinitionUpdateManager->installFieldStorageDefinition($field_name, $entity_type->id(), $entity_type->getProvider(), $storage_definition);
}
$updated_storage_definitions[$field_name] = $storage_definition;
// Add the 'revision_translation_affected' field if needed.
if ($entity_type->isTranslatable()) {
$revision_translation_affected_field = BaseFieldDefinition::create('boolean')

View File

@ -122,6 +122,11 @@ abstract class BlockContentResourceTestBase extends EntityResourceTestBase {
'value' => TRUE,
],
],
'revision_default' => [
[
'value' => TRUE,
],
],
'default_langcode' => [
[
'value' => TRUE,

View File

@ -212,6 +212,11 @@ abstract class MediaResourceTestBase extends EntityResourceTestBase {
'value' => TRUE,
],
],
'revision_default' => [
[
'value' => TRUE,
],
],
];
}

View File

@ -152,6 +152,11 @@ abstract class NodeResourceTestBase extends EntityResourceTestBase {
'value' => TRUE,
],
],
'revision_default' => [
[
'value' => TRUE,
],
],
'default_langcode' => [
[
'value' => TRUE,

View File

@ -150,6 +150,9 @@ class EntitySerializationTest extends NormalizerTestBase {
'default_langcode' => [
['value' => TRUE],
],
'revision_default' => [
['value' => TRUE],
],
'revision_translation_affected' => [
['value' => TRUE],
],
@ -226,6 +229,7 @@ class EntitySerializationTest extends NormalizerTestBase {
'user_id' => '<user_id><target_id>' . $this->user->id() . '</target_id><target_type>' . $this->user->getEntityTypeId() . '</target_type><target_uuid>' . $this->user->uuid() . '</target_uuid><url>' . $this->user->url() . '</url></user_id>',
'revision_id' => '<revision_id><value>' . $this->entity->getRevisionId() . '</value></revision_id>',
'default_langcode' => '<default_langcode><value>1</value></default_langcode>',
'revision_default' => '<revision_default><value>1</value></revision_default>',
'revision_translation_affected' => '<revision_translation_affected><value>1</value></revision_translation_affected>',
'non_mul_field' => '<non_mul_field/>',
'non_rev_field' => '<non_rev_field/>',

View File

@ -2056,3 +2056,52 @@ function system_update_8403() {
}
}
}
/**
* Add the 'revision_default' field to all relevant entity types.
*/
function system_update_8501() {
$definition_update_manager = \Drupal::entityDefinitionUpdateManager();
// Clear the cached entity type definitions so we get the new
// 'revision_default' revision metadata key.
\Drupal::entityTypeManager()->clearCachedDefinitions();
// Get a list of revisionable entity types.
/** @var \Drupal\Core\Entity\ContentEntityTypeInterface[] $definitions */
$definitions = array_filter(\Drupal::entityTypeManager()->getDefinitions(), function (EntityTypeInterface $entity_type) use ($definition_update_manager) {
if ($entity_type = $definition_update_manager->getEntityType($entity_type->id())) {
return $entity_type->isRevisionable();
}
return FALSE;
});
// Install the 'revision_default' field.
foreach ($definitions as $entity_type_id => $entity_type) {
$field_name = $entity_type->getRevisionMetadataKey('revision_default');
// Install the 'revision_default' field if needed.
if (!$definition_update_manager->getFieldStorageDefinition($field_name, $entity_type_id)) {
// Make sure the new "revision_default" revision metadata key is available
// also to code using the latest installed definition.
$installed_entity_type = $definition_update_manager->getEntityType($entity_type_id);
$revision_metadata_keys = $installed_entity_type->get('revision_metadata_keys');
$revision_metadata_keys['revision_default'] = $field_name;
$installed_entity_type->set('revision_metadata_keys', $revision_metadata_keys);
$definition_update_manager->updateEntityType($installed_entity_type);
$storage_definition = BaseFieldDefinition::create('boolean')
->setLabel(t('Default revision'))
->setDescription(t('A flag indicating whether this was a default revision when it was saved.'))
->setStorageRequired(TRUE)
->setTranslatable(FALSE)
->setRevisionable(TRUE)
// We cannot tell whether existing revisions were default or not when
// they were created, but since we did not support creating non-default
// revisions in any core stable UI so far, we default to TRUE.
->setInitialValue(TRUE);
$definition_update_manager
->installFieldStorageDefinition($field_name, $entity_type_id, $entity_type_id, $storage_definition);
}
}
}

View File

@ -38,7 +38,8 @@ function entity_test_update_update_8400() {
$revision_metadata_keys = [
'revision_user' => 'revision_user',
'revision_created' => 'revision_created',
'revision_log_message' => 'revision_log_message'
'revision_log_message' => 'revision_log_message',
'revision_default' => 'revision_default',
];
$entity_type->set('revision_metadata_keys', $revision_metadata_keys);

View File

@ -0,0 +1,90 @@
<?php
namespace Drupal\Tests\system\Functional\Update;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
use Drupal\system\Tests\Entity\EntityDefinitionTestTrait;
/**
* Tests the upgrade path for adding the 'revision_default' field.
*
* @see https://www.drupal.org/project/drupal/issues/2891215
*
* @group Update
*/
class EntityUpdateAddRevisionDefaultTest extends UpdatePathTestBase {
use EntityDefinitionTestTrait;
use DbUpdatesTrait;
/**
* The entity manager service.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The last installed schema repository service.
*
* @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface
*/
protected $lastInstalledSchemaRepository;
/**
* The state service.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->entityManager = \Drupal::entityManager();
$this->lastInstalledSchemaRepository = \Drupal::service('entity.last_installed_schema.repository');
$this->state = \Drupal::state();
}
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../fixtures/update/drupal-8.0.0-rc1-filled.standard.entity_test_update_mul_rev.php.gz',
];
}
/**
* Tests the addition of the 'revision_default' base field.
*
* @see system_update_8501()
*/
public function testAddingTheRevisionDefaultField() {
// Make the entity type revisionable and translatable prior to running the
// updates.
$this->updateEntityTypeToRevisionableAndTranslatable();
// Check that the test entity type does not have the 'revision_default'
// field before running the updates.
$field_storage_definitions = $this->lastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions('entity_test_update');
$this->assertFalse(isset($field_storage_definitions['revision_default']));
$this->runUpdates();
// Check that the 'revision_default' field has been added by
// system_update_8501().
$field_storage_definitions = $this->lastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions('entity_test_update');
$this->assertTrue(isset($field_storage_definitions['revision_default']));
// Check that the correct initial value was set when the field was
// installed.
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $this->entityManager->getStorage('entity_test_update')->load(1);
$this->assertTrue($entity->wasDefaultRevision());
}
}

View File

@ -161,7 +161,8 @@ class EntityUpdateToRevisionableAndPublishableTest extends UpdatePathTestBase {
$revision_metadata_keys = [
'revision_user' => 'revision_user',
'revision_created' => 'revision_created',
'revision_log_message' => 'revision_log_message'
'revision_log_message' => 'revision_log_message',
'revision_default' => 'revision_default',
];
$entity_type->set('revision_metadata_keys', $revision_metadata_keys);

View File

@ -113,6 +113,7 @@ class EntityDefinitionUpdateTest extends EntityKernelTestBase {
// The revision key is now defined, so the revision field needs to be
// created.
t('The %field_name field needs to be installed.', ['%field_name' => 'Revision ID']),
t('The %field_name field needs to be installed.', ['%field_name' => 'Default revision']),
],
];
$this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');

View File

@ -5,7 +5,6 @@ namespace Drupal\KernelTests\Core\Entity;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\entity_test_revlog\Entity\EntityTestMulWithRevisionLog;
use Drupal\KernelTests\KernelTestBase;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
@ -13,7 +12,7 @@ use Drupal\user\UserInterface;
* @coversDefaultClass \Drupal\Core\Entity\RevisionableContentEntityBase
* @group Entity
*/
class RevisionableContentEntityBaseTest extends KernelTestBase {
class RevisionableContentEntityBaseTest extends EntityKernelTestBase {
/**
* {@inheritdoc}
@ -25,10 +24,7 @@ class RevisionableContentEntityBaseTest extends KernelTestBase {
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('entity_test_mul_revlog');
$this->installEntitySchema('user');
$this->installSchema('system', 'sequences');
}
/**
@ -89,6 +85,74 @@ class RevisionableContentEntityBaseTest extends KernelTestBase {
$this->assertItemsTableCount(3, $definition);
}
/**
* Tests the behavior of the "revision_default" flag.
*
* @covers \Drupal\Core\Entity\ContentEntityBase::wasDefaultRevision
*/
public function testWasDefaultRevision() {
$entity_type_id = 'entity_test_mul_revlog';
$entity = EntityTestMulWithRevisionLog::create([
'type' => $entity_type_id,
]);
// Checks that in a new entity ::wasDefaultRevision() always matches
// ::isDefaultRevision().
$this->assertEquals($entity->isDefaultRevision(), $entity->wasDefaultRevision());
$entity->isDefaultRevision(FALSE);
$this->assertEquals($entity->isDefaultRevision(), $entity->wasDefaultRevision());
// Check that a new entity is always flagged as a default revision on save,
// regardless of its default revision status.
$entity->save();
$this->assertTrue($entity->wasDefaultRevision());
// Check that a pending revision is not flagged as default.
$entity->setNewRevision();
$entity->isDefaultRevision(FALSE);
$entity->save();
$this->assertFalse($entity->wasDefaultRevision());
// Check that a default revision is flagged as such.
$entity->setNewRevision();
$entity->isDefaultRevision(TRUE);
$entity->save();
$this->assertTrue($entity->wasDefaultRevision());
// Check that a manually set value for the "revision_default" flag is
// ignored on save.
$entity->setNewRevision();
$entity->isDefaultRevision(FALSE);
$entity->set('revision_default', TRUE);
$this->assertTrue($entity->wasDefaultRevision());
$entity->save();
$this->assertFalse($entity->wasDefaultRevision());
// Check that the default revision status was stored correctly.
$storage = $this->entityManager->getStorage($entity_type_id);
foreach ([TRUE, FALSE, TRUE, FALSE] as $index => $expected) {
/** @var \Drupal\entity_test_revlog\Entity\EntityTestMulWithRevisionLog $revision */
$revision = $storage->loadRevision($index + 1);
$this->assertEquals($expected, $revision->wasDefaultRevision());
}
// Check that the default revision is flagged correctly.
/** @var \Drupal\entity_test_revlog\Entity\EntityTestMulWithRevisionLog $entity */
$entity = $storage->loadUnchanged($entity->id());
$this->assertTrue($entity->wasDefaultRevision());
// Check that the "revision_default" flag cannot be changed once set.
/** @var \Drupal\entity_test_revlog\Entity\EntityTestMulWithRevisionLog $entity2 */
$entity2 = EntityTestMulWithRevisionLog::create([
'type' => $entity_type_id,
]);
$entity2->save();
$this->assertTrue($entity2->wasDefaultRevision());
$entity2->isDefaultRevision(FALSE);
$entity2->save();
$this->assertTrue($entity2->wasDefaultRevision());
}
/**
* Asserts the ammount of items on entity related tables.
*