Updated all the contrib modules to their latest versions.
[yaffs-website] / web / modules / contrib / migrate_upgrade / src / MigrateUpgradeDrushRunner.php
1 <?php
2
3 namespace Drupal\migrate_upgrade;
4
5 use Drupal\Core\Config\Entity\ConfigEntityInterface;
6 use Drupal\migrate\Plugin\MigrationInterface;
7 use Drupal\migrate\Event\MigrateEvents;
8 use Drupal\migrate\Event\MigrateIdMapMessageEvent;
9 use Drupal\migrate\MigrateExecutable;
10 use Drupal\Core\StringTranslation\StringTranslationTrait;
11 use Drupal\migrate_drupal\MigrationConfigurationTrait;
12 use Drupal\migrate_plus\Entity\Migration;
13 use Drupal\migrate_plus\Entity\MigrationGroup;
14 use Drupal\Core\Database\Database;
15 use Drush\Sql\SqlBase;
16
17 class MigrateUpgradeDrushRunner {
18
19   use MigrationConfigurationTrait;
20   use StringTranslationTrait;
21
22   /**
23    * The list of migrations to run and their configuration.
24    *
25    * @var \Drupal\migrate\Plugin\Migration[]
26    */
27   protected $migrationList;
28
29   /**
30    * MigrateMessage instance to display messages during the migration process.
31    *
32    * @var \Drupal\migrate_upgrade\DrushLogMigrateMessage
33    */
34   protected static $messages;
35
36   /**
37    * The Drupal version being imported.
38    *
39    * @var string
40    */
41   protected $version;
42
43   /**
44    * The state key used to store database configuration.
45    *
46    * @var string
47    */
48   protected $databaseStateKey;
49
50   /**
51    * List of D6 node migration IDs we've seen.
52    *
53    * @var array
54    */
55   protected $d6NodeMigrations = [];
56
57   /**
58    * List of D6 node revision migration IDs we've seen.
59    *
60    * @var array
61    */
62   protected $d6RevisionMigrations = [];
63
64   /**
65    * Drush options parameters.
66    *
67    * @var array
68    */
69   protected $options = [];
70
71   /**
72    * List of process plugin IDs used to lookup migrations.
73    *
74    * @var array
75    */
76   protected $migrationLookupPluginIds = [
77     'migration',
78     'migration_lookup',
79   ];
80
81   /**
82    * MigrateUpgradeDrushRunner constructor.
83    *
84    * @param array $options
85    *   Drush options parameters.
86    */
87   public function __construct(array $options = []) {
88     $this->setOptions($options);
89   }
90
91   /**
92    * Set options parameters according to Drush version.
93    *
94    * @param array $options
95    *   Drush options parameters.
96    */
97   protected function setOptions(array $options = []) {
98     $this->options = $options;
99     // Drush <= 8.
100     if (empty($this->options)) {
101       $this->options = [
102         'legacy_db_key' => drush_get_option('legacy-db-key'),
103         'legacy-db-url' => drush_get_option('legacy-db-url'),
104         'legacy-db-prefix' => drush_get_option('legacy-db-prefix'),
105         'legacy-root' => drush_get_option('legacy-root'),
106         'debug' => drush_get_option('debug'),
107         'migration-prefix' => drush_get_option('migration-prefix', 'upgrade_'),
108       ];
109     }
110   }
111
112   /**
113    * From the provided source information, instantiate the appropriate migrations
114    * in the active configuration.
115    *
116    * @throws \Exception
117    */
118   public function configure() {
119     $legacy_db_key = $this->options['legacy-db-key'];
120     $db_url = $this->options['legacy-db-url'];
121     $db_prefix = $this->options['legacy-db-prefix'];
122     if (!empty($legacy_db_key)) {
123       $connection = Database::getConnection('default', $legacy_db_key);
124       $this->version = $this->getLegacyDrupalVersion($connection);
125       $database_state['key'] = $legacy_db_key;
126       $database_state_key = 'migrate_drupal_' . $this->version;
127       \Drupal::state()->set($database_state_key, $database_state);
128       \Drupal::state()->set('migrate.fallback_state_key', $database_state_key);
129     }
130     else {
131       $db_spec = SqlBase::dbSpecFromDbUrl($db_url);
132       $db_spec['prefix'] = $db_prefix;
133       $connection = $this->getConnection($db_spec);
134       $this->version = $this->getLegacyDrupalVersion($connection);
135       $this->createDatabaseStateSettings($db_spec, $this->version);
136     }
137
138     $this->databaseStateKey = 'migrate_drupal_' . $this->version;
139     $migrations = $this->getMigrations($this->databaseStateKey, $this->version);
140     $this->migrationList = [];
141     foreach ($migrations as $migration) {
142       $this->applyFilePath($migration);
143       $this->expandNodeMigrations($migration);
144       $this->prefixFileMigration($migration);
145       $this->migrationList[$migration->id()] = $migration;
146     }
147   }
148
149   /**
150    * Adds the source base path configuration to relevant migrations.
151    *
152    * @param \Drupal\migrate\Plugin\MigrationInterface $migration
153    *   Migration to alter with the provided path.
154    */
155   protected function applyFilePath(MigrationInterface $migration) {
156     $destination = $migration->getDestinationConfiguration();
157     if ($destination['plugin'] === 'entity:file') {
158       // Make sure we have a single trailing slash.
159       $source_base_path = rtrim($this->options['legacy-root'], '/') . '/';
160       $source = $migration->getSourceConfiguration();
161       $source['constants']['source_base_path'] = $source_base_path;
162       $migration->set('source', $source);
163     }
164   }
165
166   /**
167    * For D6 term_node migrations, make sure the nid reference is expanded.
168    *
169    * @param \Drupal\migrate\Plugin\MigrationInterface $migration
170    *   Migration to alter with the list of node migrations.
171    */
172   protected function expandNodeMigrations(MigrationInterface $migration) {
173     $source = $migration->getSourceConfiguration();
174     // Track the node and node_revision migrations as we see them.
175     if ($source['plugin'] == 'd6_node') {
176       $this->d6NodeMigrations[] = $migration->id();
177     }
178     elseif ($source['plugin'] == 'd6_node_revision') {
179       $this->d6RevisionMigrations[] = $migration->id();
180     }
181     elseif ($source['plugin'] == 'd6_term_node') {
182       // If the ID mapping is to the underived d6_node migration, replace
183       // it with an expanded list of node migrations.
184       $process = $migration->getProcess();
185       $new_nid_process = [];
186       foreach ($process['nid'] as $delta => $plugin_configuration) {
187         if (in_array($plugin_configuration['plugin'], $this->migrationLookupPluginIds) &&
188             is_string($plugin_configuration['migration']) &&
189             substr($plugin_configuration['migration'], -7) == 'd6_node') {
190           $plugin_configuration['migration'] = $this->d6NodeMigrations;
191         }
192         $new_nid_process[$delta] = $plugin_configuration;
193       }
194       $migration->setProcessOfProperty('nid', $new_nid_process);
195     }
196     elseif ($source['plugin'] == 'd6_term_node_revision') {
197       // If the ID mapping is to the underived d6_node_revision migration, replace
198       // it with an expanded list of node migrations.
199       $process = $migration->getProcess();
200       $new_vid_process = [];
201       foreach ($process['vid'] as $delta => $plugin_configuration) {
202         if (in_array($plugin_configuration['plugin'], $this->migrationLookupPluginIds) &&
203             is_string($plugin_configuration['migration']) &&
204             substr($plugin_configuration['migration'], -16) == 'd6_node_revision') {
205           $plugin_configuration['migration'] = $this->d6RevisionMigrations;
206         }
207         $new_vid_process[$delta] = $plugin_configuration;
208       }
209       $migration->setProcessOfProperty('vid', $new_vid_process);
210     }
211   }
212
213   /**
214    * For D6 file fields, make sure the d6_file migration is prefixed.
215    *
216    * @param \Drupal\migrate\Plugin\MigrationInterface $migration
217    *   Migration to alter.
218    */
219   protected function prefixFileMigration(MigrationInterface $migration) {
220     $process = $migration->getProcess();
221     foreach ($process as $destination => &$plugins) {
222       foreach ($plugins as &$plugin) {
223         if ($plugin['plugin'] == 'd6_field_file') {
224           $plugin['migration'] = $this->modifyId($plugin['migration']);
225         }
226       }
227     }
228   }
229
230   /**
231    * Run the configured migrations.
232    */
233   public function import() {
234     static::$messages = new DrushLogMigrateMessage();
235     if ($this->options['debug']) {
236       \Drupal::service('event_dispatcher')->addListener(MigrateEvents::IDMAP_MESSAGE,
237         [get_class(), 'onIdMapMessage']);
238     }
239     foreach ($this->migrationList as $migration_id => $migration) {
240       drush_print(dt('Upgrading @migration', ['@migration' => $migration_id]));
241       $executable = new MigrateExecutable($migration, static::$messages);
242       // drush_op() provides --simulate support.
243       drush_op([$executable, 'import']);
244     }
245   }
246
247   /**
248    * Export the configured migration plugins as configuration entities.
249    */
250   public function export() {
251     $db_info = \Drupal::state()->get($this->databaseStateKey);
252
253     // Create a group to hold the database configuration.
254     $group_details = [
255       'id' => $this->databaseStateKey,
256       'label' => 'Import from Drupal ' . $this->version,
257       'description' => 'Migrations originally generated from drush migrate-upgrade --configure-only',
258       'source_type' => 'Drupal ' . $this->version,
259       'shared_configuration' => [
260         'source' => [
261           'key' => 'drupal_' . $this->version,
262         ],
263       ],
264     ];
265
266     // Only add the database connection info to the configuration entity
267     // if it was passed in as a parameter.
268     if (!empty($this->options['legacy-db-url'])) {
269       $group_details['shared_configuration']['source']['database'] = $db_info['database'];
270     }
271
272     // Ditto for the key.
273     if (!empty($this->options['legacy-db-key'])) {
274       $group_details['shared_configuration']['source']['key'] = $this->options['legacy-db-key'];
275     }
276
277     // Load existing migration group and update it with changed settings,
278     // or create a new one if none exists.
279     $group = MigrationGroup::load($group_details['id']);
280     if (empty($group)) {
281       $group = MigrationGroup::create($group_details);
282     }
283     else {
284       $this->setEntityProperties($group, $group_details);
285     }
286     $group->save();
287     foreach ($this->migrationList as $migration_id => $migration) {
288       drush_print(dt('Exporting @migration as @new_migration',
289         ['@migration' => $migration_id, '@new_migration' => $this->modifyId($migration_id)]));
290       $migration_details['id'] = $migration_id;
291       $migration_details['class'] = $migration->get('class');
292       $migration_details['cck_plugin_method'] = $migration->get('cck_plugin_method');
293       $migration_details['field_plugin_method'] = $migration->get('field_plugin_method');
294       $migration_details['migration_group'] = $this->databaseStateKey;
295       $migration_details['migration_tags'] = $migration->get('migration_tags');
296       $migration_details['label'] = $migration->get('label');
297       $migration_details['source'] = $migration->getSourceConfiguration();
298       $migration_details['destination'] = $migration->getDestinationConfiguration();
299       $migration_details['process'] = $migration->get('process');
300       $migration_details['migration_dependencies'] = $migration->getMigrationDependencies();
301       $migration_details = $this->substituteIds($migration_details);
302       $migration_entity = Migration::load($migration_details['id']);
303       if (empty($migration_entity)) {
304         $migration_entity = Migration::create($migration_details);
305       }
306       else {
307         $this->setEntityProperties($migration_entity, $migration_details);
308       }
309       $migration_entity->save();
310     }
311   }
312
313   /**
314    * Set entity properties.
315    *
316    * @param ConfigEntityInterface $entity
317    *   The entity to update.
318    * @param array $properties
319    *   The properties to update.
320    */
321   protected function setEntityProperties(ConfigEntityInterface $entity, array $properties) {
322     foreach ($properties as $key => $value) {
323       $entity->set($key, $value);
324     }
325     foreach ($entity as $property => $value) {
326       // Filter out values not in updated properties.
327       if (!isset($properties[$property])) {
328         $entity->set($property, NULL);
329       }
330     }
331   }
332
333   /**
334    * Rewrite any migration plugin IDs so they won't conflict with the core
335    * IDs.
336    *
337    * @param array $entity_array
338    *   A configuration array for a migration.
339    *
340    * @return array
341    *   The migration configuration array modified with new IDs.
342    */
343   protected function substituteIds(array $entity_array) {
344     $entity_array['id'] = $this->modifyId($entity_array['id']);
345     foreach ($entity_array['migration_dependencies'] as $type => $dependencies) {
346       foreach ($dependencies as $key => $dependency) {
347         $entity_array['migration_dependencies'][$type][$key] = $this->modifyId($dependency);
348       }
349     }
350     $this->substituteMigrationIds($entity_array['process']);
351     return $entity_array;
352   }
353
354   /**
355    * Recursively substitute IDs for migration plugins.
356    *
357    * @param mixed $process
358    */
359   protected function substituteMigrationIds(&$process) {
360     if (is_array($process)) {
361       // We found a migration plugin, change the ID.
362       if (isset($process['plugin']) && in_array($process['plugin'], $this->migrationLookupPluginIds)) {
363         if (is_array($process['migration'])) {
364           $new_migration = [];
365           foreach ($process['migration'] as $migration) {
366             $new_migration[] = $this->modifyId($migration);
367           }
368           $process['migration'] = $new_migration;
369         }
370         else {
371           $process['migration'] = $this->modifyId($process['migration']);
372         }
373         // The source_ids configuration for migrate_lookup is keyed by
374         // migration id.  If it is there, we need to rekey to the new ids.
375         if (isset($process['source_ids']) && is_array($process['source_ids'])) {
376           $new_source_ids = [];
377           foreach ($process['source_ids'] as $migration_id => $source_ids) {
378             $new_migration_id = $this->modifyId($migration_id);
379             $new_source_ids[$new_migration_id] = $source_ids;
380           }
381           $process['source_ids'] = $new_source_ids;
382         }
383       }
384       else {
385         // Recurse on each array member.
386         foreach ($process as &$subprocess) {
387           $this->substituteMigrationIds($subprocess);
388         }
389       }
390     }
391   }
392
393   /**
394    * @param $id
395    *   The original core plugin ID.
396    *
397    * @return string
398    *   The ID modified to serve as a configuration entity ID.
399    */
400   protected function modifyId($id) {
401     return $this->options['migration-prefix'] . str_replace(':', '_', $id);
402   }
403
404   /**
405    * Rolls back the configured migrations.
406    */
407   public function rollback() {
408     static::$messages = new DrushLogMigrateMessage();
409     $database_state_key = \Drupal::state()->get('migrate.fallback_state_key');
410     $database_state = \Drupal::state()->get($database_state_key);
411     $db_spec = $database_state['database'];
412     $connection = $this->getConnection($db_spec);
413     $version = $this->getLegacyDrupalVersion($connection);
414     $migrations = $this->getMigrations('migrate_drupal_' . $version, $version);
415
416     // Roll back in reverse order.
417     $this->migrationList = array_reverse($migrations);
418
419     foreach ($migrations as $migration) {
420       drush_print(dt('Rolling back @migration', ['@migration' => $migration->id()]));
421       $executable = new MigrateExecutable($migration, static::$messages);
422       // drush_op() provides --simulate support.
423       drush_op([$executable, 'rollback']);
424     }
425   }
426
427   /**
428    * Display any messages being logged to the ID map.
429    *
430    * @param \Drupal\migrate\Event\MigrateIdMapMessageEvent $event
431    *   The message event.
432    */
433   public static function onIdMapMessage(MigrateIdMapMessageEvent $event) {
434     if ($event->getLevel() == MigrationInterface::MESSAGE_NOTICE ||
435         $event->getLevel() == MigrationInterface::MESSAGE_INFORMATIONAL) {
436       $type = 'status';
437     }
438     else {
439       $type = 'error';
440     }
441     $source_id_string = implode(',', $event->getSourceIdValues());
442     $message = t('Source ID @source_id: @message',
443       ['@source_id' => $source_id_string, '@message' => $event->getMessage()]);
444     static::$messages->display($message, $type);
445   }
446
447 }