Issue #2891215 by plach, timmillwood, hchonov, amateescu, catch, gabesullice: Add a way to track whether a revision was default when originally created
parent
7e3532dbbf
commit
49c6761b56
|
@ -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}
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -122,6 +122,11 @@ abstract class BlockContentResourceTestBase extends EntityResourceTestBase {
|
|||
'value' => TRUE,
|
||||
],
|
||||
],
|
||||
'revision_default' => [
|
||||
[
|
||||
'value' => TRUE,
|
||||
],
|
||||
],
|
||||
'default_langcode' => [
|
||||
[
|
||||
'value' => TRUE,
|
||||
|
|
|
@ -212,6 +212,11 @@ abstract class MediaResourceTestBase extends EntityResourceTestBase {
|
|||
'value' => TRUE,
|
||||
],
|
||||
],
|
||||
'revision_default' => [
|
||||
[
|
||||
'value' => TRUE,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -152,6 +152,11 @@ abstract class NodeResourceTestBase extends EntityResourceTestBase {
|
|||
'value' => TRUE,
|
||||
],
|
||||
],
|
||||
'revision_default' => [
|
||||
[
|
||||
'value' => TRUE,
|
||||
],
|
||||
],
|
||||
'default_langcode' => [
|
||||
[
|
||||
'value' => TRUE,
|
||||
|
|
|
@ -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/>',
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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.');
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue