I am working on upgrading a Drupal 7 site to Drupal 8. The upgrade uses a content-only custom migration. The Drupal 7 source site and the Drupal 8 destination do not contain any custom modules other than a Drupal 8 custom module containing the custom migration configuration. Both source and destination site were created using the Standard install profile.

I have created migrations for both the node and node revisions of the Page content type. The page node consists of a media reference field and a paragraph reference field. After running the migration I notice that for migrate nodes which had both a published and draft version in which the draft revision is more recent than the published revision then the current content revision is not set.

Here is the moderation tab of such a node in Drupal 7:

Drupal 7 node moderation tab

Here is the moderation tab of the same node after migrating it to Drupal 8:

Drupal 8 node moderation tab

Following is the `content_moderation_state_revision` table from the destination database. The node in question is id = 3.

Contents of content_moderation_state_revision table in Drupal 8 database

Following is the `content_moderation_state_field_revision` table from the destination database.

Contents of content_moderation_state_field_revision table in Drupal 8 database

Here is the config for the Page node migration:

langcode: en 

status: true 

dependencies: {  } 

id: health_node_standard_page 

label: Standard Page Nodes 

audit: true 

migration_tags: 

  - Drupal 7 

  - Content 

migration_group: health 

deriver: Drupal\node\Plugin\migrate\D7NodeDeriver 

source: 

  plugin: d7_node_extended 

  node_type: page 

process: 

  nid: tnid 

  vid: vid 

  langcode: 

    - 

      plugin: static_map 

      source: language 

      map: 

        und: en 

      bypass: true 

  title: title 

  uid: node_uid 

  status: status 

  created: created 

  changed: changed 

  promote: promote 

  sticky: sticky 

  revision_uid: revision_uid 

  revision_log: log 

  revision_timestamp: timestamp 

  field_h_content_blocks: 

    plugin: sub_process 

    source: field_content_blocks 

    process: 

      paragraphs: 

        plugin: migration_lookup 

        migration: 

          - health_paragraph_content_text 

        source: value 

      target_id: 

        - 

          plugin: extract 

          source: '@paragraphs' 

          index: 

            - 0 

      target_revision_id: 

        - 

          plugin: extract 

          source: '@paragraphs' 

          index: 

            - 1 

  field_h_image_featured: 

    - 

      plugin: sub_process 

      source: field_featured_image 

      process: 

        target_id: 

          - 

            plugin: migration_lookup 

            migration: health_media_image 

            source: fid 

  moderation_state: 

    - 

      plugin: static_map 

      source: moderation_state 

      map: 

        published: published 

        draft: draft 

        needs_review: needs_review 

      default_value: draft 

destination: 

  plugin: entity:node 

  default_bundle: h_standard_page 

migration_dependencies: 

  required: 

    - health_user 

    - health_paragraph_content_text 

    - health_media_image 

Here is the config for the Page node revisions migration:

langcode: en 

status: true 

dependencies: {  } 

id: health_node_revision_standard_page 

label: Node revisions - Standard Page 

audit: true 

migration_tags: 

  - Drupal 7 

  - Content 

migration_group: health 

deriver: Drupal\node\Plugin\migrate\D7NodeDeriver 

source: 

  plugin: d7_node_revision_extended 

process: 

  nid: 

    - 

      plugin: get 

      source: nid 

  vid: 

    - 

      plugin: get 

      source: vid 

  langcode: 

    - 

      plugin: static_map 

      source: language 

      map: 

        und: en 

      bypass: true 

  title: title 

  uid: node_uid 

  status: 

    - 

      plugin: get 

      source: status 

  created: created 

  changed: changed 

  promote: promote 

  sticky: sticky 

  revision_uid: revision_uid 

  revision_log: log 

  revision_timestamp: timestamp 

  moderation_state: 

    - 

      plugin: static_map 

      source: moderation_state 

      map: 

        published: published 

        draft: draft 

        needs_review: needs_review 

      default_value: draft 

destination: 

  plugin: entity_revision:node 

  default_bundle: h_standard_page 

migration_dependencies: 

  required: 

    - health_node_standard_page 

Both migrations make use of a custom "Source" which extends the "d7_node" and "d7_node_revision" source plugins accordingly. These extended source plugins get the moderation state info of the relevant node or node revision.

/**
 * Extended version of core Drupal 7 node source from database.
 *
 * @MigrateSource(
 *   id = "d7_node_extended",
 *   source_module = "node"
 * )
 */
class D7NodeExtended extends Node {

  /**
   * {@inheritdoc}
   */
  public function prepareRow(Row $row) {
    // Make the node's moderation state available to migration.
    $database = $this->getDatabase();
    $vid = $row->getSourceProperty('vid');
    $query = $database
      ->select('workbench_moderation_node_history', 'wm')
      ->fields('wm', ['state'])
      ->condition('wm.vid', $vid)
      ->orderBy('wm.stamp', 'DESC')
      ->range(0, 1);
    $value = $query
      ->execute()
      ->fetchField();
    if ($value) {
      $row->setSourceProperty('moderation_state', $value);
    }

    return parent::prepareRow($row);
  }
}
/**
 * Extended version of core Drupal 7 node source from database.
 *
 * @MigrateSource(
 *   id = "d7_node_revision_extended",
 *   source_module = "node"
 * )
 */
class D7NodeRevisionExtended extends NodeRevision {

  /**
   * {@inheritdoc}
   */
  public function prepareRow(Row $row) {
    // Make the node revision's moderation state available to migration.
    $database = $this->getDatabase();
    $vid = $row->getSourceProperty('vid');
    $query = $database
      ->select('workbench_moderation_node_history', 'wm')
      ->fields('wm', ['state'])
      ->condition('wm.vid', $vid)
      ->orderBy('wm.stamp', 'DESC')
      ->range(0, 1);
    $value = $query
      ->execute()
      ->fetchField();
    if ($value) {
      $row->setSourceProperty('moderation_state', $value);
    }

    return parent::prepareRow($row);
  }
}

Comments

Begun created an issue. See original summary.

quietone’s picture

@Begun, thanks for reporting this problem.

My first thought is that using the node complete migration, d7_node_complete, and the node complete source plugin, \Drupal\node\Plugin\migrate\source\d7\NodeComplete is preferred for migrating all the revision. Also, take a look at #3105518: Migrating while content moderation is enabled ignores the published state of the source, which may be of use. As I recall, several people offered solutions to that problem, which is related to this one.

msnassar’s picture

For my case for node revision migration, I am also checking whether the revision is the published one or not.

  protected function prepareModerationState(Row $row) {
    $query = $this->select('workbench_moderation_node_history', 'wm')
      ->fields('wm', ['state', 'published'])
      ->condition('wm.vid', $row->getSourceProperty('vid'))
      ->orderBy('wm.stamp', 'DESC')
      ->range(0, 1);

    $data = $query->execute()->fetchAssoc();
    if (!empty($data)) {
      // In case the final state is published but the revision is not the
      // published one, set the state to draft.
      if ($data['state'] == 'published' && $data['published'] == 0) {
        return 'draft';
      }
      return $data['state'];
    }
    return NULL; // I have validation on migration. So if it returns null, I know which revision to debug (because moderation state can't be null)
  }

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

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev
cilefen’s picture

Status: Active » Postponed (maintainer needs more info)

Is this support request still an issue? Is it a bug?

cilefen’s picture

Status: Postponed (maintainer needs more info) » Closed (outdated)
sandip.prajapati’s picture

Hello
can you please provide full code?

I am getting error node class not exist.
Error: Class "Drupal\my_module\Plugin\migrate\source\Node\Node" not found in include() (line 24 of C:\xampp\htdocs\d10_custom\web\modules\custom\mymodule\src\Plugin\migrate\source\D7NodeExtended.php).
[warning] Drush command terminated abnormally.

<?php

namespace Drupal\my_module\Plugin\migrate\source\Node;

use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\migrate\Plugin\migrate\source\SqlBase;





/**
 * Extended version of core Drupal 7 node source from database.
 *
 * @MigrateSource(
 *   id = "d7_node_extended",
 *   source_module = "node"
 * )
 */
class D7NodeExtended extends Node {
 /**
   * {@inheritdoc}
   */
  public function prepareRow(Row $row) {
    // Make the node's moderation state available to migration.
    $database = $this->getDatabase();
    $vid = $row->getSourceProperty('vid');
    $query = $database
      ->select('workbench_moderation_node_history', 'wm')
      ->fields('wm', ['state'])
      ->condition('wm.vid', $vid)
      ->orderBy('wm.stamp', 'DESC')
      ->range(0, 1);
    $value = $query
      ->execute()
      ->fetchField();
    if ($value) {
      $row->setSourceProperty('moderation_state', $value);
    }

    return parent::prepareRow($row);
  }
}