To reproduce:

  1. Set up a D8 site with the default "Editorial" content moderation workflow under /admin/config/workflow/workflows.
  2. Select one of your content types under "This workflow applies to:"
  3. Create a migration of node content that brings some nodes into the selected content type.

Actual result:

All migrated nodes will be "unpublished" and in "Draft" mode; even if they where published on the origin.

Expected result:

The published state of the migrated nodes should be published or un-published as they were at the origin.

What I have tried:

If I disable content moderation for the specific content type before importing content, all nodes are imported with their correct published state.

Comments

jcmartinez created an issue. See original summary.

wim leers’s picture

Issue tags: +Workflow Initiative

Interesting!

This is probably due to content_moderation's hook_entity_presave() + hook_entity_insert() implementations.

mariaioann’s picture

I have the same problem. Moreover, due to content moderation new revisions are created for nodes when executing an incremental nodes migration (with --update flag).

kualee’s picture

I have just tried migrating one site from latest D7 to D8.8.2 and faced the exact same issue.
Any content types that doesn't have content moderation applied would migrate just fine, all revisions included.
Those content types with content moderation applied would face the above issue (unpublished)
I have also faced another issue (not sure whether related):
When trying to publish those unpublished contents, I also get error message "The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved."
Error saving unpublished node
I found this old issue that is possibly related, see https://www.drupal.org/project/drupal/issues/2848089#comment-12609586 #9, first point.

1) Node is Published on D7 (status 1). If you don't specify a value for moderation_state in the migration script (as in #5) then the node becomes unpublished after migration on D8. This is bad. Need the moderation_state to be "published" (which yes, depends on your exact workflow state ids)

The patch in this issue has been committed to core though, not sure why this issue still persists.

mariaioann’s picture

I have managed to overcome the problem with content moderation and migration as follows:

  1. Before migration of nodes and node revisions, I disable any workflow for the relevant CTs.
  2. I run the migration scripts.
  3. I re-enable the relevant workflows.
  4. I run the following sql scripts which fix the nodes' and revisions' moderated state:
drush sqlq "update content_moderation_state_field_data set moderation_state = 'published' where content_entity_id in (SELECT nid FROM node_field_data where status = 1);"
drush sqlq "update content_moderation_state_field_revision set moderation_state = 'published' where content_entity_revision_id in (SELECT vid FROM node_field_revision where status = 1);"
drush cr
logickal’s picture

We ran into this ourselves after we had added moderation to a content type and re-tested our migration. After some digging, I think the solution is relatively straightforward:

1) We had already subclassed the d7_node source plugin for our own purposes, and had a prepareRow() implementation already. If you don't have that, then you can either take this approach or create an event subscriber similar to what is outlined in https://www.lullabot.com/articles/overview-migrating-drupal-sites-8.

In our prepareRow, we based that check on published state, so it looks something like this:

  /**
   * {@inheritdoc}
   */
  public function prepareRow(Row $row) {
    // If the item is published, we should set the content moderation state to
    // active.
    if ($row->get('status') == 1) {
      $state = 'published';
    }
    else {
      $state = 'draft';
    }
    // Set the Moderation State on the source for processing.
    $row->setSourceProperty('moderation_state', $state);

    return parent::prepareRow($row);
  }

What we are doing here is adding the moderation_state property to the row, since it doesn't exist from the Drupal 7 data.

2) Now, you just need to make sure that your migration config is handling that data from the row and setting the destination with that property:

moderation_state: moderation_state

Version: 8.8.1 » 8.8.x-dev

Core issues are now filed against the dev versions where changes will be made. Document the specific release you are using in your issue comment. More information about choosing a version.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

akozoriz’s picture

I think you could use static_map plugin to properly setup moderation_state and node state

moderation_state:
     plugin: static_map
     source: status
     map:
        0: draft
        1: published
quietone’s picture

Status: Active » Needs review
StatusFileSize
new38.89 KB

An idea for a test.

Status: Needs review » Needs work

The last submitted patch, 10: 3105518-10.patch, failed testing. View results

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new39.38 KB

Maybe put some login on EntityContentBase that adds the moderation state.

Status: Needs review » Needs work

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

quietone’s picture

Status: Needs work » Needs review
StatusFileSize
new1.4 KB
new39.33 KB

Coding standard fixes.
Not sure why there are failures, the test passes locally.

Status: Needs review » Needs work

The last submitted patch, 14: 3105518-14.patch, failed testing. View results

quietone’s picture

Version: 8.9.x-dev » 9.1.x-dev
Status: Needs work » Needs review

Of course, I am working on 9.1.x.

mikelutz’s picture

Status: Needs review » Reviewed & tested by the community

It's quite a large patch for such a tiny change, but I think the test is useful. The fix looks correct, and it fixes the test, LGTM.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 14: 3105518-14.patch, failed testing. View results

mikelutz’s picture

Status: Needs work » Reviewed & tested by the community

Rtbc for 9 and 9.1, will need a reroll if RMs want to backport to 8.

larowlan’s picture

Status: Reviewed & tested by the community » Needs review
  1. +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php
    @@ -155,6 +156,12 @@ public static function create(ContainerInterface $container, array $configuratio
    +    if ($this->storage instanceof NodeStorageInterface) {
    

    It is possible to enable moderation of entities other than nodes - should we be using some of the logic that CM uses - ie. $entity_type->hasHandlerClass('moderation')

  2. +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php
    @@ -155,6 +156,12 @@ public static function create(ContainerInterface $container, array $configuratio
    +      if (!$row->hasDestinationProperty('moderation')) {
    ...
    +        $row->setDestinationProperty('moderation_state', $moderation_state);
    

    is there a reason we check for the 'moderation' property, but then set the 'moderation_state' property?

  3. +++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php
    @@ -155,6 +156,12 @@ public static function create(ContainerInterface $container, array $configuratio
    +        $moderation_state = $row->get('@status') ? 'published' : 'draft';
    

    Unfortunately, we cannot assume this either. Moderation states are per-site. Yes, these two are defined in the editorial workflow in the standard profile, but they may be named something completely different in actual sites.

    Can we make that configurable perhaps?

    Or should this whole thing just be resolved by process pipelines, i.e Migrate can't know all the things, and this might be some customisation that migrating sites need to consider?

mikelutz’s picture

Status: Needs review » Needs work

So the big issue here is, what are we trying to code for? In core, typically we are coding for the big one-off migration. In that case, nothing in d7 or d6 was moderatable except for nodes, so on that level, #20.1 makes sense, and is also the reason for #20.3, in that we are trying to map the status into the default moderation state. For #20.2, I maybe wrong, but I think somewhere we set the moderation property to tell the destination to treat the import as a moderated node, and should therefore set the moderation_state, but I do need to check that still.

The bigger issue right now is, If we go back to the initial issue reproduce steps though, we get:

  1. Set up a D8 site with the default "Editorial" content moderation workflow under /admin/config/workflow/workflows.
  2. Select one of your content types under "This workflow applies to:"
  3. Create a migration of node content that brings some nodes into the selected content type.

This, of course is not how the core migration system works. The migration system in core is a direct migration from a d7 site. The steps to reproduce above is not running a core migration. it's creating a custom migration. Which is fine, we support and allow that. So the issue is really just asking for a custom migration using the standard destination to set the moderation state if it's enabled. That gets tricky for a few reasons, #20.3 above specifically.

So what's really important here, is that if you are coming from d7 workbench moderation that we handle that correctly. And if you are migrating a custom migration into a moderated node bundle, that we give you a way to handle that correctly, and I think we already do. If you do steps one and two above, then in step three, you are responsible for setting status and moderation_state according to however you set up that node. we should not be trying to guess in core. I think this is working as designed. I'm going to set it to NW for a week for someone to convince me otherwise, but I don't think this needs to be done in core.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

mrP’s picture

Ran into this same issue. The static_map approach as in #9 worked great for my purposes.

@akozoriz Thanks!

EDIT -- I did find that node_field_data.publish_state and node_field_data.unpublish_state did not get populated. However, that appears to be more on the scheduler end of things. Perhaps they can address this as a part of #3012154: Migration handler for publish_on and unpublish_on node fields.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

wim leers’s picture

I'm going to set it to NW for a week for someone to convince me otherwise, but I don't think this needs to be done in core.

I think you're right. But I also think it needs to be documented explicitly. Perhaps in a change record, for visibility and discoverability? (Even if that is atypical.)

nikhil_drupal’s picture

@larowlan Did you found a solution for this...?
I came across same issue now, In D7 we have different moderation states like draft, needs-review, rejected, etc. when I'm migrated the d7_node_Complete_node_type plugin, it dont show the custom moderation states names and rather it saves as draft for all the revision version even the published version.

Can someone help with fix .

My drupal version is 8.9.7

hardik_patel_12’s picture

Faced this same issue. The static_map approach suggested in #9 worked great for my purposes.

@akozoriz Thanks!

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

aolvera’s picture

StatusFileSize
new39.35 KB

Reroll for 9.3.x

mikelutz’s picture

Status: Needs work » Closed (works as designed)

Closing this as there is consensus that there is nothing to do for core migrations.

fengtan’s picture

We ran into this as well and applied a fix similar to #6 and #9, with moderation state mappings defined in the yml migration file instead of the event subscriber:

// You will need to add this event subscriber to mymodule.services.yml
class MyModulePopulateWorkbenchModerationState implements EventSubscriberInterface {

  public static function getSubscribedEvents() {
    return [
      MigrateEvents::PREPARE_ROW => 'prepareRow',
    ];
  }

  public function prepareRow(MigratePrepareRowEvent $event) {
    if (!$event->getSource() instanceof Node) {
      return;
    }

    $source = $event->getSource();
    if (!$source instanceof SqlBase) {
      return;
    }

    // Get D7 workbench moderation information for this node revision.
    //
    // In D7, changing the moderation state does not automatically create a new
    // revision: {workbench_moderation_node_history} may have multiple rows with
    // the same vid.
    //
    // In D9, changing the moderation state always creates a new revision:
    // revision_id and langcode are a primary key for
    // {content_moderation_state_field_revision}.
    //
    // Hence we populate the source row with the *latest* D7 moderation state,
    // by ordering the query by 'stamp DESC' and by fetching only one record.
    $ids = $event->getRow()->getSourceIdValues();
    $workbench_moderation = $source
      ->getDatabase()
      ->select('workbench_moderation_node_history', 'w')
      ->fields('w', [
        'state',
        'published',
      ])
      ->condition('vid', $ids['vid'])
      ->condition('nid', $ids['nid'])
      ->orderBy('stamp', 'DESC')
      ->execute()
      ->fetchAssoc();

    if ($workbench_moderation) {
      $event->getRow()->setSourceProperty('workbench_moderation_state', $workbench_moderation['state']);
      $event->getRow()->setSourceProperty('workbench_moderation_published', $workbench_moderation['published']);
    }
  }

}
# mymodule/migrations/my_node_migration.yml
process:
  # Support creating forward, non-default revisions (e.g. unpublished draft)
  # Requires core patch https://www.drupal.org/project/drupal/issues/3200949#comment-14014993
  revision_default: workbench_moderation_published
  moderation_state:
     plugin: static_map
     source:
       - workbench_moderation_state
       - status
     map:
        draft:
          0: unpublished
          1: draft
        published:
          0: published
          1: published