Loading README.md +23 −2 Original line number Diff line number Diff line Loading @@ -26,7 +26,7 @@ Also we have next possible options: * update - Update previously-generated migrations. * migrate_group - Migration Group Id. Defaults to "migrate_generator_group" Migration Group Id. Defaults to "mgg" This command creates Migration for each source file and Migration Group for these created migrations. Migration group ID can be specified by **migrate_group** option in drush command. Loading Loading @@ -56,15 +56,19 @@ There are some rules for structure and content of source csv file: ## Complex fields with multiple properties For complex fields like Formatted text, Link, Datetime Range, Address, Price, we can have several properties in source file. For complex fields (like Formatted text, Link, Datetime Range, Address, Price, etc.) we can have several properties in source file. In this case, you can use `/` separator in column name for these cases. Supported complex field types: - **formatted text** (we can use format and value properties) - **link** (we can use uri and title properties) - **datetime_range** (we can use value and end_value properties) - **date_recur** (we can use value, end_value, rrule and timezone properties) - **address** (we can use various properties like country_code, locality, postal_code, etc.) - **price** (we can use number and currency_code properties) - **geolocation** (we can use lat and lng properties) - **key_value_field** (we can use key, value and description properties) - **double_field** (we can use first and second properties) For example, `body/value`, `body/format`, `date/value`, `date/end_value`, `price/number`, `price/currency_code`, etc. Loading Loading @@ -112,3 +116,20 @@ so we will have separate sources for these paragraphs `paragraph-media.csv`, `pa 2. Media paragraph could have references to Image media and Document media. so we will have separate media sources here too `media-document.csv`, `media-image.csv` ## Multilingual translation support Translation source files should be placed separately in **translation** folder. They should have similar filename pattern, as original source files: `{entity_type}-{bundle}.csv` or `{entity_type}.csv`. You can see some examples in /example/translation folder of this module. Structure and content of translation source files are the same, but with some additional rules: * In addition to first column with ID, need to have second column with langcode of translation. id| langcode |...| ---| --- | --- | 1| fr | ... | 1| de | ... | * Translation source file is supposed to have only translated values, it makes no sense to duplicate it with same values as in original import file. example/translation/node-article.csv 0 → 100644 +1 −0 Original line number Diff line number Diff line id;langcode;title;body/value example/translation/node-basic_page.csv 0 → 100644 +1 −0 Original line number Diff line number Diff line id;langcode;title;body src/Commands/DrushCommands.php +14 −2 Original line number Diff line number Diff line Loading @@ -74,7 +74,7 @@ class DrushCommands extends DrushCommandsBase { 'values_delimiter' => '|', 'date_format' => 'd-m-Y H:i:s', 'update' => FALSE, 'migrate_group' => 'migrate_generator_group', 'migrate_group' => 'mgg', ]) { // Scan source files. $sources = $this->scanner->readSources($directory, $options); Loading @@ -89,6 +89,18 @@ class DrushCommands extends DrushCommandsBase { } } } // Process translations. $translation_sources = $this->scanner->readSources($directory, $options, TRUE); if ($translation_sources) { foreach ($translation_sources as $entity_type => $entity_info) { foreach ($entity_info as $bundle => $source_info) { $translation_migration = $this->generator->createMigration($entity_type, $bundle, $source_info, $migration_group, $options, TRUE); if ($translation_migration) { $this->logger()->notice('Translation migration ' . $translation_migration->id() . ' was created'); } } } } $this->logger()->notice( 'Generation of migrations was completed. You can now run them using next command: drush migrate:import --all --group=' . $options['migrate_group'] Loading @@ -106,7 +118,7 @@ drush migrate:import --all --group=' . $options['migrate_group'] * @usage migrate_generator:clean_migrations migrate_generator_group * Remove all migrations in a given group and group itself. */ public function cleanMigrationGroup($group = 'migrate_generator_group') { public function cleanMigrationGroup($group = 'mgg') { $this->generator->removeMigrationGroup($group); } Loading src/Generator.php +77 −8 Original line number Diff line number Diff line Loading @@ -101,12 +101,17 @@ class Generator { * Migration Group object. * @param array $options * Additional options. * @param bool $is_translation * TRUE for translation migration. * * @return \Drupal\migrate_plus\Entity\MigrationInterface|null * Migration object or NULL. */ public function createMigration($entity_type, $bundle, array $source, MigrationGroupInterface $migration_group, array $options) { public function createMigration($entity_type, $bundle, array $source, MigrationGroupInterface $migration_group, array $options, $is_translation = FALSE) { $migration_id = $source['migration_id']; if ($is_translation) { $migration_id = $migration_id . '__translation'; } // Check if migration exists. $migration = Migration::load($migration_id); if ($migration) { Loading @@ -123,17 +128,70 @@ class Generator { } } if (empty($migration)) { $label = Unicode::ucfirst($entity_type) . ':' . Unicode::ucfirst($bundle); if ($is_translation) { $label = $label . ' translation'; } $migrate_properties = [ 'id' => $migration_id, 'label' => Unicode::ucfirst($entity_type) . ':' . Unicode::ucfirst($bundle), 'label' => $label, 'status' => TRUE, 'migration_group' => $migration_group->id(), 'migration_tags' => ['migrate_generator'], 'source' => $this->setSource($source, $options), 'destination' => $this->setDestination($entity_type, $bundle), 'source' => $this->setSource($source, $options, $is_translation), 'destination' => $this->setDestination($entity_type, $bundle, $is_translation), ]; $migration_dependencies = []; $migration_process = []; // Add some default configuration for translations. if ($is_translation) { // Add dependency to base migration. $migration_dependencies[] = $source['migration_id']; if ($source['id_key']) { // Add default process to find entity from base migration. $migration_process['pseudo_entity_id'] = [ [ 'plugin' => 'get', 'source' => $source['id'], ], [ 'plugin' => 'migration_lookup', 'migration' => $source['migration_id'], ], [ 'plugin' => 'skip_on_empty', 'method' => 'row', ], ]; $migration_process[$source['id_key']] = [ 'plugin' => 'get', 'source' => '@pseudo_entity_id', ]; // We need to fill revision field to paragraphs too. if ($entity_type == 'paragraph' && $source['revision_key']) { $migration_process[$source['id_key']] = [ [ 'plugin' => 'get', 'source' => '@pseudo_entity_id', ], [ 'plugin' => 'extract', 'index' => ['0'], ], ]; $migration_process[$source['revision_key']] = [ [ 'plugin' => 'get', 'source' => '@pseudo_entity_id', ], [ 'plugin' => 'extract', 'index' => ['1'], ], ]; } } } foreach ($source['fields'] as $field_name => $field_info) { $field_info['options'] = $options; try { Loading Loading @@ -183,18 +241,24 @@ class Generator { * Source array. * @param array $options * Source CSV options. * @param bool $is_translation * TRUE for translation migration. * * @return array * Source definition. */ protected function setSource(array $source, array $options) { protected function setSource(array $source, array $options, $is_translation = FALSE) { $fields = []; foreach ($source['header'] as $column) { $fields[] = [ 'name' => $column, ]; } $ids = [$source['id']]; if ($is_translation) { // Use pair of ID+Langcode as unique id for translation. $ids = array_slice($source['header'], 0, 2); } return [ 'plugin' => 'csv', 'path' => $source['source'], Loading @@ -202,7 +266,7 @@ class Generator { 'enclosure' => $options['enclosure'], 'header_row_count' => 1, 'fields' => $fields, 'ids' => [$source['id']], 'ids' => $ids, 'constants' => [], ]; } Loading @@ -214,11 +278,13 @@ class Generator { * Destination entity type. * @param string $bundle * Destination bundle. * @param bool $is_translation * TRUE for translation migration. * * @return array * Destination definition. */ protected function setDestination($entity_type, $bundle) { protected function setDestination($entity_type, $bundle, $is_translation = FALSE) { $destination = [ 'plugin' => 'entity:' . $entity_type, ]; Loading @@ -228,6 +294,9 @@ class Generator { if ($entity_type != $bundle) { $destination['default_bundle'] = $bundle; } if ($is_translation) { $destination['translations'] = TRUE; } return $destination; } Loading Loading
README.md +23 −2 Original line number Diff line number Diff line Loading @@ -26,7 +26,7 @@ Also we have next possible options: * update - Update previously-generated migrations. * migrate_group - Migration Group Id. Defaults to "migrate_generator_group" Migration Group Id. Defaults to "mgg" This command creates Migration for each source file and Migration Group for these created migrations. Migration group ID can be specified by **migrate_group** option in drush command. Loading Loading @@ -56,15 +56,19 @@ There are some rules for structure and content of source csv file: ## Complex fields with multiple properties For complex fields like Formatted text, Link, Datetime Range, Address, Price, we can have several properties in source file. For complex fields (like Formatted text, Link, Datetime Range, Address, Price, etc.) we can have several properties in source file. In this case, you can use `/` separator in column name for these cases. Supported complex field types: - **formatted text** (we can use format and value properties) - **link** (we can use uri and title properties) - **datetime_range** (we can use value and end_value properties) - **date_recur** (we can use value, end_value, rrule and timezone properties) - **address** (we can use various properties like country_code, locality, postal_code, etc.) - **price** (we can use number and currency_code properties) - **geolocation** (we can use lat and lng properties) - **key_value_field** (we can use key, value and description properties) - **double_field** (we can use first and second properties) For example, `body/value`, `body/format`, `date/value`, `date/end_value`, `price/number`, `price/currency_code`, etc. Loading Loading @@ -112,3 +116,20 @@ so we will have separate sources for these paragraphs `paragraph-media.csv`, `pa 2. Media paragraph could have references to Image media and Document media. so we will have separate media sources here too `media-document.csv`, `media-image.csv` ## Multilingual translation support Translation source files should be placed separately in **translation** folder. They should have similar filename pattern, as original source files: `{entity_type}-{bundle}.csv` or `{entity_type}.csv`. You can see some examples in /example/translation folder of this module. Structure and content of translation source files are the same, but with some additional rules: * In addition to first column with ID, need to have second column with langcode of translation. id| langcode |...| ---| --- | --- | 1| fr | ... | 1| de | ... | * Translation source file is supposed to have only translated values, it makes no sense to duplicate it with same values as in original import file.
example/translation/node-article.csv 0 → 100644 +1 −0 Original line number Diff line number Diff line id;langcode;title;body/value
example/translation/node-basic_page.csv 0 → 100644 +1 −0 Original line number Diff line number Diff line id;langcode;title;body
src/Commands/DrushCommands.php +14 −2 Original line number Diff line number Diff line Loading @@ -74,7 +74,7 @@ class DrushCommands extends DrushCommandsBase { 'values_delimiter' => '|', 'date_format' => 'd-m-Y H:i:s', 'update' => FALSE, 'migrate_group' => 'migrate_generator_group', 'migrate_group' => 'mgg', ]) { // Scan source files. $sources = $this->scanner->readSources($directory, $options); Loading @@ -89,6 +89,18 @@ class DrushCommands extends DrushCommandsBase { } } } // Process translations. $translation_sources = $this->scanner->readSources($directory, $options, TRUE); if ($translation_sources) { foreach ($translation_sources as $entity_type => $entity_info) { foreach ($entity_info as $bundle => $source_info) { $translation_migration = $this->generator->createMigration($entity_type, $bundle, $source_info, $migration_group, $options, TRUE); if ($translation_migration) { $this->logger()->notice('Translation migration ' . $translation_migration->id() . ' was created'); } } } } $this->logger()->notice( 'Generation of migrations was completed. You can now run them using next command: drush migrate:import --all --group=' . $options['migrate_group'] Loading @@ -106,7 +118,7 @@ drush migrate:import --all --group=' . $options['migrate_group'] * @usage migrate_generator:clean_migrations migrate_generator_group * Remove all migrations in a given group and group itself. */ public function cleanMigrationGroup($group = 'migrate_generator_group') { public function cleanMigrationGroup($group = 'mgg') { $this->generator->removeMigrationGroup($group); } Loading
src/Generator.php +77 −8 Original line number Diff line number Diff line Loading @@ -101,12 +101,17 @@ class Generator { * Migration Group object. * @param array $options * Additional options. * @param bool $is_translation * TRUE for translation migration. * * @return \Drupal\migrate_plus\Entity\MigrationInterface|null * Migration object or NULL. */ public function createMigration($entity_type, $bundle, array $source, MigrationGroupInterface $migration_group, array $options) { public function createMigration($entity_type, $bundle, array $source, MigrationGroupInterface $migration_group, array $options, $is_translation = FALSE) { $migration_id = $source['migration_id']; if ($is_translation) { $migration_id = $migration_id . '__translation'; } // Check if migration exists. $migration = Migration::load($migration_id); if ($migration) { Loading @@ -123,17 +128,70 @@ class Generator { } } if (empty($migration)) { $label = Unicode::ucfirst($entity_type) . ':' . Unicode::ucfirst($bundle); if ($is_translation) { $label = $label . ' translation'; } $migrate_properties = [ 'id' => $migration_id, 'label' => Unicode::ucfirst($entity_type) . ':' . Unicode::ucfirst($bundle), 'label' => $label, 'status' => TRUE, 'migration_group' => $migration_group->id(), 'migration_tags' => ['migrate_generator'], 'source' => $this->setSource($source, $options), 'destination' => $this->setDestination($entity_type, $bundle), 'source' => $this->setSource($source, $options, $is_translation), 'destination' => $this->setDestination($entity_type, $bundle, $is_translation), ]; $migration_dependencies = []; $migration_process = []; // Add some default configuration for translations. if ($is_translation) { // Add dependency to base migration. $migration_dependencies[] = $source['migration_id']; if ($source['id_key']) { // Add default process to find entity from base migration. $migration_process['pseudo_entity_id'] = [ [ 'plugin' => 'get', 'source' => $source['id'], ], [ 'plugin' => 'migration_lookup', 'migration' => $source['migration_id'], ], [ 'plugin' => 'skip_on_empty', 'method' => 'row', ], ]; $migration_process[$source['id_key']] = [ 'plugin' => 'get', 'source' => '@pseudo_entity_id', ]; // We need to fill revision field to paragraphs too. if ($entity_type == 'paragraph' && $source['revision_key']) { $migration_process[$source['id_key']] = [ [ 'plugin' => 'get', 'source' => '@pseudo_entity_id', ], [ 'plugin' => 'extract', 'index' => ['0'], ], ]; $migration_process[$source['revision_key']] = [ [ 'plugin' => 'get', 'source' => '@pseudo_entity_id', ], [ 'plugin' => 'extract', 'index' => ['1'], ], ]; } } } foreach ($source['fields'] as $field_name => $field_info) { $field_info['options'] = $options; try { Loading Loading @@ -183,18 +241,24 @@ class Generator { * Source array. * @param array $options * Source CSV options. * @param bool $is_translation * TRUE for translation migration. * * @return array * Source definition. */ protected function setSource(array $source, array $options) { protected function setSource(array $source, array $options, $is_translation = FALSE) { $fields = []; foreach ($source['header'] as $column) { $fields[] = [ 'name' => $column, ]; } $ids = [$source['id']]; if ($is_translation) { // Use pair of ID+Langcode as unique id for translation. $ids = array_slice($source['header'], 0, 2); } return [ 'plugin' => 'csv', 'path' => $source['source'], Loading @@ -202,7 +266,7 @@ class Generator { 'enclosure' => $options['enclosure'], 'header_row_count' => 1, 'fields' => $fields, 'ids' => [$source['id']], 'ids' => $ids, 'constants' => [], ]; } Loading @@ -214,11 +278,13 @@ class Generator { * Destination entity type. * @param string $bundle * Destination bundle. * @param bool $is_translation * TRUE for translation migration. * * @return array * Destination definition. */ protected function setDestination($entity_type, $bundle) { protected function setDestination($entity_type, $bundle, $is_translation = FALSE) { $destination = [ 'plugin' => 'entity:' . $entity_type, ]; Loading @@ -228,6 +294,9 @@ class Generator { if ($entity_type != $bundle) { $destination['default_bundle'] = $bundle; } if ($is_translation) { $destination['translations'] = TRUE; } return $destination; } Loading