Steps to reproduce:

  1. Create a moderate entity
  2. Delete it

Expected result: all records in the content_moderation_state_* tables referencing the entity are gone
Actual result: they still are happily sitting there :)

Marking this as a "WI critical" as this can introduce data integrity issues. Feel free to demote if this is not a concern.

CommentFileSizeAuthor
#43 interdiff-39-43.txt1.18 KBevilehk
#43 cm-delete-2893888-43.patch14.79 KBevilehk
#40 cm-delete-2893888-39.patch14.6 KBplach
#40 cm-delete-2893888-39.interdiff.txt804 bytesplach
#33 2893888-33.patch14.53 KBSam152
#33 interdiff.txt1.47 KBSam152
#30 interdiff.txt1.27 KBamateescu
#30 2893888-30.patch14.57 KBamateescu
#24 cm-delete-2893888-23.patch14.6 KBplach
#24 cm-delete-2893888-23.interdiff.txt1.25 KBplach
#22 cm-delete-2893888-18.interdiff.txt792 bytesplach
#21 cm-delete-2893888-21.patch14.61 KBplach
#21 cm-delete-2893888-21.interdiff.txt1.31 KBplach
#21 cm-delete-2893888-18.interdiff.txt938.41 KBplach
#21 cm-delete-2893888-18.test.patch8.87 KBplach
#19 interdiff.txt1.41 KBSam152
#19 cm-delete-2893888-19.patch14.39 KBSam152
#16 cm-delete-2893888-16.patch14.61 KBSam152
#16 interdiff.txt874 bytesSam152
#15 cm-delete-2893888-15.patch14.79 KBSam152
#15 interdiff.txt4.42 KBSam152
#14 cm-delete-2893888-14.patch15.13 KBplach
#14 cm-delete-2893888-14.interdiff.txt4 KBplach
#12 cm-delete-2893888-12.patch14.89 KBplach
#12 cm-delete-2893888-12.interdiff.txt6.14 KBplach
#7 cm-delete-2893888-7.interdiff.txt4.06 KBplach
#7 cm-delete-2893888-7.patch14.79 KBplach
#6 cm-delete-2893888-6.patch14.67 KBplach
#6 cm-delete-2893888-6.interdiff.txt14.57 KBplach
#3 cm-delete-2893888-3.test.patch1.79 KBplach
#2 cm-delete-2893888-2.patch7.32 KBplach
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

plach created an issue. See original summary.

plach’s picture

Assigned: plach » Unassigned
Status: Active » Needs review
FileSize
7.32 KB
plach’s picture

And here's a test-only patch

amateescu’s picture

Status: Needs review » Needs work

Nice find!

+++ b/core/modules/content_moderation/content_moderation.module
@@ -92,6 +92,15 @@ function content_moderation_entity_update(EntityInterface $entity) {
+ * Implements hook_entity_delete().
+ */
+function content_moderation_entity_delete(EntityInterface $entity) {

I'm pretty sure we need to do the same for hook_entity_translation_delete() and hook_entity_revision_delete() :)

Sam152’s picture

This looks great, the refactor into loadContentModerationStateEntity is a nice clean up.

+++ b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php
@@ -176,6 +178,23 @@ public function testBasicModeration($entity_type_id) {
+    // Test content moderation state deletion.
+    $entity2 = $entity_storage->create([
+      'title' => 'Test title',
+      $this->entityTypeManager->getDefinition($entity_type_id)->getKey('bundle') => $bundle_id,
+    ]);
+    $entity2->save();
+    $entity2 = $this->reloadEntity($entity2);
+    $entity2->delete();
+    $result = $this->entityTypeManager
+      ->getStorage('content_moderation_state')
+      ->loadByProperties([
+        'content_entity_type_id' => $entity_type_id,
+        'content_entity_id' => $entity2->id(),
+        'workflow' => $workflow->id(),
+      ]);
+    $this->assertFalse($result);

Do we need this in the middle of this test, any chance we can use the same data provider and spit it out? It's already pretty big.

plach’s picture

Status: Needs work » Needs review
FileSize
14.57 KB
14.67 KB

Sorry for the delay but I got completely derailed by trying to (x)debug PHPUnit tests via PHP Storm, which for some reason was no longer working.

This should address #4 and #5, however it still has a couple of known test failures that I was not sure how to address. I can easily fix them by adding a flag to the new ContentModerationState::loadFromEntity() helper method to retrieve the CMS entity revision matching the current entity revision only when explicitly specified, however that does not feel right to me.

In particular, the fact ViewsDataIntegrationTest::testContentModerationStateBaseJoin() is now failing, but it has a @todo explicitly stating the expected result looks weird, makes me think there may actually something wrong in those tests.

It would be great if you guys could have a closer look to them and figure out whether I'm doing something wrong and the current behavior is the expected one, or whether this unintentionally uncovered an unrelated bug.

plach’s picture

plach’s picture

Title: Content moderation state entity data not deleted when the host entity is » Content moderation state entity field data not removed when the host field data is
amateescu’s picture

This looks like an awesome cleanup in addition to the bug fix. I'll wait for the test results before diving deeper into the problems mentioned in #6, but here's a review in the meantime:

  1. +++ b/core/modules/content_moderation/src/Entity/ContentModerationState.php
    @@ -138,6 +139,53 @@ public static function updateOrCreateFromEntity(ContentModerationState $content_
    +  public static function loadFromEntity(EntityInterface $entity) {
    

    How about loadFromModeratedEntity()?

  2. +++ b/core/modules/content_moderation/src/EntityOperations.php
    @@ -220,6 +212,61 @@ protected function setLatestRevision(EntityInterface $entity) {
    +   * @param \Drupal\Core\Entity\EntityInterface $entity
    +   *   The entity being deleted.
    ...
    +  public function entityDelete(EntityInterface $entity) {
    ...
    +   * @param \Drupal\Core\Entity\EntityInterface $entity
    +   *   The entity revision being deleted.
    ...
    +  public function entityRevisionDelete(EntityInterface $entity) {
    +    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
    ...
    +   * @param \Drupal\Core\Entity\EntityInterface $translation
    +   *   The entity translation being deleted.
    ...
    +  public function entityTranslationDelete(EntityInterface $translation) {
    +    /** @var \Drupal\Core\Entity\ContentEntityInterface $translation */
    

    Since we know that all these hook bridges are only working with content entities, why don't we type hint that directly?

  3. +++ b/core/modules/content_moderation/src/EntityOperations.php
    @@ -220,6 +212,61 @@ protected function setLatestRevision(EntityInterface $entity) {
    +    $content_moderation_state = ContentModerationStateEntity::loadFromEntity($entity);
    +    if ($content_moderation_state) {
    ...
    +      $content_moderation_state = ContentModerationStateEntity::loadFromEntity($entity);
    +      if ($content_moderation_state) {
    

    I would inline the assignments in the condition, but that's just personal preference, feel free to ignore it :)

The last submitted patch, 6: cm-delete-2893888-6.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 7: cm-delete-2893888-7.patch, failed testing. View results

plach’s picture

1: Done
2: Well, it was mainly for consistency: most entity hooks (including hook_entity_delete()) are called also for config entities, so the system would throw a recoverable fatal if we used ContentEntityInterface for those. It would make sense for revisions and translations but it would be inconsistent, aside from the fact that also those hooks are documented as passing a generic EntityInterface, so that could somehow happen in contrib.
3: ignored :)

Leaving at NW due to the test failures.

Sam152’s picture

I've looked into that @todo before (#2859455: Remove superfluous @todos from content moderation). The ContentModerationState entity makes no attempt to match the default-ness of the entity being moderated. The latest ContentModerationState entity will always be the default. I believe this is by design or hasn't been considered before.

Possibly just need to remove the $default_revision check in \Drupal\content_moderation\Entity\ContentModerationState::loadFromModeratedEntity? Something like the following:

diff --git a/core/modules/content_moderation/src/Entity/ContentModerationState.php b/core/modules/content_moderation/src/Entity/ContentModerationState.php
index 563f6c1..9fb43c2 100644
--- a/core/modules/content_moderation/src/Entity/ContentModerationState.php
+++ b/core/modules/content_moderation/src/Entity/ContentModerationState.php
@@ -163,21 +163,19 @@ public static function loadFromModeratedEntity(EntityInterface $entity) {
       $query = $storage->getQuery()
         ->condition('content_entity_type_id', $entity->getEntityTypeId())
         ->condition('content_entity_id', $entity->id())
-        ->condition('workflow', $moderation_info->getWorkflowForEntity($entity)->id());
-
-      if (!$default_revision) {
-        $query
-          ->allRevisions()
-          ->condition('content_entity_revision_id', $entity->getRevisionId());
-      }
+        ->condition('workflow', $moderation_info->getWorkflowForEntity($entity)->id())
+        ->condition('content_entity_revision_id', $entity->getRevisionId())
+        ->allRevisions();
 
       $ids = $query->execute();
       if ($ids) {
         /** @var \Drupal\content_moderation\ContentModerationStateInterface $content_moderation_state */
-        $content_moderation_state = $default_revision ?
-          $storage->load(current($ids)) : $storage->loadRevision(key($ids));
+        $content_moderation_state = $storage->loadRevision(key($ids));
         if ($content_moderation_state instanceof ContentModerationStateInterface) {
           $result = $content_moderation_state;
+          if (!$entity->isDefaultTranslation()) {
+            $result = $content_moderation_state->getTranslation($entity->language()->getId());
+          }
         }
       }
     }

Note loading the matching translation as well.

plach’s picture

Status: Needs work » Needs review
FileSize
4 KB
15.13 KB

@Sam152:

I tried that patch but it's triggering the generation of multiple CMS entities related to the same moderated entity, which is a bit weird at first sight, not sure whether that's expected. As mentioned previously, the attached patch fixes those failures, but I'm not sure it's correct.

Sam152’s picture

Removing the flag and fixing with getLoadedRevisionId(). Seems to do the trick.

Sam152’s picture

plach’s picture

Nice! I'd say we're good here, RTBC on my end.

timmillwood’s picture

Sorry, a bit nitpicky. Otherwise, looks fine.

+++ b/core/modules/content_moderation/src/Entity/ContentModerationState.php
@@ -138,6 +139,46 @@ public static function updateOrCreateFromEntity(ContentModerationState $content_
+          $result = $content_moderation_state;

We could do an early return here. Then we don't need to set $result to NULL, and don't need to return $result. Also, $result isn't a very clear variable name. Without reading the docblock I'd expect a DB query result.

Sam152’s picture

How about the following tidy-up? loadRevision already returns NULL, so no need to check that exists.

timmillwood’s picture

Status: Needs review » Reviewed & tested by the community

That looks like a great tidy up! 8 lines removed!

plach’s picture

The check on $ids was needed, moreover there was this:

+++ b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php
@@ -212,6 +229,69 @@ public function basicModerationTestCases() {
+   * Tests basic monolingual content moderation through the API.

Wrong PHP doc, copy/paste--

Here's an updated version. I'd prefer to avoid early returns, they make code harder to follow. Uploading also a "test-only" patch.

plach’s picture

And the correct missing interdiff for the PHP doc, disregard the previous one.

timmillwood’s picture

Status: Needs review » Reviewed & tested by the community

I'm not going to be too precious either way, and good catch on the PHP doc.

plach’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
1.25 KB
14.6 KB

Aaand simpler :)

plach’s picture

Status: Needs review » Reviewed & tested by the community

Oops, sorry, crosspost. Back to RTBC

Sam152’s picture

+1 LGTM

timmillwood’s picture

Perfect!

The last submitted patch, 21: cm-delete-2893888-18.test.patch, failed testing. View results

The last submitted patch, 12: cm-delete-2893888-12.patch, failed testing. View results

amateescu’s picture

I looked over this patch as well and I could only find two small cosmetic issues. Fixed them quickly with the attached interdiff :)

catch’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/modules/content_moderation/src/Entity/ContentModerationState.php
    @@ -138,6 +139,44 @@ public static function updateOrCreateFromEntity(ContentModerationState $content_
    +
    +      $ids = $storage->getQuery()
    +        ->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())
    +        ->allRevisions()
    +        ->execute();
    +
    

    This should have an accessCheck(FALSE). In practice I can't ever see it being necessary, but bad if it does and it's a common mistake with nodes that are affected by node access modules.

  2. +++ b/core/modules/content_moderation/src/EntityOperations.php
    @@ -198,6 +183,12 @@ protected function updateOrCreateFromEntity(EntityInterface $entity) {
    +    // @todo what if $entity->moderation_state is null at this point?
    

    Is this still a @todo and if so does it need an issue?

Sam152’s picture

That @todo is probably not relevant anymore now that #2844594: Default workflow states and transitions is in.

Sam152’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
1.47 KB
14.53 KB

Fixing feedback from #31, I think these changes are small enough to go back to RTBC.

timmillwood’s picture

LGTM!

plach’s picture

RTBC +1

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

catch’s picture

+++ b/core/modules/content_moderation/src/EntityOperations.php
@@ -222,6 +212,61 @@ protected function setLatestRevision(EntityInterface $entity) {
+      $content_moderation_state = ContentModerationStateEntity::loadFromModeratedEntity($entity);
+      if ($content_moderation_state) {
+        $this->entityTypeManager
+          ->getStorage('content_moderation_state')
+          ->deleteRevision($content_moderation_state->getRevisionId());
+      }
+    }
+  }

Sorry another one I didn't notice first look through and these are all nits... isn't this identical to $content_moderation_state->deleteRevision($content_moderation_state->getRevisionId());?

plach’s picture

There is no ::deleteRevision() method on the entity itself AFAICT

timmillwood’s picture

@plach it looks like you're correct.

plach’s picture

+++ b/core/modules/content_moderation/src/Entity/ContentModerationState.php
@@ -143,6 +144,45 @@ public static function updateOrCreateFromEntity(ContentModerationState $content_
+   * @return \Drupal\content_moderation\ContentModerationStateInterface|null
+   *   The related content moderation state or NULL if non could be found.

fixed typo

timmillwood’s picture

Version: 8.5.x-dev » 8.4.x-dev

As this is already RTBC it should be fine for 8.4.x

catch’s picture

Yes you're right, not sure where I came up with that non-existent method from, but looks good from here then.

evilehk’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
14.79 KB
1.18 KB

Rebase of patch from #40 with latest 8.4.x branch.

timmillwood’s picture

Status: Needs review » Reviewed & tested by the community
catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.5.x and cherry-picked to 8.4.x. Thanks!

  • catch committed e6f7799 on 8.5.x
    Issue #2893888 by plach, Sam152, amateescu, evilehk: Content moderation...

  • catch committed 7759ca1 on 8.4.x
    Issue #2893888 by plach, Sam152, amateescu, evilehk: Content moderation...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.