Commit 7f1193a7 authored by effulgentsia's avatar effulgentsia
Browse files

Issue #2941736 by plach, Sam152, amateescu, timmillwood: Moderation state...

Issue #2941736 by plach, Sam152, amateescu, timmillwood: Moderation state revisions should have their isDefaultRevision() match the host entity's
parent 734e2e71
<?php
/**
* @file
* Post update functions for the Content Moderation module.
*/
use Drupal\Core\Site\Settings;
use Drupal\workflows\Entity\Workflow;
/**
* Synchronize moderation state default revisions with their host entities.
*/
function content_moderation_post_update_update_cms_default_revisions(&$sandbox) {
// For every moderated entity, identify the default revision ID, track the
// corresponding "content_moderation_state" revision and save it as the new
// default revision, if needed.
// Initialize sandbox info.
$entity_type_id = &$sandbox['entity_type_id'];
if (!isset($entity_type_id)) {
$sandbox['bundles'] = [];
/** @var \Drupal\workflows\WorkflowInterface $workflow */
foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
/** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration $plugin */
$plugin = $workflow->getTypePlugin();
foreach ($plugin->getEntityTypes() as $entity_type_id) {
$sandbox['entity_type_ids'][$entity_type_id] = $entity_type_id;
foreach ($plugin->getBundlesForEntityType($entity_type_id) as $bundle) {
$sandbox['bundles'][$entity_type_id][$bundle] = $bundle;
}
}
}
$sandbox['offset'] = 0;
$sandbox['limit'] = Settings::get('entity_update_batch_size', 50);
$sandbox['total'] = count($sandbox['entity_type_ids']);
$entity_type_id = array_shift($sandbox['entity_type_ids']);
}
// If there are no moderated bundles or we processed all of them, we are done.
$entity_type_manager = \Drupal::entityTypeManager();
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $content_moderation_state_storage */
$content_moderation_state_storage = $entity_type_manager->getStorage('content_moderation_state');
if (!$entity_type_id) {
$content_moderation_state_storage->resetCache();
$sandbox['#finished'] = 1;
return;
}
// Retrieve a batch of moderated entities to be processed.
$storage = $entity_type_manager->getStorage($entity_type_id);
$entity_type = $entity_type_manager->getDefinition($entity_type_id);
$query = $storage->getQuery()
->accessCheck(FALSE)
->sort($entity_type->getKey('id'))
->range($sandbox['offset'], $sandbox['limit']);
$bundle_key = $entity_type->getKey('bundle');
if ($bundle_key && !empty($sandbox['bundles'][$entity_type_id])) {
$bundles = array_keys($sandbox['bundles'][$entity_type_id]);
$query->condition($bundle_key, $bundles, 'IN');
}
$entity_ids = $query->execute();
// Compute progress status and skip to the next entity type, if needed.
$sandbox['#finished'] = ($sandbox['total'] - count($sandbox['entity_type_ids']) - 1) / $sandbox['total'];
if (!$entity_ids) {
$sandbox['offset'] = 0;
$entity_type_id = array_shift($sandbox['entity_type_ids']) ?: FALSE;
return;
}
// Load the "content_moderation_state" revisions corresponding to the
// moderated entity default revisions.
$result = $content_moderation_state_storage->getQuery()
->allRevisions()
->condition('content_entity_type_id', $entity_type_id)
->condition('content_entity_revision_id', array_keys($entity_ids), 'IN')
->execute();
/** @var \Drupal\Core\Entity\ContentEntityInterface[] $revisions */
$revisions = $content_moderation_state_storage->loadMultipleRevisions(array_keys($result));
// Update "content_moderation_state" data.
foreach ($revisions as $revision) {
if (!$revision->isDefaultRevision()) {
$revision->setNewRevision(FALSE);
$revision->isDefaultRevision(TRUE);
$content_moderation_state_storage->save($revision);
}
}
// Clear static cache to avoid memory issues.
$storage->resetCache($entity_ids);
$sandbox['offset'] += $sandbox['limit'];
}
......@@ -147,9 +147,10 @@ protected function updateOrCreateFromEntity(EntityInterface $entity) {
$entity_revision_id = $entity->getRevisionId();
$workflow = $this->moderationInfo->getWorkflowForEntity($entity);
$content_moderation_state = ContentModerationStateEntity::loadFromModeratedEntity($entity);
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
$storage = $this->entityTypeManager->getStorage('content_moderation_state');
if (!($content_moderation_state instanceof ContentModerationStateInterface)) {
$storage = $this->entityTypeManager->getStorage('content_moderation_state');
$content_moderation_state = $storage->create([
'content_entity_type_id' => $entity->getEntityTypeId(),
'content_entity_id' => $entity->id(),
......@@ -159,11 +160,6 @@ protected function updateOrCreateFromEntity(EntityInterface $entity) {
]);
$content_moderation_state->workflow->target_id = $workflow->id();
}
elseif ($content_moderation_state->content_entity_revision_id->value != $entity_revision_id) {
// If a new revision of the content has been created, add a new content
// moderation state revision.
$content_moderation_state->setNewRevision(TRUE);
}
// Sync translations.
if ($entity->getEntityType()->hasKey('langcode')) {
......@@ -176,6 +172,12 @@ protected function updateOrCreateFromEntity(EntityInterface $entity) {
}
}
// If a new revision of the content has been created, add a new content
// moderation state revision.
if (!$content_moderation_state->isNew() && $content_moderation_state->content_entity_revision_id->value != $entity_revision_id) {
$content_moderation_state = $storage->createRevision($content_moderation_state, $entity->isDefaultRevision());
}
// Create the ContentModerationState entity for the inserted entity.
$moderation_state = $entity->moderation_state->value;
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
......
<?php
namespace Drupal\Tests\content_moderation\Functional;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
/**
* Test updating the ContentModerationState entity default revisions.
*
* @group Update
* @see content_moderation_post_update_update_cms_default_revisions
*/
class DefaultContentModerationStateRevisionUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz',
__DIR__ . '/../../fixtures/update/drupal-8.4.0-content_moderation_installed.php',
__DIR__ . '/../../fixtures/update/drupal-8.default-cms-entity-id-2941736.php',
];
}
/**
* Test updating the default revision.
*/
public function testUpdateDefaultRevision() {
$this->runUpdates();
foreach (['node', 'block_content'] as $entity_type_id) {
$draft_pending_revision = $this->getEntityByLabel($entity_type_id, 'draft pending revision');
$this->assertFalse($draft_pending_revision->isLatestRevision());
$this->assertCompositeEntityMatchesDefaultRevisionId($draft_pending_revision);
$published_default_revision = $this->getEntityByLabel($entity_type_id, 'published default revision');
$this->assertTrue($published_default_revision->isLatestRevision());
$this->assertCompositeEntityMatchesDefaultRevisionId($published_default_revision);
$archived_default_revision = $this->getEntityByLabel($entity_type_id, 'archived default revision');
$this->assertTrue($archived_default_revision->isLatestRevision());
$this->assertCompositeEntityMatchesDefaultRevisionId($archived_default_revision);
}
}
/**
* Assert for the given entity, the default revision ID matches.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity to use for the assertion.
*/
protected function assertCompositeEntityMatchesDefaultRevisionId(ContentEntityInterface $entity) {
$entity_type_manager = $this->container->get('entity_type.manager');
$entity_list = $entity_type_manager->getStorage('content_moderation_state')
->loadByProperties([
'content_entity_type_id' => $entity->getEntityTypeId(),
'content_entity_id' => $entity->id(),
]);
$content_moderation_state_entity = array_shift($entity_list);
$this->assertEquals($entity->getLoadedRevisionId(), $content_moderation_state_entity->content_entity_revision_id->value);
// Check that the data table records were updated correctly.
/** @var \Drupal\Core\Database\Connection $database */
$database = $this->container->get('database');
$query = 'SELECT * FROM {content_moderation_state_field_data} WHERE id = :id';
$records = $database->query($query, [':id' => $content_moderation_state_entity->id()])
->fetchAllAssoc('langcode');
foreach ($records as $langcode => $record) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $translation */
$translation = $content_moderation_state_entity->getTranslation($langcode);
foreach ((array) $record as $field_name => $value) {
if ($translation->hasField($field_name)) {
$items = $translation->get($field_name)->getValue();
$this->assertEquals(current($items[0]), $value);
}
}
}
}
/**
* Load an entity by label.
*
* @param string $entity_type_id
* The entity type ID.
* @param string $label
* The label of the entity to load.
*
* @return \Drupal\Core\Entity\ContentEntityInterface
* The loaded entity.
*/
protected function getEntityByLabel($entity_type_id, $label) {
$entity_type_manager = $this->container->get('entity_type.manager');
$label_field = $entity_type_manager->getDefinition($entity_type_id)->getKey('label');
$entity_list = $entity_type_manager->getStorage($entity_type_id)
->loadByProperties([$label_field => $label]);
return array_shift($entity_list);
}
}
......@@ -536,6 +536,48 @@ public function testWorkflowNonConfigBundleDependencies() {
], $workflow->getDependencies());
}
/**
* Test the revision default state of the moderation state entity revisions.
*
* @param string $entity_type_id
* The ID of entity type to be tested.
*
* @dataProvider basicModerationTestCases
*/
public function testRevisionDefaultState($entity_type_id) {
// Check that the revision default state of the moderated entity and the
// content moderation state entity always match.
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
$storage = $this->entityTypeManager->getStorage($entity_type_id);
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $cms_storage */
$cms_storage = $this->entityTypeManager->getStorage('content_moderation_state');
$entity = $this->createEntity($entity_type_id);
$entity->get('moderation_state')->value = 'published';
$storage->save($entity);
/** @var \Drupal\Core\Entity\ContentEntityInterface $cms_entity */
$cms_entity = $cms_storage->loadUnchanged(1);
$this->assertEquals($entity->getLoadedRevisionId(), $cms_entity->get('content_entity_revision_id')->value);
$entity->get('moderation_state')->value = 'published';
$storage->save($entity);
/** @var \Drupal\Core\Entity\ContentEntityInterface $cms_entity */
$cms_entity = $cms_storage->loadUnchanged(1);
$this->assertEquals($entity->getLoadedRevisionId(), $cms_entity->get('content_entity_revision_id')->value);
$entity->get('moderation_state')->value = 'draft';
$storage->save($entity);
/** @var \Drupal\Core\Entity\ContentEntityInterface $cms_entity */
$cms_entity = $cms_storage->loadUnchanged(1);
$this->assertEquals($entity->getLoadedRevisionId() - 1, $cms_entity->get('content_entity_revision_id')->value);
$entity->get('moderation_state')->value = 'published';
$storage->save($entity);
/** @var \Drupal\Core\Entity\ContentEntityInterface $cms_entity */
$cms_entity = $cms_storage->loadUnchanged(1);
$this->assertEquals($entity->getLoadedRevisionId(), $cms_entity->get('content_entity_revision_id')->value);
}
/**
* Creates an entity.
*
......
......@@ -108,14 +108,12 @@ public function testContentModerationStateBaseJoin() {
$expected_result = [
[
'nid' => $node->id(),
// @todo I would have expected that the content_moderation_state default
// revision is the same one as in the node, but it isn't.
// Joins from the base table to the default revision of the
// content_moderation.
'moderation_state' => 'draft',
'moderation_state' => 'published',
// Joins from the revision table to the default revision of the
// content_moderation.
'moderation_state_1' => 'draft',
'moderation_state_1' => 'published',
// Joins from the revision table to the revision of the
// content_moderation.
'moderation_state_2' => 'published',
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment