Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / web / core / modules / migrate / src / Plugin / MigrationPluginManager.php
1 <?php
2
3 namespace Drupal\migrate\Plugin;
4
5 use Drupal\Component\Graph\Graph;
6 use Drupal\Component\Plugin\PluginBase;
7 use Drupal\Core\Cache\CacheBackendInterface;
8 use Drupal\Core\Extension\ModuleHandlerInterface;
9 use Drupal\Core\Language\LanguageManagerInterface;
10 use Drupal\Core\Plugin\DefaultPluginManager;
11 use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
12 use Drupal\migrate\Plugin\Discovery\ProviderFilterDecorator;
13 use Drupal\Core\Plugin\Discovery\YamlDirectoryDiscovery;
14 use Drupal\Core\Plugin\Factory\ContainerFactory;
15 use Drupal\migrate\MigrateBuildDependencyInterface;
16
17 /**
18  * Plugin manager for migration plugins.
19  */
20 class MigrationPluginManager extends DefaultPluginManager implements MigrationPluginManagerInterface, MigrateBuildDependencyInterface {
21
22   /**
23    * Provides default values for migrations.
24    *
25    * @var array
26    */
27   protected $defaults = [
28     'class' => '\Drupal\migrate\Plugin\Migration',
29   ];
30
31   /**
32    * The interface the plugins should implement.
33    *
34    * @var string
35    */
36   protected $pluginInterface = 'Drupal\migrate\Plugin\MigrationInterface';
37
38   /**
39    * The module handler.
40    *
41    * @var \Drupal\Core\Extension\ModuleHandlerInterface
42    */
43   protected $moduleHandler;
44
45   /**
46    * Construct a migration plugin manager.
47    *
48    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
49    *   The module handler.
50    * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
51    *   The cache backend for the definitions.
52    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
53    *   The language manager.
54    */
55   public function __construct(ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager) {
56     $this->factory = new ContainerFactory($this, $this->pluginInterface);
57     $this->alterInfo('migration_plugins');
58     $this->setCacheBackend($cache_backend, 'migration_plugins', ['migration_plugins']);
59     $this->moduleHandler = $module_handler;
60   }
61
62   /**
63    * Gets the plugin discovery.
64    *
65    * This method overrides DefaultPluginManager::getDiscovery() in order to
66    * search for migration configurations in the MODULENAME/migrations and
67    * MODULENAME/migration_templates directories. Throws a deprecation notice if
68    * the MODULENAME/migration_templates directory exists.
69    */
70   protected function getDiscovery() {
71     if (!isset($this->discovery)) {
72       $directories = array_map(function ($directory) {
73         // Check for use of the @deprecated /migration_templates directory.
74         // @todo Remove use of /migration_templates in Drupal 9.0.0.
75         if (is_dir($directory . '/migration_templates')) {
76           @trigger_error('Use of the /migration_templates directory to store migration configuration files is deprecated in Drupal 8.1.0 and will be removed before Drupal 9.0.0. See https://www.drupal.org/node/2920988.', E_USER_DEPRECATED);
77         }
78         // But still accept configurations found in /migration_templates.
79         return [$directory . '/migration_templates', $directory . '/migrations'];
80       }, $this->moduleHandler->getModuleDirectories());
81
82       $yaml_discovery = new YamlDirectoryDiscovery($directories, 'migrate');
83       // This gets rid of migrations which try to use a non-existent source
84       // plugin. The common case for this is if the source plugin has, or
85       // specifies, a non-existent provider.
86       $only_with_source_discovery  = new NoSourcePluginDecorator($yaml_discovery);
87       // This gets rid of migrations with explicit providers set if one of the
88       // providers do not exist before we try to use a potentially non-existing
89       // deriver. This is a rare case.
90       $filtered_discovery = new ProviderFilterDecorator($only_with_source_discovery, [$this->moduleHandler, 'moduleExists']);
91       $this->discovery = new ContainerDerivativeDiscoveryDecorator($filtered_discovery);
92     }
93     return $this->discovery;
94   }
95
96   /**
97    * {@inheritdoc}
98    */
99   public function createInstance($plugin_id, array $configuration = []) {
100     $instances = $this->createInstances([$plugin_id], [$plugin_id => $configuration]);
101     return reset($instances);
102   }
103
104   /**
105    * {@inheritdoc}
106    */
107   public function createInstances($migration_id, array $configuration = []) {
108     if (empty($migration_id)) {
109       $migration_id = array_keys($this->getDefinitions());
110     }
111
112     $factory = $this->getFactory();
113     $migration_ids = (array) $migration_id;
114     $plugin_ids = $this->expandPluginIds($migration_ids);
115
116     $instances = [];
117     foreach ($plugin_ids as $plugin_id) {
118       $instances[$plugin_id] = $factory->createInstance($plugin_id, isset($configuration[$plugin_id]) ? $configuration[$plugin_id] : []);
119     }
120
121     foreach ($instances as $migration) {
122       $migration->set('migration_dependencies', array_map([$this, 'expandPluginIds'], $migration->getMigrationDependencies()));
123     }
124
125     // Sort the migrations based on their dependencies.
126     return $this->buildDependencyMigration($instances, []);
127   }
128
129   /**
130    * {@inheritdoc}
131    */
132   public function createInstancesByTag($tag) {
133     $migrations = array_filter($this->getDefinitions(), function ($migration) use ($tag) {
134       return !empty($migration['migration_tags']) && in_array($tag, $migration['migration_tags']);
135     });
136     return $this->createInstances(array_keys($migrations));
137   }
138
139   /**
140    * Expand derivative migration dependencies.
141    *
142    * We need to expand any derivative migrations. Derivative migrations are
143    * calculated by migration derivers such as D6NodeDeriver. This allows
144    * migrations to depend on the base id and then have a dependency on all
145    * derivative migrations. For example, d6_comment depends on d6_node but after
146    * we've expanded the dependencies it will depend on d6_node:page,
147    * d6_node:story and so on, for other derivative migrations.
148    *
149    * @return array
150    *   An array of expanded plugin ids.
151    */
152   protected function expandPluginIds(array $migration_ids) {
153     $plugin_ids = [];
154     foreach ($migration_ids as $id) {
155       $plugin_ids += preg_grep('/^' . preg_quote($id, '/') . PluginBase::DERIVATIVE_SEPARATOR . '/', array_keys($this->getDefinitions()));
156       if ($this->hasDefinition($id)) {
157         $plugin_ids[] = $id;
158       }
159     }
160     return $plugin_ids;
161   }
162
163
164   /**
165    * {@inheritdoc}
166    */
167   public function buildDependencyMigration(array $migrations, array $dynamic_ids) {
168     // Migration dependencies can be optional or required. If an optional
169     // dependency does not run, the current migration is still OK to go. Both
170     // optional and required dependencies (if run at all) must run before the
171     // current migration.
172     $dependency_graph = [];
173     $required_dependency_graph = [];
174     $have_optional = FALSE;
175     foreach ($migrations as $migration) {
176       /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
177       $id = $migration->id();
178       $requirements[$id] = [];
179       $dependency_graph[$id]['edges'] = [];
180       $migration_dependencies = $migration->getMigrationDependencies();
181
182       if (isset($migration_dependencies['required'])) {
183         foreach ($migration_dependencies['required'] as $dependency) {
184           if (!isset($dynamic_ids[$dependency])) {
185             $this->addDependency($required_dependency_graph, $id, $dependency, $dynamic_ids);
186           }
187           $this->addDependency($dependency_graph, $id, $dependency, $dynamic_ids);
188         }
189       }
190       if (!empty($migration_dependencies['optional'])) {
191         foreach ($migration_dependencies['optional'] as $dependency) {
192           $this->addDependency($dependency_graph, $id, $dependency, $dynamic_ids);
193         }
194         $have_optional = TRUE;
195       }
196     }
197     $dependency_graph = (new Graph($dependency_graph))->searchAndSort();
198     if ($have_optional) {
199       $required_dependency_graph = (new Graph($required_dependency_graph))->searchAndSort();
200     }
201     else {
202       $required_dependency_graph = $dependency_graph;
203     }
204     $weights = [];
205     foreach ($migrations as $migration_id => $migration) {
206       // Populate a weights array to use with array_multisort() later.
207       $weights[] = $dependency_graph[$migration_id]['weight'];
208       if (!empty($required_dependency_graph[$migration_id]['paths'])) {
209         $migration->set('requirements', $required_dependency_graph[$migration_id]['paths']);
210       }
211     }
212     array_multisort($weights, SORT_DESC, SORT_NUMERIC, $migrations);
213
214     return $migrations;
215   }
216
217   /**
218    * Add one or more dependencies to a graph.
219    *
220    * @param array $graph
221    *   The graph so far, passed by reference.
222    * @param int $id
223    *   The migration ID.
224    * @param string $dependency
225    *   The dependency string.
226    * @param array $dynamic_ids
227    *   The dynamic ID mapping.
228    */
229   protected function addDependency(array &$graph, $id, $dependency, $dynamic_ids) {
230     $dependencies = isset($dynamic_ids[$dependency]) ? $dynamic_ids[$dependency] : [$dependency];
231     if (!isset($graph[$id]['edges'])) {
232       $graph[$id]['edges'] = [];
233     }
234     $graph[$id]['edges'] += array_combine($dependencies, $dependencies);
235   }
236
237   /**
238    * {@inheritdoc}
239    */
240   public function createStubMigration(array $definition) {
241     $id = isset($definition['id']) ? $definition['id'] : uniqid();
242     return Migration::create(\Drupal::getContainer(), [], $id, $definition);
243   }
244
245   /**
246    * Finds plugin definitions.
247    *
248    * @return array
249    *   List of definitions to store in cache.
250    *
251    * @todo This is a temporary solution to the fact that migration source
252    *   plugins have more than one provider. This functionality will be moved to
253    *   core in https://www.drupal.org/node/2786355.
254    */
255   protected function findDefinitions() {
256     $definitions = $this->getDiscovery()->getDefinitions();
257     foreach ($definitions as $plugin_id => &$definition) {
258       $this->processDefinition($definition, $plugin_id);
259     }
260     $this->alterDefinitions($definitions);
261     return ProviderFilterDecorator::filterDefinitions($definitions, function ($provider) {
262       return $this->providerExists($provider);
263     });
264   }
265
266 }