back

Knowlegde

Knowledge Centre

How to migrate paragraphs from Drupal 7 to Drupal 8

by editor | 22.12.2017

How to migrate paragraphs from Drupal 7 to Drupal 8

Found this really exciting task a couple of days ago, of migrating paragraphs to paragraphs from a Drupal 7 platform to a Drupal8. While searching for the right solution, we’ve started browsing the web. As nothing seemed to help, our backend developers created a custom solution. Before proceeding, here’s a special thanks to mtech-llc.com and to Ada Hernández, who gave us the inspiration from the solution.

While Ada is describing how to migrate field collections, we had to migrate paragraphs. Anyhow, the article really helped us.

 

INITIAL REQUIREMENTS:

 

Before proceeding, you will need to make sure that you have a Drupal 8 installed and functional website. Then, ensure that you have installed the following modules:

  • migrate_drupal
  • migrate_plus
  • migrate_tools
  • migrate_drupal_ui

An alternative method is to add dependencies to your custom module.

After you have ensured that you have all the modules installed, you will need to create a paragraph with bundle “contact” having 2 fields inside it:

  • Text plain (machine name: field_name)
  • Email: (machine name: field_email)
  • Number(Integer): (machine name: field_phone)

Then create a content type called Organization(machine name: organization) with:

  • Body field by default
  • Field paragraph type (machine name: field_contact)

Steps to be followed to make the setups:

The procedure is rather complicated and you may want to take your time to understand everything that is going on. Errors may appear if some of the steps are not done properly, and in this case, you may want to start it over, while reading carefully the steps below.

 

1. SPECIFY IN SETTINGS.PHP FILE THE SECOND CONNECTION TO DRUPAL 7

 

In the following example, my db name is “drupal_7_56”.

 

Example:

 

migrate
Top
default
$databases['migrate']['default'] = array (
'database' => 'drupal_7_56',
'username' => 'root',
'password' => '',
'prefix' => '',
'host' => '127.0.0.1',
'port' => '33067',
'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql',
'driver' => 'mysql',
);

 

2. CREATE A CUSTOM MODULE (MACHINENAME: CUSTOM)

 

3. CREATE THE YML TEMPLATES AND PLACE IT INTO THE CONFIG/INSTALL FOLDER

 

To create the yml template, you will need to firstly create a paragraph yml file. This ensures that the paragraphs will be migrated properly. The name of the file will be created by using this suffix “migrate_plus.migration.” before the name you choose for the migrate id.

 

To get back to my initial example, the name of the migrate id is: ‘d7_paragraph_contact’.

 

This means that the final name file will be: “migrate_plus.migration.d7_paragraph_contact.yml”.

 

Make sure you are not forgetting the .yml extension.

 

Use the following code to create the yml template:

 

langcode: en
status: true
dependencies: { }
id: d7_paragraph_contact
class: null
field_plugin_method: null
cck_plugin_method: null
migration_tags:
 - 'Drupal 7'
migration_group: migrate_drupal_7
label: Contacts
source:
 plugin: d7_paragraph_item
 key: migrate
#  field_name is used in our custom plugin to get data about the paragraph item.
 field_name: field_contact
Process:
 field_name:
   plugin: iterator
   source: field_name
   process:
     value: value
   revision_id: revision_id
 field_email:
   plugin: iterator
   source: field_email
   process:
     value: email
   revision_id: revision_id
 field_phone:
   plugin: iterator
   source: field_phone
   process:
     value: value
   revision_id: revision_id
destination:
 plugin: 'entity_reference_revisions:paragraph'
 default_bundle: contact
migration_dependencies:
 required: {  }
 optional: {  }

 

After the first yml template is created, you will need to create a second yml file for migrating the node (type: organization) containing the body and paragraph.

 

Getting back to my initial example, the name of this file will be: “migrate_plus.migrate.d7_node_organization.yml

 

langcode: en
status: true  
dependencies: {  }  
id: d7_node_organization  
class: null  
field_plugin_method: null  
cck_plugin_method: null  
migration_tags:  
 - 'Drupal 7'  
 - Content  
migration_group: migrate_drupal_7  
label: 'Nodes (Organization)'  
source:  
 plugin: d7_node  
 node_type: organization  
process:  
 nid: nid  
 vid: vid  
 langcode:  
   plugin: default_value  
   source: language  
   default_value: und  
 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  
 body:  
   plugin: iterator  
   source: body  
   process:  
     value: value  
     format:  
       -  
         plugin: static_map  
         bypass: true  
         source: format  
         map:  
           - null  
       -  
         plugin: skip_on_empty  
         method: process  
       -  
         plugin: migration  
         migration:  
           - d6_filter_format  
           - d7_filter_format  
         source: format  
 field_contact:  
   -  
     plugin: skip_on_empty  
     method: process  
     source: field_contact  
   -  
     plugin: migration_lookup  
     migration: d7_paragraph_contact  
     no_stub: true  
   -  
     plugin: iterator  
     process:  
       target_id: '0'  
       target_revision_id: '1'  
destination:  
 plugin: 'entity:node'  
 default_bundle: organization  
migration_dependencies:  
 required: {  }  
 optional: {  }

 

4. CREATE THE CLASS WHICH IMPLEMENTS THE SOURCE PLUGIN FOR THE PARAGRAPH

 

This is the hard part, so you may want to pay attention to the code below. Assumed the class name is ContactParagraph.php, place it into ‘/src/Plugin/migrate/source’ path.

 

<?php

 namespace Drupal\mparagraf\Plugin\migrate\source; 

 use Drupal\migrate\Row;  
use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity; 

 /**  
* D7_paragraph_item source.  
*  
* @MigrateSource(  
*   id = "d7_paragraph_item"  
* )  
*/  
class ContactParagraph extends FieldableEntity { 

  /**  
  * {@inheritdoc}  
  */  
 public function query() {  
   // Select node in its last revision.  
   $query = $this->select('paragraphs_item', 'fci')  
     ->fields('fci', [  
       'item_id',  
       'field_name',  
       'revision_id',  
     ]);  
   if (isset($this->configuration['field_name'])) {  
     $query->innerJoin('field_data_' . $this->configuration['field_name'], 'fd', 'fd.' . $this->configuration['field_name'] . '_value = fci.item_id');  
     $query->fields('fd',  
       ['entity_type',  
         'bundle',  
         'entity_id',  
         $this->configuration['field_name'] . '_revision_id',  
       ]);  
     $query->condition('fci.field_name', $this->configuration['field_name']); 

    } 

    return $query;  
 } 

  /**  
  * {@inheritdoc}  
  */  
 public function prepareRow(Row $row) { 

    // If field specified, get field revision ID so there aren't issues mapping.  
   if (isset($this->configuration['field_name'])) {  
     $row->setSourceProperty('revision_id', $row->getSourceProperty($this->configuration['field_name'] . '_revision_id'));  
   } 

    // Get field API field values.  
   foreach (array_keys($this->getFields('paragraphs_item', 'contact')) as $field) { 

      $item_id = $row->getSourceProperty('item_id');  
     $revision_id = $row->getSourceProperty('revision_id');  
     $row->setSourceProperty($field, $this->getFieldValues('paragraphs_item', $field, $item_id, $revision_id)); 

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

  /**  
  * {@inheritdoc}  
  */  
 public function fields() {  
   $fields = [  
     'item_id' => $this->t('Item ID'),  
     'revision_id' => $this->t('Revision ID'),  
     'field_name' => $this->t('Name of field'),  
   ];  
   return $fields;  
 } 

  /**  
  * {@inheritdoc}  
  */  
 public function getIds() {  
   $ids['item_id']['type'] = 'integer';  
   $ids['item_id']['alias'] = 'fci';  
   return $ids;  
 } 
 } 

 

5. IMPLEMENT HOOK_MIGRATE_MIGRATION_ID_PREPARE_ROW FOR CHANGING THE FIELD_CONTACT

 

After you have created the class which implements the source plugin for the paragraph, you will need a hook to change the field_contact. Pay attention, as this has to be done in the module’s ‘.module’ file.

 

<?php 

 use Drupal\migrate\Plugin\MigrationInterface; 
use Drupal\migrate\Plugin\MigrateSourceInterface; 
use Drupal\migrate\Row; 

 /** 
 * Implements hook_migrate_MIGRATION_ID_prepare_row(). 
 */ 
function custom_migrate_d7_node_organization_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) { 
    $values = $row->getSourceProperty('field_contact'); 
    $value_new = []; 
    if ($values) {
      foreach ($values as $value) { 
        $value_new[] = ['item_id' => $value['value']]; 
      } 
      $row->setSourceProperty('field_contact', $value_new); 

 

6. GRAB A BEER/JUICE/WINE, ENJOY AND COMMENT

 

Supposed this finally works, you may want to grab a beer, write a great “thanks” in the comment section below, and watch how your paragraphs are being migrated. Then, take a deeeep breath and then do the rest of the things you have to do with the paragraphs.

  • Knowlegde
    Knowledge Centre
    Replace a field's content based on another field using AJAX
    editor
  • Knowlegde
    Knowledge Centre
    Port your sites to Drupal 9
    editor
  • Knowlegde
    Knowledge Centre
    Drupal 8 or Drupal 9
    editor

Post a Comment.