Problem/Motivation

When working via the UI, everything behaves as expected. However, when you populate cached_moderation_state via Drush (for example, after enabling the module):

drush cached-moderation-state:update-all --only-uninitialized --batch-size=50

only the default revisions are stored in the database tables. Records for translations and non-default revision are missing.

Steps to reproduce

  1. Create revisionable and translatable content.
  2. Create a few revisions and a few translations.
  3. Check what you have in the database tables:
  4. node__cached_moderation_state and node_revision__cached_moderation_state.
  5. Manually remove the records from these tables.
  6. Run: drush cached-moderation-state:update-all --only-uninitialized --batch-size=50

After the Drush command finishes, you would expect to see the same set of records again (only the
timestamps may differ). Currently, records for non-default revisions and translation-specific
records are not recreated.

Proposed resolution

There are two issues to address:

1. https://www.drupal.org/project/drupal/issues/2859042 has been resolved, and our logic in cached_moderation_state_entity_presave(EntityInterface $entity) no longer works as intended.

This is because, on save, SqlContentEntityStorage::saveToDedicatedTables() now calls:

$original = !empty($entity->original) ? $entity->original : NULL;

// Use the loaded revision instead of the default one to check for data changes.
if ($original && !$entity->isNewRevision() && !$entity->isDefaultRevision()) {
  $original = $this->loadRevision($entity->getLoadedRevisionId());
}

It then compares our changes against $original. For non-default revisions, the whole condition is TRUE and $original is loaded from database. That means our change in cached_moderation_state_entity_presave():

unset($entity->original->cached_moderation_state);

is no longer taken into account for non-default revisions (as $entity->original is reloaded as described above). This is why we do not see non-default revisions after running:

drush cached-moderation-state:update-all --only-uninitialized --batch-size=50

The proposed solution is to write directly to the database for non-default revisions, while keeping the current approach for default revisions. The existing solution for default revisions clears the content cache and triggers postSave actions, which can be important (for example, in our case it marks the entity to be reindexed in SOLR).

2. BatchUpdateHelper::updateEntity() does not take into account that an entity can have translations. In that case, the following is not sufficient:

$entity->setSyncing(TRUE);
$entity->setNewRevision(FALSE);
$entity->save();

We have to iterate over translations and do something like:

// Without this, all translations for the saved revision will get
// revision_translation_affected = 1, which is not correct.
if (!$translation->isRevisionTranslationAffected()) {
  $translation->setRevisionTranslationAffected(FALSE);
}

// Without this, only a record for the default translation is created.
$translation->set('cached_moderation_state', [
  'value' => $translation->get('moderation_state')->value,
  'updated' => \Drupal::time()->getCurrentTime(),
]);

I will propose a patch that addresses both of these issues soon.

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

gugalamaciek created an issue. See original summary.

gugalamaciek’s picture

Assigned: Unassigned » gugalamaciek

clayfreeman’s picture

The proposed solution is to write directly to the database for non-default revisions, while keeping the current approach for default revisions. The existing solution for default revisions clears the content cache and triggers postSave actions, which can be important (for example, in our case it marks the entity to be reindexed in SOLR).

I would much prefer to unset cached_moderation_state when loading non-default revisions via hook_entity_load(). In theory, this should work with the modified logic in Drupal core. End users of this module are not meant to use the field on loaded entities anyway.