Issue #3181439 by tstoeckler, Gauravvv, Tomefa, Sam152, smustgrave, larowlan: Content Moderation fatals when a moderated entity is re-saved on hook_insert()

merge-requests/3460/head
catch 2023-02-13 10:01:47 +00:00
parent 88be8b540b
commit 775b3cffe5
5 changed files with 165 additions and 1 deletions

View File

@ -134,12 +134,17 @@ class ContentModerationState extends ContentEntityBase implements ContentModerat
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$storage = \Drupal::entityTypeManager()->getStorage('content_moderation_state');
// New entities may not have a loaded revision ID at this point, but the
// creation of a content moderation state entity may have already been
// triggered elsewhere. In this case we have to match on the revision ID
// (instead of the loaded revision ID).
$revision_id = $entity->getLoadedRevisionId() ?: $entity->getRevisionId();
$ids = $storage->getQuery()
->accessCheck(FALSE)
->condition('content_entity_type_id', $entity->getEntityTypeId())
->condition('content_entity_id', $entity->id())
->condition('workflow', $moderation_info->getWorkflowForEntity($entity)->id())
->condition('content_entity_revision_id', $entity->getLoadedRevisionId())
->condition('content_entity_revision_id', $revision_id)
->allRevisions()
->execute();

View File

@ -0,0 +1,7 @@
name: 'Content moderation test re-save'
type: module
description: 'Re-saves moderated entities for testing purposes.'
package: Testing
version: VERSION
dependencies:
- drupal:content_moderation

View File

@ -0,0 +1,15 @@
<?php
/**
* @file
* Contains install functions for the Content moderation test re-save module.
*/
/**
* Implements hook_install().
*/
function content_moderation_test_resave_install() {
// Make sure that this module's hooks are run before Content Moderation's
// hooks.
module_set_weight('content_moderation_test_resave', -10);
}

View File

@ -0,0 +1,30 @@
<?php
/**
* @file
* Contains hook implementations for the Content moderation test re-save module.
*/
use Drupal\Core\Entity\EntityInterface;
/**
* Implements hook_entity_insert().
*/
function content_moderation_test_resave_entity_insert(EntityInterface $entity) {
/** @var \Drupal\content_moderation\ModerationInformationInterface $content_moderation */
$content_moderation = \Drupal::service('content_moderation.moderation_information');
if ($content_moderation->isModeratedEntity($entity)) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
// Saving the passed entity object would populate its loaded revision ID,
// which we want to avoid. Thus, save a clone of the original object.
$cloned_entity = clone $entity;
// Set the entity's syncing status, as we do not want Content Moderation to
// create new revisions for the re-saving. Without this call Content
// Moderation would end up creating two separate content moderation state
// entities: one for the re-save revision and one for the initial revision.
$cloned_entity->setSyncing(TRUE)->save();
// Record the fact that a re-save happened.
\Drupal::state()->set('content_moderation_test_resave', TRUE);
}
}

View File

@ -0,0 +1,107 @@
<?php
namespace Drupal\Tests\content_moderation\Kernel;
use Drupal\content_moderation\Entity\ContentModerationState;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\content_moderation\Traits\ContentModerationTestTrait;
/**
* Tests Content Moderation with entities that get re-saved automatically.
*
* @group content_moderation
*/
class ContentModerationResaveTest extends KernelTestBase {
use ContentModerationTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
// Make sure the test module is listed first as module weights do not apply
// for kernel tests.
/* @see \content_moderation_test_resave_install() */
'content_moderation_test_resave',
'content_moderation',
'entity_test',
'user',
'workflows',
];
/**
* The content moderation state entity storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $contentModerationStateStorage;
/**
* The entity storage for the entity type used in the test.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $entityStorage;
/**
* The state service.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$entity_type_id = 'entity_test_rev';
$this->installEntitySchema('content_moderation_state');
$this->installEntitySchema($entity_type_id);
$workflow = $this->createEditorialWorkflow();
$this->addEntityTypeAndBundleToWorkflow($workflow, $entity_type_id, $entity_type_id);
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
$entity_type_manager = $this->container->get('entity_type.manager');
$this->contentModerationStateStorage = $entity_type_manager->getStorage('content_moderation_state');
$this->entityStorage = $entity_type_manager->getStorage($entity_type_id);
$this->state = $this->container->get('state');
}
/**
* Tests that Content Moderation works with entities being resaved.
*/
public function testContentModerationResave() {
$entity = $this->entityStorage->create();
$this->assertSame('draft', $entity->get('moderation_state')->value);
$this->assertNull(\Drupal::state()->get('content_moderation_test_resave'));
$this->assertNull(ContentModerationState::loadFromModeratedEntity($entity));
$content_moderation_state_query = $this->contentModerationStateStorage
->getQuery()
->accessCheck(FALSE)
->count();
$this->assertSame(0, (int) $content_moderation_state_query->execute());
$content_moderation_state_revision_query = $this->contentModerationStateStorage
->getQuery()
->accessCheck(FALSE)
->allRevisions()
->count();
$this->assertSame(0, (int) $content_moderation_state_revision_query->execute());
// The test module will re-save the entity in its hook_insert()
// implementation creating the content moderation state entity before
// Content Moderation's hook_insert() has run for the initial save
// operation.
$entity->save();
$this->assertSame('draft', $entity->get('moderation_state')->value);
$this->assertTrue(\Drupal::state()->get('content_moderation_test_resave'));
$content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
$this->assertInstanceOf(ContentModerationState::class, $content_moderation_state);
$this->assertSame(1, (int) $content_moderation_state_query->execute());
$this->assertSame(1, (int) $content_moderation_state_revision_query->execute());
}
}