cc8d804da5e534c9211955a5b11df104730607eb
[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    * Whether the migration is auditable.
159    *
160    * If set to TRUE, the migration's IDs will be audited. This means that, if
161    * the highest destination ID is greater than the highest source ID, a warning
162    * will be displayed that entities might be overwritten.
163    *
164    * @var bool
165    */
166   protected $audit = FALSE;
167
168   /**
169    * These migrations, if run, must be executed before this migration.
170    *
171    * These are different from the configuration dependencies. Migration
172    * dependencies are only used to store relationships between migrations.
173    *
174    * The migration_dependencies value is structured like this:
175    * @code
176    * array(
177    *   'required' => array(
178    *     // An array of migration IDs that must be run before this migration.
179    *   ),
180    *   'optional' => array(
181    *     // An array of migration IDs that, if they exist, must be run before
182    *     // this migration.
183    *   ),
184    * );
185    * @endcode
186    *
187    * @var array
188    */
189   protected $migration_dependencies = [];
190
191   /**
192    * The migration's configuration dependencies.
193    *
194    * These store any dependencies on modules or other configuration (including
195    * other migrations) that must be available before the migration can be
196    * created.
197    *
198    * @see \Drupal\Core\Config\Entity\ConfigDependencyManager
199    *
200    * @var array
201    */
202   protected $dependencies = [];
203
204   /**
205    * The migration plugin manager for loading other migration plugins.
206    *
207    * @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
208    */
209   protected $migrationPluginManager;
210
211   /**
212    * The source plugin manager.
213    *
214    * @var \Drupal\migrate\Plugin\MigratePluginManager
215    */
216   protected $sourcePluginManager;
217
218   /**
219    * Thep process plugin manager.
220    *
221    * @var \Drupal\migrate\Plugin\MigratePluginManager
222    */
223   protected $processPluginManager;
224
225   /**
226    * The destination plugin manager.
227    *
228    * @var \Drupal\migrate\Plugin\MigrateDestinationPluginManager
229    */
230   protected $destinationPluginManager;
231
232   /**
233    * The ID map plugin manager.
234    *
235    * @var \Drupal\migrate\Plugin\MigratePluginManager
236    */
237   protected $idMapPluginManager;
238
239   /**
240    * Labels corresponding to each defined status.
241    *
242    * @var array
243    */
244   protected $statusLabels = [
245     self::STATUS_IDLE => 'Idle',
246     self::STATUS_IMPORTING => 'Importing',
247     self::STATUS_ROLLING_BACK => 'Rolling back',
248     self::STATUS_STOPPING => 'Stopping',
249     self::STATUS_DISABLED => 'Disabled',
250   ];
251
252   /**
253    * Constructs a Migration.
254    *
255    * @param array $configuration
256    *   Plugin configuration.
257    * @param string $plugin_id
258    *   The plugin ID.
259    * @param mixed $plugin_definition
260    *   The plugin definition.
261    * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager
262    *   The migration plugin manager.
263    * @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $source_plugin_manager
264    *   The source migration plugin manager.
265    * @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $process_plugin_manager
266    *   The process migration plugin manager.
267    * @param \Drupal\migrate\Plugin\MigrateDestinationPluginManager $destination_plugin_manager
268    *   The destination migration plugin manager.
269    * @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $idmap_plugin_manager
270    *   The ID map migration plugin manager.
271    */
272   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) {
273     parent::__construct($configuration, $plugin_id, $plugin_definition);
274     $this->migrationPluginManager = $migration_plugin_manager;
275     $this->sourcePluginManager = $source_plugin_manager;
276     $this->processPluginManager = $process_plugin_manager;
277     $this->destinationPluginManager = $destination_plugin_manager;
278     $this->idMapPluginManager = $idmap_plugin_manager;
279
280     foreach (NestedArray::mergeDeep($plugin_definition, $configuration) as $key => $value) {
281       $this->$key = $value;
282     }
283   }
284
285   /**
286    * {@inheritdoc}
287    */
288   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
289     return new static(
290       $configuration,
291       $plugin_id,
292       $plugin_definition,
293       $container->get('plugin.manager.migration'),
294       $container->get('plugin.manager.migrate.source'),
295       $container->get('plugin.manager.migrate.process'),
296       $container->get('plugin.manager.migrate.destination'),
297       $container->get('plugin.manager.migrate.id_map')
298     );
299   }
300
301   /**
302    * {@inheritdoc}
303    */
304   public function id() {
305     return $this->pluginId;
306   }
307
308   /**
309    * {@inheritdoc}
310    */
311   public function label() {
312     return $this->label;
313   }
314
315   /**
316    * Gets any arbitrary property's value.
317    *
318    * @param string $property
319    *   The property to retrieve.
320    *
321    * @return mixed
322    *   The value for that property, or NULL if the property does not exist.
323    *
324    * @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.x. Use
325    *   more specific getters instead.
326    *
327    * @see https://www.drupal.org/node/2873795
328    */
329   public function get($property) {
330     return isset($this->$property) ? $this->$property : NULL;
331   }
332
333   /**
334    * Retrieves the ID map plugin.
335    *
336    * @return \Drupal\migrate\Plugin\MigrateIdMapInterface
337    *   The ID map plugin.
338    */
339   public function getIdMapPlugin() {
340     return $this->idMapPlugin;
341   }
342
343   /**
344    * {@inheritdoc}
345    */
346   public function getSourcePlugin() {
347     if (!isset($this->sourcePlugin)) {
348       $this->sourcePlugin = $this->sourcePluginManager->createInstance($this->source['plugin'], $this->source, $this);
349     }
350     return $this->sourcePlugin;
351   }
352
353   /**
354    * {@inheritdoc}
355    */
356   public function getProcessPlugins(array $process = NULL) {
357     if (!isset($process)) {
358       $process = $this->getProcess();
359     }
360     $index = serialize($process);
361     if (!isset($this->processPlugins[$index])) {
362       $this->processPlugins[$index] = [];
363       foreach ($this->getProcessNormalized($process) as $property => $configurations) {
364         $this->processPlugins[$index][$property] = [];
365         foreach ($configurations as $configuration) {
366           if (isset($configuration['source'])) {
367             $this->processPlugins[$index][$property][] = $this->processPluginManager->createInstance('get', $configuration, $this);
368           }
369           // Get is already handled.
370           if ($configuration['plugin'] != 'get') {
371             $this->processPlugins[$index][$property][] = $this->processPluginManager->createInstance($configuration['plugin'], $configuration, $this);
372           }
373           if (!$this->processPlugins[$index][$property]) {
374             throw new MigrateException("Invalid process configuration for $property");
375           }
376         }
377       }
378     }
379     return $this->processPlugins[$index];
380   }
381
382   /**
383    * Resolve shorthands into a list of plugin configurations.
384    *
385    * @param array $process
386    *   A process configuration array.
387    *
388    * @return array
389    *   The normalized process configuration.
390    */
391   protected function getProcessNormalized(array $process) {
392     $normalized_configurations = [];
393     foreach ($process as $destination => $configuration) {
394       if (is_string($configuration)) {
395         $configuration = [
396           'plugin' => 'get',
397           'source' => $configuration,
398         ];
399       }
400       if (isset($configuration['plugin'])) {
401         $configuration = [$configuration];
402       }
403       $normalized_configurations[$destination] = $configuration;
404     }
405     return $normalized_configurations;
406   }
407
408   /**
409    * {@inheritdoc}
410    */
411   public function getDestinationPlugin($stub_being_requested = FALSE) {
412     if ($stub_being_requested && !empty($this->destination['no_stub'])) {
413       throw new MigrateSkipRowException();
414     }
415     if (!isset($this->destinationPlugin)) {
416       $this->destinationPlugin = $this->destinationPluginManager->createInstance($this->destination['plugin'], $this->destination, $this);
417     }
418     return $this->destinationPlugin;
419   }
420
421   /**
422    * {@inheritdoc}
423    */
424   public function getIdMap() {
425     if (!isset($this->idMapPlugin)) {
426       $configuration = $this->idMap;
427       $plugin = isset($configuration['plugin']) ? $configuration['plugin'] : 'sql';
428       $this->idMapPlugin = $this->idMapPluginManager->createInstance($plugin, $configuration, $this);
429     }
430     return $this->idMapPlugin;
431   }
432
433   /**
434    * {@inheritdoc}
435    */
436   public function checkRequirements() {
437     // Check whether the current migration source and destination plugin
438     // requirements are met or not.
439     if ($this->getSourcePlugin() instanceof RequirementsInterface) {
440       $this->getSourcePlugin()->checkRequirements();
441     }
442     if ($this->getDestinationPlugin() instanceof RequirementsInterface) {
443       $this->getDestinationPlugin()->checkRequirements();
444     }
445
446     if (empty($this->requirements)) {
447       // There are no requirements to check.
448       return;
449     }
450     /** @var \Drupal\migrate\Plugin\MigrationInterface[] $required_migrations */
451     $required_migrations = $this->getMigrationPluginManager()->createInstances($this->requirements);
452
453     $missing_migrations = array_diff($this->requirements, array_keys($required_migrations));
454     // Check if the dependencies are in good shape.
455     foreach ($required_migrations as $migration_id => $required_migration) {
456       if (!$required_migration->allRowsProcessed()) {
457         $missing_migrations[] = $migration_id;
458       }
459     }
460     if ($missing_migrations) {
461       throw new RequirementsException('Missing migrations ' . implode(', ', $missing_migrations) . '.', ['requirements' => $missing_migrations]);
462     }
463   }
464
465   /**
466    * Gets the migration plugin manager.
467    *
468    * @return \Drupal\migrate\Plugin\MigratePluginManager
469    *   The plugin manager.
470    */
471   protected function getMigrationPluginManager() {
472     return $this->migrationPluginManager;
473   }
474
475   /**
476    * {@inheritdoc}
477    */
478   public function setStatus($status) {
479     \Drupal::keyValue('migrate_status')->set($this->id(), $status);
480   }
481
482   /**
483    * {@inheritdoc}
484    */
485   public function getStatus() {
486     return \Drupal::keyValue('migrate_status')->get($this->id(), static::STATUS_IDLE);
487   }
488
489   /**
490    * {@inheritdoc}
491    */
492   public function getStatusLabel() {
493     $status = $this->getStatus();
494     if (isset($this->statusLabels[$status])) {
495       return $this->statusLabels[$status];
496     }
497     else {
498       return '';
499     }
500   }
501
502   /**
503    * {@inheritdoc}
504    */
505   public function getInterruptionResult() {
506     return \Drupal::keyValue('migrate_interruption_result')->get($this->id(), static::RESULT_INCOMPLETE);
507   }
508
509   /**
510    * {@inheritdoc}
511    */
512   public function clearInterruptionResult() {
513     \Drupal::keyValue('migrate_interruption_result')->delete($this->id());
514   }
515
516   /**
517    * {@inheritdoc}
518    */
519   public function interruptMigration($result) {
520     $this->setStatus(MigrationInterface::STATUS_STOPPING);
521     \Drupal::keyValue('migrate_interruption_result')->set($this->id(), $result);
522   }
523
524   /**
525    * {@inheritdoc}
526    */
527   public function allRowsProcessed() {
528     $source_count = $this->getSourcePlugin()->count();
529     // If the source is uncountable, we have no way of knowing if it's
530     // complete, so stipulate that it is.
531     if ($source_count < 0) {
532       return TRUE;
533     }
534     $processed_count = $this->getIdMap()->processedCount();
535     // We don't use == because in some circumstances (like unresolved stubs
536     // being created), the processed count may be higher than the available
537     // source rows.
538     return $source_count <= $processed_count;
539   }
540
541   /**
542    * {@inheritdoc}
543    */
544   public function set($property_name, $value) {
545     if ($property_name == 'source') {
546       // Invalidate the source plugin.
547       unset($this->sourcePlugin);
548     }
549     elseif ($property_name === 'destination') {
550       // Invalidate the destination plugin.
551       unset($this->destinationPlugin);
552     }
553     $this->{$property_name} = $value;
554     return $this;
555   }
556
557
558   /**
559    * {@inheritdoc}
560    */
561   public function getProcess() {
562     return $this->getProcessNormalized($this->process);
563   }
564
565   /**
566    * {@inheritdoc}
567    */
568   public function setProcess(array $process) {
569     $this->process = $process;
570     return $this;
571   }
572
573   /**
574    * {@inheritdoc}
575    */
576   public function setProcessOfProperty($property, $process_of_property) {
577     $this->process[$property] = $process_of_property;
578     return $this;
579   }
580
581   /**
582    * {@inheritdoc}
583    */
584   public function mergeProcessOfProperty($property, array $process_of_property) {
585     // If we already have a process value then merge the incoming process array
586     // otherwise simply set it.
587     $current_process = $this->getProcess();
588     if (isset($current_process[$property])) {
589       $this->process = NestedArray::mergeDeepArray([$current_process, $this->getProcessNormalized([$property => $process_of_property])], TRUE);
590     }
591     else {
592       $this->setProcessOfProperty($property, $process_of_property);
593     }
594
595     return $this;
596   }
597
598   /**
599    * {@inheritdoc}
600    */
601   public function isTrackLastImported() {
602     return $this->trackLastImported;
603   }
604
605   /**
606    * {@inheritdoc}
607    */
608   public function setTrackLastImported($track_last_imported) {
609     $this->trackLastImported = (bool) $track_last_imported;
610     return $this;
611   }
612
613   /**
614    * {@inheritdoc}
615    */
616   public function getMigrationDependencies() {
617     $this->migration_dependencies = ($this->migration_dependencies ?: []) + ['required' => [], 'optional' => []];
618     $this->migration_dependencies['optional'] = array_unique(array_merge($this->migration_dependencies['optional'], $this->findMigrationDependencies($this->process)));
619     return $this->migration_dependencies;
620   }
621
622   /**
623    * Find migration dependencies from the migration and the iterator plugins.
624    *
625    * @param $process
626    * @return array
627    */
628   protected function findMigrationDependencies($process) {
629     $return = [];
630     foreach ($this->getProcessNormalized($process) as $process_pipeline) {
631       foreach ($process_pipeline as $plugin_configuration) {
632         if ($plugin_configuration['plugin'] == 'migration') {
633           $return = array_merge($return, (array) $plugin_configuration['migration']);
634         }
635         if ($plugin_configuration['plugin'] == 'sub_process') {
636           $return = array_merge($return, $this->findMigrationDependencies($plugin_configuration['process']));
637         }
638       }
639     }
640     return $return;
641   }
642
643   /**
644    * {@inheritdoc}
645    */
646   public function getPluginDefinition() {
647     $definition = [];
648     // While normal plugins do not change their definitions on the fly, this
649     // one does so accommodate for that.
650     foreach (parent::getPluginDefinition() as $key => $value) {
651       $definition[$key] = isset($this->$key) ? $this->$key : $value;
652     }
653     return $definition;
654   }
655
656   /**
657    * {@inheritdoc}
658    */
659   public function getDestinationConfiguration() {
660     return $this->destination;
661   }
662
663   /**
664    * {@inheritdoc}
665    */
666   public function getSourceConfiguration() {
667     return $this->source;
668   }
669
670   /**
671    * {@inheritdoc}
672    */
673   public function getTrackLastImported() {
674     return $this->trackLastImported;
675   }
676
677   /**
678    * {@inheritdoc}
679    */
680   public function getDestinationIds() {
681     return $this->destinationIds;
682   }
683
684   /**
685    * {@inheritdoc}
686    */
687   public function getMigrationTags() {
688     return $this->migration_tags;
689   }
690
691   /**
692    * {@inheritdoc}
693    */
694   public function isAuditable() {
695     return (bool) $this->audit;
696   }
697
698 }