Pull merge.
[yaffs-website] / web / core / modules / migrate / src / Plugin / migrate / process / MigrationLookup.php
1 <?php
2
3 namespace Drupal\migrate\Plugin\migrate\process;
4
5 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
6 use Drupal\migrate\MigrateSkipProcessException;
7 use Drupal\migrate\Plugin\MigratePluginManagerInterface;
8 use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
9 use Drupal\migrate\Plugin\MigrateIdMapInterface;
10 use Drupal\migrate\ProcessPluginBase;
11 use Drupal\migrate\Plugin\MigrationInterface;
12 use Drupal\migrate\MigrateExecutableInterface;
13 use Drupal\migrate\Row;
14 use Symfony\Component\DependencyInjection\ContainerInterface;
15
16 /**
17  * Looks up the value of a property based on a previous migration.
18  *
19  * It is important to maintain relationships among content coming from the
20  * source site. For example, on the source site, a given user account may
21  * have an ID of 123, but the Drupal user account created from it may have
22  * a uid of 456. The migration process maintains the relationships between
23  * source and destination identifiers in map tables, and this information
24  * is leveraged by the migration_lookup process plugin.
25  *
26  * Available configuration keys
27  * - migration: A single migration ID, or an array of migration IDs.
28  * - source_ids: (optional) An array keyed by migration IDs with values that are
29  *   a list of source properties.
30  * - stub_id: (optional) Identifies the migration which will be used to create
31  *   any stub entities.
32  * - no_stub: (optional) Prevents the creation of a stub entity when no
33  *   relationship is found in the migration map.
34  *
35  * Examples:
36  *
37  * Consider a node migration, where you want to maintain authorship. Let's
38  * assume that users are previously migrated in a migration named 'users'. The
39  * 'users' migration saved the mapping between the source and destination IDs in
40  * a map table. The node migration example below maps the node 'uid' property so
41  * that we first take the source 'author' value and then do a lookup for the
42  * corresponding Drupal user ID from the map table.
43  * @code
44  * process:
45  *   uid:
46  *     plugin: migration_lookup
47  *     migration: users
48  *     source: author
49  * @endcode
50  *
51  * The value of 'migration' can be a list of migration IDs. When using multiple
52  * migrations it is possible each use different source identifiers. In this
53  * case one can use source_ids which is an array keyed by the migration IDs
54  * and the value is a list of source properties. See example below.
55  * @code
56  * process:
57  *   uid:
58  *     plugin: migration_lookup
59  *       migration:
60  *         - users
61  *         - members
62  *       source_ids:
63  *         users:
64  *           - author
65  *         members:
66  *           - id
67  * @endcode
68  *
69  * If the migration_lookup plugin does not find the source ID in the migration
70  * map it will create a stub entity for the relationship to use. This stub is
71  * generated by the migration provided. In the case of multiple migrations the
72  * first value of the migration list will be used, but you can select the
73  * migration you wish to use by using the stub_id configuration key. The example
74  * below uses 'members' migration to create stub entities.
75  * @code
76  * process:
77  *   uid:
78  *     plugin: migration_lookup
79  *     migration:
80  *       - users
81  *       - members
82  *     stub_id: members
83  * @endcode
84  *
85  * To prevent the creation of a stub entity when no relationship is found in the
86  * migration map, 'no_stub' configuration can be used as shown below.
87  * @code
88  * process:
89  *   uid:
90  *     plugin: migration_lookup
91  *     migration: users
92  *     no_stub: true
93  *     source: author
94  * @endcode
95  *
96  * @see \Drupal\migrate\Plugin\MigrateProcessInterface
97  *
98  * @MigrateProcessPlugin(
99  *   id = "migration_lookup"
100  * )
101  */
102 class MigrationLookup extends ProcessPluginBase implements ContainerFactoryPluginInterface {
103
104   /**
105    * The process plugin manager.
106    *
107    * @var \Drupal\migrate\Plugin\MigratePluginManager
108    */
109   protected $processPluginManager;
110
111   /**
112    * The migration plugin manager.
113    *
114    * @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
115    */
116   protected $migrationPluginManager;
117
118   /**
119    * The migration to be executed.
120    *
121    * @var \Drupal\migrate\Plugin\MigrationInterface
122    */
123   protected $migration;
124
125   /**
126    * {@inheritdoc}
127    */
128   public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, MigrationPluginManagerInterface $migration_plugin_manager, MigratePluginManagerInterface $process_plugin_manager) {
129     parent::__construct($configuration, $plugin_id, $plugin_definition);
130     $this->migrationPluginManager = $migration_plugin_manager;
131     $this->migration = $migration;
132     $this->processPluginManager = $process_plugin_manager;
133   }
134
135   /**
136    * {@inheritdoc}
137    */
138   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
139     return new static(
140       $configuration,
141       $plugin_id,
142       $plugin_definition,
143       $migration,
144       $container->get('plugin.manager.migration'),
145       $container->get('plugin.manager.migrate.process')
146     );
147   }
148
149   /**
150    * {@inheritdoc}
151    */
152   public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
153     $migration_ids = $this->configuration['migration'];
154     if (!is_array($migration_ids)) {
155       $migration_ids = [$migration_ids];
156     }
157     $self = FALSE;
158     /** @var \Drupal\migrate\Plugin\MigrationInterface[] $migrations */
159     $destination_ids = NULL;
160     $source_id_values = [];
161     $migrations = $this->migrationPluginManager->createInstances($migration_ids);
162     foreach ($migrations as $migration_id => $migration) {
163       if ($migration_id == $this->migration->id()) {
164         $self = TRUE;
165       }
166       if (isset($this->configuration['source_ids'][$migration_id])) {
167         $configuration = ['source' => $this->configuration['source_ids'][$migration_id]];
168         $value = $this->processPluginManager
169           ->createInstance('get', $configuration, $this->migration)
170           ->transform(NULL, $migrate_executable, $row, $destination_property);
171       }
172       if (!is_array($value)) {
173         $value = [$value];
174       }
175       $this->skipOnEmpty($value);
176       $source_id_values[$migration_id] = $value;
177       // Break out of the loop as soon as a destination ID is found.
178       if ($destination_ids = $migration->getIdMap()->lookupDestinationId($source_id_values[$migration_id])) {
179         break;
180       }
181     }
182
183     if (!$destination_ids && !empty($this->configuration['no_stub'])) {
184       return NULL;
185     }
186
187     if (!$destination_ids && ($self || isset($this->configuration['stub_id']) || count($migrations) == 1)) {
188       // If the lookup didn't succeed, figure out which migration will do the
189       // stubbing.
190       if ($self) {
191         $migration = $this->migration;
192       }
193       elseif (isset($this->configuration['stub_id'])) {
194         $migration = $migrations[$this->configuration['stub_id']];
195       }
196       else {
197         $migration = reset($migrations);
198       }
199       $destination_plugin = $migration->getDestinationPlugin(TRUE);
200       // Only keep the process necessary to produce the destination ID.
201       $process = $migration->getProcess();
202
203       // We already have the source ID values but need to key them for the Row
204       // constructor.
205       $source_ids = $migration->getSourcePlugin()->getIds();
206       $values = [];
207       foreach (array_keys($source_ids) as $index => $source_id) {
208         $values[$source_id] = $source_id_values[$migration->id()][$index];
209       }
210
211       $stub_row = $this->createStubRow($values + $migration->getSourceConfiguration(), $source_ids);
212
213       // Do a normal migration with the stub row.
214       $migrate_executable->processRow($stub_row, $process);
215       $destination_ids = [];
216       $id_map = $migration->getIdMap();
217       try {
218         $destination_ids = $destination_plugin->import($stub_row);
219       }
220       catch (\Exception $e) {
221         $id_map->saveMessage($stub_row->getSourceIdValues(), $e->getMessage());
222       }
223
224       if ($destination_ids) {
225         $id_map->saveIdMapping($stub_row, $destination_ids, MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
226       }
227     }
228     if ($destination_ids) {
229       if (count($destination_ids) == 1) {
230         return reset($destination_ids);
231       }
232       else {
233         return $destination_ids;
234       }
235     }
236   }
237
238   /**
239    * Skips the migration process entirely if the value is FALSE.
240    *
241    * @param array $value
242    *   The incoming value to transform.
243    *
244    * @throws \Drupal\migrate\MigrateSkipProcessException
245    */
246   protected function skipOnEmpty(array $value) {
247     if (!array_filter($value)) {
248       throw new MigrateSkipProcessException();
249     }
250   }
251
252   /**
253    * Create a stub row source for later import as stub data.
254    *
255    * This simple wrapper of the Row constructor allows sub-classing plugins to
256    * have more control over the row.
257    *
258    * @param array $values
259    *   An array of values to add as properties on the object.
260    * @param array $source_ids
261    *   An array containing the IDs of the source using the keys as the field
262    *   names.
263    *
264    * @return \Drupal\migrate\Row
265    *   The stub row.
266    */
267   protected function createStubRow(array $values, array $source_ids) {
268     return new Row($values, $source_ids, TRUE);
269   }
270
271 }