I'm currently investigating migrate support for paragraphs, as it is crucial for a project. Keep you updated here. Experimental code might be available at https://github.com/derhasi/paragraphs

Files: 
CommentFileSizeAuthor
#17 2271181-17-paragraphs_migrate_revision.patch1.15 KBti2m
PASSED: [[SimpleTest]]: [MySQL] 0 pass(es). View
#3 2271181-3-paragraphs-migrate.patch10.96 KBjeroen.b
PASSED: [[SimpleTest]]: [MySQL] 0 pass(es). View
#1 2271181-1-paragraphs-migrate.patch10.96 KBderhasi
PASSED: [[SimpleTest]]: [MySQL] 0 pass(es). View

Comments

derhasi’s picture

Status: Active » Needs review
FileSize
10.96 KB
PASSED: [[SimpleTest]]: [MySQL] 0 pass(es). View

So, there is the first patch, that provides a basic Handler for Paragraphs Item Entity and the Paragraphs field.

The patch provides the state of my latest commit in my github repo: d5fce366544a86f6f25907586fc1c49db1ab41db.

jeroen.b’s picture

Thanks!
We are doing a project with migrate soon, in the project we also need to migrate paragraphs.
I will test this patch when we start migrating, most likely next week.

jeroen.b’s picture

FileSize
10.96 KB
PASSED: [[SimpleTest]]: [MySQL] 0 pass(es). View

I couldn't get the patch to work with Drupal make using beta4 (probably due to the added packaging information by Drupal), here's a version that works.

jeroen.b’s picture

No problems so far. Will be merged in beta5.

daveiano’s picture

First it´s very nice that you have written such a FileHandler for Paragraphs, thank you ;)

But I need a little help on this. How have I to use this in my Migration Class? Can you give a little Example?

jeroen.b’s picture

daveiano, for the FieldHandler for Paragraphs you just return an array of paragraph entity id's.

daveiano’s picture

yeah I have found out that, but the Paragraph Entities I have to create manually?

jeroen.b’s picture

You can use the MigrateDestinationParagraphsItem class for that, I didn't try that yet though.
Maybe derhasi can write something about how to use it.

jeroen.b’s picture

daveiano, I think you have to make a migration class that extends MigrateDestinationParagraphsItem.
Like "MyProjectParagraphsMigrate extends MigrateDestinationParagraphsItem" (register the class as MyProjectParagraphs in the hook_migrate_api()).

In that class you do field mappings to the paragraph entity like you normally would on the node.

Then in the node migrate you put something like:
$this->addFieldMapping('field_paragraphs', 'field_paragraphs')
->sourceMigration('MyProjectParagraphs');

ti2m’s picture

I can confirm that the patched worked for me, no problems so far. A basic example/some documentation would be nice though. IF I got it right, then one has to use two migrations, one for the paragraphs, one for the nodes that use the paragraphs.

My essential lines for the paragraphs import:

    $paragraph_options = MigrateDestinationParagraphsItem::options('en', 'filtered_html');
    $paragraph_options['field_name'] = 'field_pcf_content';
    $this->destination = new MigrateDestinationParagraphsItem('para_image_alligned_left_text', $paragraph_options);

where field_pcf_content is my paragraphs db field and para_image_alligned_left_text my paragraph type.

In the nodes import I then used (as stated above):

    $this->addFieldMapping('field_pcf_content', 'paragraphs')
      ->sourceMigration('Paragraphs');

I didn't extend 'MigrateDestinationParagraphsItem' though, both of the migrations extend 'Migration' directly.

derhasi’s picture

Yeah, that's right. We could provide an example feature with an example migration, so it could become clear.

ti2m’s picture

I seem to be having an issue when updating paragraphs (e.g. with drush mi --update). A new revison of a the paragraph is always being created, which means I end up with a ton of old revisions which aren't used for anything anymore. Especially confusing when working with file entites, files are being referenced by "dead" paragraphs. As far as I can tell there is no way to revert a paragraphs revison by itself, or is there? So my question is if this is a bug or a feature, that I'm not aware of. By looking at the code it doesn't look like there is any way right now to avoid the creation of a new paragraph revision.

I looked at MigrateDestinationNode and checked how they handle updates. The latest vid is loaded for the given nid. So I did the same for paragraphs revision_id and everything seems to be working for me as expected. I can post the patch if anyone is interested.

jeroen.b’s picture

@ti2m nice, please provide the patch so I can put the patch in a release!

jeroen.b’s picture

Just pushed this to dev, please test.

@ti2m, can you provide the patch?

meppapza’s picture

Hey, I am getting the following error: Unable to create a paragraphs item without a given host entity.

What is a common cause for this?
My code: http://pastebin.com/s1DUck0m

ti2m’s picture

FileSize
1.15 KB
PASSED: [[SimpleTest]]: [MySQL] 0 pass(es). View

@jeroen.b Sorry for the delay, here is the patch, just rerolled it against 7.x-1.x

jeroen.b’s picture

@meppapza, this patch is only pushed to dev. If you want to try it without upgrading to dev, please apply the patch.

Thanks @ti2m, pushed to dev.

  • jeroen.b committed 4f240a7 on 7.x-1.x authored by ti2m
    Issue #2271181 by jeroen.b, ti2m, derhasi: Migrate support for...
jeroen.b’s picture

Status: Needs review » Fixed

Status: Fixed » Closed (fixed)

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

moonray’s picture

Status: Closed (fixed) » Needs work

The docs were never added. It's still not very clear how to implement migration for paragraphs.

adamzimmermann’s picture

Hopefully this helps someone. I was able to piece this together with some of the examples above and some other random links.



/**
 * Base functionality for all paragraph slide types.
 */
abstract class ParagraphSlide extends ExampleAbstractClass {
  /**
   * The default text filter to use on filtered text fields.
   */
  const DEFAULT_TEXT_FILTER = 'filtered_html';

  /**
   * Options for constructing the destination.
   */
  protected $paragraph_options;

  /**
   * {@inheritdoc}
   */
  public function __construct($arguments) {
    parent::__construct($arguments);
    $this->description = t('Create paragraph entities that represent each slide.');

    // Set the item identifier.
    $this->itemIdentifier = 'slide_id';

    // Set the destination field and other options.
    $this->paragraph_options = MigrateDestinationParagraphsItem::options('en', 'filtered_html');
    $this->paragraph_options['field_name'] = 'field_slideshow_slides';

    // Name the source fields.
    $fields = array(
      'slide_id' => t('Slide ID'),
      ...
    );

    // Set the migration source.
    $this->source = new MigrateSourceXML(
      $this->xmlFilePath,
      $this->itemXpath,
      $this->itemIdentifier,
      $fields
    );

    // Create a migration map.
    $this->map = new MigrateSQLMap($this->machineName,
      array(
        'slide_id' => array(
          'type' => 'varchar',
          'length' => 255,
          'not null' => TRUE,
        ),
      ),
      MigrateDestinationParagraphsItem::getKeySchema()
    );

    // Add mappings.
    ...
  }

}

/**
 * Slide - Basic.
 */
class SlideBasic extends ParagraphSlide {

  /**
   * {@inheritdoc}
   */
  public function __construct($arguments) {
    // Set the xpath to the items to import.
    $this->itemXpath = '/entries/gallery/slides[@type="slide_basic"]';

    parent::__construct($arguments);
    $this->description = t('Basic Slide.');

    // Set a destination.
    $this->destination = new MigrateDestinationParagraphsItem(
      'slide_basic',
      $this->paragraph_options
    );
  }

}

/**
 * Slide - Video Slide.
 */
class SlideVideo extends ParagraphSlide {

  /**
   * {@inheritdoc}
   */
  public function __construct($arguments) {
    // Set the xpath to the items to import.
    $this->itemXpath = '/entries/gallery/slides[@type="slide_video"]';

    parent::__construct($arguments);
    $this->description = t('Video Slide.');

    // Set a destination.
    $this->destination = new MigrateDestinationParagraphsItem(
      'slide_video',
      $this->paragraph_options
    );

    // Video.
    $this->addFieldMapping('field_slide_video', 'video')
         ->xpath('video');
  }

}


/**
 * Import Slideshow content.
 */
class SlideshowNode extends ExampleCustomNodeClass {
  /**
   * The default text filter to use on filtered text fields.
   */
  const DEFAULT_TEXT_FILTER = 'filtered_html';

  /**
   * {@inheritdoc}
   */
  public function __construct($arguments) {
    parent::__construct($arguments);
    $this->description = t('Creates Gallery nodes');
    // Set the xpath to the items to import.
    $this->itemXpath = '/entries/gallery';

    // Name the source fields.
    $fields = array(
      'slides' => t('Slides'),
      ...
    );
    // Set the migration source.
    $this->source = new MigrateSourceXML($this->xmlFilePath, $this->itemXpath, $this->itemIdentifier, $fields);

    // Set the Gallery node type as the destination.
    $this->destination = new MigrateDestinationNode('slideshow');

    // Create a migration map.
    $this->map = new MigrateSQLMap($this->machineName,
      array(
        'id' => array(
          'type' => 'varchar',
          'length' => 255,
          'not null' => TRUE,
        ),
      ),
      MigrateDestinationNode::getKeySchema()
    );

    // Slides.
    $this->addFieldMapping('field_slideshow_slides', 'slides')
          ->xpath('slides/slide_id')
          ->sourceMigration(array('SlideBasic',
                                  'SlideVideo'));
  }

}
bburg’s picture

@Adamzimmermann Just curious what classes you actually did extend (or what classes did your parent classes extend)?

adamzimmermann’s picture

@bburg I was migrating from XML, so I used XMLMigration, but this should work with other base classes as well.

jeroen.b’s picture

Could it be added to the documentation?