Version 1
[yaffs-website] / web / core / modules / migrate / src / Plugin / Migration.php
1 <?php
2
3 namespace Drupal\migrate\Plugin;
4
5 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
6 use Drupal\Core\Plugin\PluginBase;
7 use Drupal\migrate\Exception\RequirementsException;
8 use Drupal\migrate\MigrateException;
9 use Drupal\migrate\MigrateSkipRowException;
10 use Drupal\Component\Utility\NestedArray;
11 use Symfony\Component\DependencyInjection\ContainerInterface;
12
13 /**
14  * Defines the Migration plugin.
15  *
16  * The migration process plugin represents one single migration and acts like a
17  * container for the information about a single migration such as the source,
18  * process and destination plugins.
19  */
20 class Migration extends PluginBase implements MigrationInterface, RequirementsInterface, ContainerFactoryPluginInterface {
21
22   /**
23    * The migration ID (machine name).
24    *
25    * @var string
26    */
27   protected $id;
28
29   /**
30    * The human-readable label for the migration.
31    *
32    * @var string
33    */
34   protected $label;
35
36   /**
37    * The plugin ID for the row.
38    *
39    * @var string
40    */
41   protected $row;
42
43   /**
44    * The source configuration, with at least a 'plugin' key.
45    *
46    * Used to initialize the $sourcePlugin.
47    *
48    * @var array
49    */
50   protected $source;
51
52   /**
53    * The source plugin.
54    *
55    * @var \Drupal\migrate\Plugin\MigrateSourceInterface
56    */
57   protected $sourcePlugin;
58
59   /**
60    * The configuration describing the process plugins.
61    *
62    * This is a strictly internal property and should not returned to calling
63    * code, use getProcess() instead.
64    *
65    * @var array
66    */
67   protected $process = [];
68
69   /**
70    * The cached process plugins.
71    *
72    * @var array
73    */
74   protected $processPlugins = [];
75
76   /**
77    * The destination configuration, with at least a 'plugin' key.
78    *
79    * Used to initialize $destinationPlugin.
80    *
81    * @var array
82    */
83   protected $destination;
84
85   /**
86    * The destination plugin.
87    *
88    * @var \Drupal\migrate\Plugin\MigrateDestinationInterface
89    */
90   protected $destinationPlugin;
91
92   /**
93    * The identifier map data.
94    *
95    * Used to initialize $idMapPlugin.
96    *
97    * @var string
98    */
99   protected $idMap = [];
100
101   /**
102    * The identifier map.
103    *
104    * @var \Drupal\migrate\Plugin\MigrateIdMapInterface
105    */
106   protected $idMapPlugin;
107
108   /**
109    * The source identifiers.
110    *
111    * An array of source identifiers: the keys are the name of the properties,
112    * the values are dependent on the ID map plugin.
113    *
114    * @var array
115    */
116   protected $sourceIds = [];
117
118   /**
119    * The destination identifiers.
120    *
121    * An array of destination identifiers: the keys are the name of the
122    * properties, the values are dependent on the ID map plugin.
123    *
124    * @var array
125    */
126   protected $destinationIds = [];
127
128   /**
129    * Specify value of source_row_status for current map row. Usually set by
130    * MigrateFieldHandler implementations.
131    *
132    * @var int
133    */
134   protected $sourceRowStatus = MigrateIdMapInterface::STATUS_IMPORTED;
135
136   /**
137    * Track time of last import if TRUE.
138    *
139    * @var bool
140    */
141   protected $trackLastImported = FALSE;
142
143   /**
144    * These migrations must be already executed before this migration can run.
145    *
146    * @var array
147    */
148   protected $requirements = [];
149
150   /**
151    * An optional list of tags, used by the plugin manager for filtering.
152    *
153    * @var array
154    */
155   protected $migration_tags = [];
156
157   /**
158    * These migrations, if run, must be executed before this migration.
159    *
160    * These are different from the configuration dependencies. Migration
161    * dependencies are only used to store relationships between migrations.
162    *
163    * The migration_dependencies value is structured like this:
164    * @code
165    * array(
166    *   'required' => array(
167    *     // An array of migration IDs that must be run before this migration.
168    *   ),
169    *   'optional' => array(
170    *     // An array of migration IDs that, if they exist, must be run before
171    *     // this migration.
172    *   ),
173    * );
174    * @endcode
175    *
176    * @var array
177    */
178   protected $migration_dependencies = [];
179
180   /**
181    * The migration's configuration dependencies.
182    *
183    * These store any dependencies on modules or other configuration (including
184    * other migrations) that must be available before the migration can be
185    * created.
186    *
187    * @see \Drupal\Core\Config\Entity\ConfigDependencyManager
188    *
189    * @var array
190    */
191   protected $dependencies = [];
192
193   /**
194    * The migration plugin manager for loading other migration plugins.
195    *
196    * @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
197    */
198   protected $migrationPluginManager;
199
200   /**
201    * The source plugin manager.
202    *
203    * @var \Drupal\migrate\Plugin\MigratePluginManager
204    */
205   protected $sourcePluginManager;
206
207   /**
208    * Thep process plugin manager.
209    *
210    * @var \Drupal\migrate\Plugin\MigratePluginManager
211    */
212   protected $processPluginManager;
213
214   /**
215    * The destination plugin manager.
216    *
217    * @var \Drupal\migrate\Plugin\MigrateDestinationPluginManager
218    */
219   protected $destinationPluginManager;
220
221   /**
222    * The ID map plugin manager.
223    *
224    * @var \Drupal\migrate\Plugin\MigratePluginManager
225    */
226   protected $idMapPluginManager;
227
228   /**
229    * Labels corresponding to each defined status.
230    *
231    * @var array
232    */
233   protected $statusLabels = [
234     self::STATUS_IDLE => 'Idle',
235     self::STATUS_IMPORTING => 'Importing',
236     self::STATUS_ROLLING_BACK => 'Rolling back',
237     self::STATUS_STOPPING => 'Stopping',
238     self::STATUS_DISABLED => 'Disabled',
239   ];
240
241   /**
242    * Constructs a Migration.
243    *
244    * @param array $configuration
245    *   Plugin configuration.
246    * @param string $plugin_id
247    *   The plugin ID.
248    * @param mixed $plugin_definition
249    *   The plugin definition.
250    * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager
251    *   The migration plugin manager.
252    * @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $source_plugin_manager
253    *   The source migration plugin manager.
254    * @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $process_plugin_manager
255    *   The process migration plugin manager.
256    * @param \Drupal\migrate\Plugin\MigrateDestinationPluginManager $destination_plugin_manager
257    *   The destination migration plugin manager.
258    * @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $idmap_plugin_manager
259    *   The ID map migration plugin manager.
260    */
261   public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationPluginManagerInterface $migration_plugin_manager, MigratePluginManagerInterface $source_plugin_manager, MigratePluginManagerInterface $process_plugin_manager, MigrateDestinationPluginManager $destination_plugin_manager, MigratePluginManagerInterface $idmap_plugin_manager) {
262     parent::__construct($configuration, $plugin_id, $plugin_definition);
263     $this->migrationPluginManager = $migration_plugin_manager;
264     $this->sourcePluginManager = $source_plugin_manager;
265     $this->processPluginManager = $process_plugin_manager;
266     $this->destinationPluginManager = $destination_plugin_manager;
267     $this->idMapPluginManager = $idmap_plugin_manager;
268
269     foreach (NestedArray::mergeDeep($plugin_definition, $configuration) as $key => $value) {
270       $this->$key = $value;
271     }
272   }
273
274   /**
275    * {@inheritdoc}
276    */
277   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
278     return new static(
279       $configuration,
280       $plugin_id,
281       $plugin_definition,
282       $container->get('plugin.manager.migration'),
283       $container->get('plugin.manager.migrate.source'),
284       $container->get('plugin.manager.migrate.process'),
285       $container->get('plugin.manager.migrate.destination'),
286       $container->get('plugin.manager.migrate.id_map')
287     );
288   }
289
290   /**
291    * {@inheritdoc}
292    */
293   public function id() {
294     return $this->pluginId;
295   }
296
297   /**
298    * {@inheritdoc}
299    */
300   public function label() {
301     return $this->label;
302   }
303
304   /**
305    * Gets any arbitrary property's value.
306    *
307    * @param string $property
308    *   The property to retrieve.
309    *
310    * @return mixed
311    *   The value for that property, or NULL if the property does not exist.
312    *
313    * @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.x. Use
314    *   more specific getters instead.
315    */
316   public function get($property) {
317     return isset($this->$property) ? $this->$property : NULL;
318   }
319
320   /**
321    * Retrieves the ID map plugin.
322    *
323    * @return \Drupal\migrate\Plugin\MigrateIdMapInterface
324    *   The ID map plugin.
325    */
326   public function getIdMapPlugin() {
327     return $this->idMapPlugin;
328   }
329
330   /**
331    * {@inheritdoc}
332    */
333   public function getSourcePlugin() {
334     if (!isset($this->sourcePlugin)) {
335       $this->sourcePlugin = $this->sourcePluginManager->createInstance($this->source['plugin'], $this->source, $this);
336     }
337     return $this->sourcePlugin;
338   }
339
340   /**
341    * {@inheritdoc}
342    */
343   public function getProcessPlugins(array $process = NULL) {
344     if (!isset($process)) {
345       $process = $this->getProcess();
346     }
347     $index = serialize($process);
348     if (!isset($this->processPlugins[$index])) {
349       $this->processPlugins[$index] = [];
350       foreach ($this->getProcessNormalized($process) as $property => $configurations) {
351         $this->processPlugins[$index][$property] = [];
352         foreach ($configurations as $configuration) {
353           if (isset($configuration['source'])) {
354             $this->processPlugins[$index][$property][] = $this->processPluginManager->createInstance('get', $configuration, $this);
355           }
356           // Get is already handled.
357           if ($configuration['plugin'] != 'get') {
358             $this->processPlugins[$index][$property][] = $this->processPluginManager->createInstance($configuration['plugin'], $configuration, $this);
359           }
360           if (!$this->processPlugins[$index][$property]) {
361             throw new MigrateException("Invalid process configuration for $property");
362           }
363         }
364       }
365     }
366     return $this->processPlugins[$index];
367   }
368
369   /**
370    * Resolve shorthands into a list of plugin configurations.
371    *
372    * @param array $process
373    *   A process configuration array.
374    *
375    * @return array
376    *   The normalized process configuration.
377    */
378   protected function getProcessNormalized(array $process) {
379     $normalized_configurations = [];
380     foreach ($process as $destination => $configuration) {
381       if (is_string($configuration)) {
382         $configuration = [
383           'plugin' => 'get',
384           'source' => $configuration,
385         ];
386       }
387       if (isset($configuration['plugin'])) {
388         $configuration = [$configuration];
389       }
390       $normalized_configurations[$destination] = $configuration;
391     }
392     return $normalized_configurations;
393   }
394
395   /**
396    * {@inheritdoc}
397    */
398   public function getDestinationPlugin($stub_being_requested = FALSE) {
399     if ($stub_being_requested && !empty($this->destination['no_stub'])) {
400       throw new MigrateSkipRowException();
401     }
402     if (!isset($this->destinationPlugin)) {
403       $this->destinationPlugin = $this->destinationPluginManager->createInstance($this->destination['plugin'], $this->destination, $this);
404     }
405     return $this->destinationPlugin;
406   }
407
408   /**
409    * {@inheritdoc}
410    */
411   public function getIdMap() {
412     if (!isset($this->idMapPlugin)) {
413       $configuration = $this->idMap;
414       $plugin = isset($configuration['plugin']) ? $configuration['plugin'] : 'sql';
415       $this->idMapPlugin = $this->idMapPluginManager->createInstance($plugin, $configuration, $this);
416     }
417     return $this->idMapPlugin;
418   }
419
420   /**
421    * {@inheritdoc}
422    */
423   public function checkRequirements() {
424     // Check whether the current migration source and destination plugin
425     // requirements are met or not.
426     if ($this->getSourcePlugin() instanceof RequirementsInterface) {
427       $this->getSourcePlugin()->checkRequirements();
428     }
429     if ($this->getDestinationPlugin() instanceof RequirementsInterface) {
430       $this->getDestinationPlugin()->checkRequirements();
431     }
432
433     if (empty($this->requirements)) {
434       // There are no requirements to check.
435       return;
436     }
437     /** @var \Drupal\migrate\Plugin\MigrationInterface[] $required_migrations */
438     $required_migrations = $this->getMigrationPluginManager()->createInstances($this->requirements);
439
440     $missing_migrations = array_diff($this->requirements, array_keys($required_migrations));
441     // Check if the dependencies are in good shape.
442     foreach ($required_migrations as $migration_id => $required_migration) {
443       if (!$required_migration->allRowsProcessed()) {
444         $missing_migrations[] = $migration_id;
445       }
446     }
447     if ($missing_migrations) {
448       throw new RequirementsException('Missing migrations ' . implode(', ', $missing_migrations) . '.', ['requirements' => $missing_migrations]);
449     }
450   }
451
452   /**
453    * Gets the migration plugin manager.
454    *
455    * @return \Drupal\migrate\Plugin\MigratePluginManager
456    *   The plugin manager.
457    */
458   protected function getMigrationPluginManager() {
459     return $this->migrationPluginManager;
460   }
461
462   /**
463    * {@inheritdoc}
464    */
465   public function setStatus($status) {
466     \Drupal::keyValue('migrate_status')->set($this->id(), $status);
467   }
468
469   /**
470    * {@inheritdoc}
471    */
472   public function getStatus() {
473     return \Drupal::keyValue('migrate_status')->get($this->id(), static::STATUS_IDLE);
474   }
475
476   /**
477    * {@inheritdoc}
478    */
479   public function getStatusLabel() {
480     $status = $this->getStatus();
481     if (isset($this->statusLabels[$status])) {
482       return $this->statusLabels[$status];
483     }
484     else {
485       return '';
486     }
487   }
488
489   /**
490    * {@inheritdoc}
491    */
492   public function getInterruptionResult() {
493     return \Drupal::keyValue('migrate_interruption_result')->get($this->id(), static::RESULT_INCOMPLETE);
494   }
495
496   /**
497    * {@inheritdoc}
498    */
499   public function clearInterruptionResult() {
500     \Drupal::keyValue('migrate_interruption_result')->delete($this->id());
501   }
502
503   /**
504    * {@inheritdoc}
505    */
506   public function interruptMigration($result) {
507     $this->setStatus(MigrationInterface::STATUS_STOPPING);
508     \Drupal::keyValue('migrate_interruption_result')->set($this->id(), $result);
509   }
510
511   /**
512    * {@inheritdoc}
513    */
514   public function allRowsProcessed() {
515     $source_count = $this->getSourcePlugin()->count();
516     // If the source is uncountable, we have no way of knowing if it's
517     // complete, so stipulate that it is.
518     if ($source_count < 0) {
519       return TRUE;
520     }
521     $processed_count = $this->getIdMap()->processedCount();
522     // We don't use == because in some circumstances (like unresolved stubs
523     // being created), the processed count may be higher than the available
524     // source rows.
525     return $source_count <= $processed_count;
526   }
527
528   /**
529    * {@inheritdoc}
530    */
531   public function set($property_name, $value) {
532     if ($property_name == 'source') {
533       // Invalidate the source plugin.
534       unset($this->sourcePlugin);
535     }
536     elseif ($property_name === 'destination') {
537       // Invalidate the destination plugin.
538       unset($this->destinationPlugin);
539     }
540     $this->{$property_name} = $value;
541     return $this;
542   }
543
544
545   /**
546    * {@inheritdoc}
547    */
548   public function getProcess() {
549     return $this->getProcessNormalized($this->process);
550   }
551
552   /**
553    * {@inheritdoc}
554    */
555   public function setProcess(array $process) {
556     $this->process = $process;
557     return $this;
558   }
559
560   /**
561    * {@inheritdoc}
562    */
563   public function setProcessOfProperty($property, $process_of_property) {
564     $this->process[$property] = $process_of_property;
565     return $this;
566   }
567
568   /**
569    * {@inheritdoc}
570    */
571   public function mergeProcessOfProperty($property, array $process_of_property) {
572     // If we already have a process value then merge the incoming process array
573     //otherwise simply set it.
574     $current_process = $this->getProcess();
575     if (isset($current_process[$property])) {
576       $this->process = NestedArray::mergeDeepArray([$current_process, $this->getProcessNormalized([$property => $process_of_property])], TRUE);
577     }
578     else {
579       $this->setProcessOfProperty($property, $process_of_property);
580     }
581
582     return $this;
583   }
584
585   /**
586    * {@inheritdoc}
587    */
588   public function isTrackLastImported() {
589     return $this->trackLastImported;
590   }
591
592   /**
593    * {@inheritdoc}
594    */
595   public function setTrackLastImported($track_last_imported) {
596     $this->trackLastImported = (bool) $track_last_imported;
597     return $this;
598   }
599
600   /**
601    * {@inheritdoc}
602    */
603   public function getMigrationDependencies() {
604     $this->migration_dependencies = ($this->migration_dependencies ?: []) + ['required' => [], 'optional' => []];
605     $this->migration_dependencies['optional'] = array_unique(array_merge($this->migration_dependencies['optional'], $this->findMigrationDependencies($this->process)));
606     return $this->migration_dependencies;
607   }
608
609   /**
610    * Find migration dependencies from the migration and the iterator plugins.
611    *
612    * @param $process
613    * @return array
614    */
615   protected function findMigrationDependencies($process) {
616     $return = [];
617     foreach ($this->getProcessNormalized($process) as $process_pipeline) {
618       foreach ($process_pipeline as $plugin_configuration) {
619         if ($plugin_configuration['plugin'] == 'migration') {
620           $return = array_merge($return, (array) $plugin_configuration['migration']);
621         }
622         if ($plugin_configuration['plugin'] == 'iterator') {
623           $return = array_merge($return, $this->findMigrationDependencies($plugin_configuration['process']));
624         }
625       }
626     }
627     return $return;
628   }
629
630   /**
631    * {@inheritdoc}
632    */
633   public function getPluginDefinition() {
634     $definition = [];
635     // While normal plugins do not change their definitions on the fly, this
636     // one does so accommodate for that.
637     foreach (parent::getPluginDefinition() as $key => $value) {
638       $definition[$key] = isset($this->$key) ? $this->$key : $value;
639     }
640     return $definition;
641   }
642
643   /**
644    * {@inheritdoc}
645    */
646   public function getDestinationConfiguration() {
647     return $this->destination;
648   }
649
650   /**
651    * {@inheritdoc}
652    */
653   public function getSourceConfiguration() {
654     return $this->source;
655   }
656
657   /**
658    * {@inheritdoc}
659    */
660   public function getTrackLastImported() {
661     return $this->trackLastImported;
662   }
663
664   /**
665    * {@inheritdoc}
666    */
667   public function getDestinationIds() {
668     return $this->destinationIds;
669   }
670
671   /**
672    * {@inheritdoc}
673    */
674   public function getMigrationTags() {
675     return $this->migration_tags;
676   }
677
678 }