Problem/Motivation

Content Moderation sets the 'default revision' flag in \Drupal\content_moderation\Entity\Handler\ModerationHandler::onPresave() based on various logic checks in \Drupal\content_moderation\EntityOperations::entityPresave(), which prevents an entity validation restriction to be applied to an entity form.

For a concrete example: in #2858431: Book storage and UI is not revision aware, changes to drafts leak into live site we shouldn't allow users to submit the node form if they made any changes to the book outline and the new revision which results from that node form submission will not be a default revision. We should be able to simply throw an entity validation error and not allow the form to be submitted until either 1) the book changes are reverted or 2) the node will be made the default revision.

Proposed resolution

Move the logic from \Drupal\content_moderation\Entity\Handler\ModerationHandler::onPresave() and \Drupal\content_moderation\EntityOperations::entityPresave() somewhere in the entity form processing.

Remaining tasks

Do it.

User interface changes

Nope.

API changes

Not sure yet.

Data model changes

Nope.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

amateescu created an issue. See original summary.

amateescu’s picture

Status: Active » Needs review
FileSize
10.4 KB

I think our best bet is to move the logic from \Drupal\content_moderation\Entity\Handler\ModerationHandler::onPresave() and \Drupal\content_moderation\EntityOperations::entityPresave() to an entity builder, something like the patch attached.

amateescu’s picture

However, after looking at the code some more, I'm not sure what would happen when some module decides to remove the 'moderation_state_default' widget from the form. We probably need to move all this code to a generic #entity_builder added through entity_form_alter() instead.

Status: Needs review » Needs work

The last submitted patch, 2: 2883868.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

timmillwood’s picture

We would also need to make sure this is done on Entity forms and on forms such as \Drupal\content_moderation\Form\EntityModerationForm.

This could also cause issues if the moderation state is changed programmatically and not via a form. The main place I can think of that this is done is tests.

Sam152’s picture

Yeah, we can't purely rely on forms to do this for us, it has to work at an API level for changes made programmatically as well as for REST support. Given we want the same validation to apply for REST, is there another concept which can accomodate this earlier in the lifecycle?

amateescu’s picture

Status: Needs work » Needs review
FileSize
12.47 KB
8.73 KB

#5 and #6 are correct, we definitely want this to work on the API level, so how about doing it in \Drupal\content_moderation\Plugin\Field\ModerationStateFieldItemList::onChange()?

Status: Needs review » Needs work

The last submitted patch, 7: 2883868-7.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Sam152’s picture

amateescu’s picture

@Sam152, I don't think so, it's related but not really in the scope of this bug.

Since #7 failed as well (and rightfully so), it seems that the only thing we can do is duplicate the logic in preSave() and use it in onChange() too. I'm still pondering if this covers the case for forms as well, since they don't change the moderation_state value directly.

No interdiff because it's mostly a new patch.

The last submitted patch, 10: 2883868-10-test-only.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 10: 2883868-10.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

amateescu’s picture

Status: Needs work » Needs review
FileSize
9.53 KB
712 bytes

We just need to update the expected exception now that \Drupal\content_moderation\Plugin\Field\ModerationStateFieldItemList::onChange() calls \Drupal\workflows\WorkflowInterface::getState(), which throws an \InvalidArgumentException if the given state doesn't exist.

Status: Needs review » Needs work

The last submitted patch, 13: 2883868-13.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

amateescu’s picture

Status: Needs work » Needs review
FileSize
9.08 KB
3.34 KB

Ok, after looking at the tests in EntityStateChangeValidationTest it seems that we shouldn't throw an error when changing the moderation state to an invalid one, that's being handled in the entity validation process. So we should simply check if the new state is valid for the given workflow before changing the host entity's 'default revision' flag and publishing status.

plach’s picture

Priority: Normal » Major
Status: Needs review » Reviewed & tested by the community
Issue tags: +WI critical
catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.4.x, thanks!

  • catch committed 6ea03e3 on 8.4.x
    Issue #2883868 by amateescu, Sam152, timmillwood, plach: Content...
tstoeckler’s picture

Knocking back to get some clarification on the first point, although it would be nice to quickly fix 2./3. as well, but they are really minor, so feel free to knock back to RTBC otherwise.

  1. +++ b/core/modules/content_moderation/src/EntityOperations.php
    @@ -110,7 +110,7 @@ public function entityPresave(EntityInterface $entity) {
           $update_default_revision = $entity->isNew()
             || $entity->isNewTranslation()
             || $current_state->isDefaultRevisionState()
    -        || !$this->isDefaultRevisionPublished($entity, $workflow);
    +        || !$this->moderationInfo->isDefaultRevisionPublished($entity);
    

    Do we still need this given that ModerationStateFieldItemList::onChange() seems to to the same thing? At the very least it seems the $entity->isDefaultRevision() call can be removed from ModerationHandler? #7 / #10 seem to reference this, but it's not entirely clear to me what's going on.

  2. +++ b/core/modules/content_moderation/src/ModerationInformation.php
    @@ -140,6 +141,29 @@ public function isLiveRevision(ContentEntityInterface $entity) {
    +    $default_revision = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId())->load($entity->id());
    

    As far as I can tell $this->entityTypeManager is already available in this class.

  3. +++ b/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php
    @@ -117,4 +118,41 @@ protected function computeModerationFieldItemList() {
    +    $current_state_id = $this->list[0]->value;
    

    I get that the current workflow state field will always have only a single item, but since we are being passed $delta anyway, I think it would make sense to use it here, instead of hardcoding 0.

Edit: X-posted with the commit. Thus, leaving at fixed, but would still like to get some clarification on point 1.

amateescu’s picture

Title: Content Moderation decides to set a new revision as the default one way too late in the entity update process » Followup: Content Moderation decides to set a new revision as the default one way too late in the entity update process
Status: Fixed » Needs review
FileSize
1.74 KB

Re #19.1:

Do we still need this given that ModerationStateFieldItemList::onChange() seems to to the same thing?

Yep, we still need to do the same thing in preSave() because ModerationStateFieldItemList::onChange() does not handle the case when an entity is created with an upfront value for moderation_state.

ModerationStateFieldItemList::onChange() is called by \Drupal\Core\Field\FieldItem(List)Base::set() (actually, by \Drupal\Core\TypedData\Plugin\DataType\Map::set()), which doesn't happen on entity creation.

Here's a small followup patch for points 2. and 3. :)

plach’s picture

Status: Needs review » Reviewed & tested by the community

And RTBC again, thanks @tstoeckler for spotting these!

tstoeckler’s picture

Yep, we still need to do the same thing in preSave() because ModerationStateFieldItemList::onChange() does not handle the case when an entity is created with an upfront value for moderation_state.

Ahh, that kind of (?) makes sense, as I guess nothing is "changing" in that case. Very interesting, in any case, hadn't thought of that. That definitely makes things clear, thanks!

And thanks for the quick follow-up!

plach’s picture

Status: Reviewed & tested by the community » Fixed

Opened a proper follow-up for this at #2897483: Use injected entity type manager in ModerationInformation, moving back to fixed.

plach’s picture

Title: Followup: Content Moderation decides to set a new revision as the default one way too late in the entity update process » Content Moderation decides to set a new revision as the default one way too late in the entity update process

Status: Fixed » Closed (fixed)

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

eyilmaz’s picture

Are we not missing a $entity->setNewRevision(TRUE); here?