8d9fd8f0a10411049948e31b5dbc4a22cefc3ff1
[yaffs-website] / web / core / modules / migrate_drupal_ui / src / Batch / MigrateUpgradeImportBatch.php
1 <?php
2
3 namespace Drupal\migrate_drupal_ui\Batch;
4
5 use Drupal\Core\Link;
6 use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
7 use Drupal\Core\StringTranslation\TranslatableMarkup;
8 use Drupal\Core\Url;
9 use Drupal\migrate\Plugin\MigrationInterface;
10 use Drupal\migrate\Event\MigrateEvents;
11 use Drupal\migrate\Event\MigrateIdMapMessageEvent;
12 use Drupal\migrate\Event\MigrateMapDeleteEvent;
13 use Drupal\migrate\Event\MigrateMapSaveEvent;
14 use Drupal\migrate\Event\MigratePostRowSaveEvent;
15 use Drupal\migrate\Event\MigrateRowDeleteEvent;
16 use Drupal\migrate\MigrateExecutable;
17
18 /**
19  * Runs a single migration batch.
20  */
21 class MigrateUpgradeImportBatch {
22
23   /**
24    * Maximum number of previous messages to display.
25    */
26   const MESSAGE_LENGTH = 20;
27
28   /**
29    * The processed items for one batch of a given migration.
30    *
31    * @var int
32    */
33   protected static $numProcessed = 0;
34
35   /**
36    * Ensure we only add the listeners once per request.
37    *
38    * @var bool
39    */
40   protected static $listenersAdded = FALSE;
41
42   /**
43    * The maximum length in seconds to allow processing in a request.
44    *
45    * @see self::run()
46    *
47    * @var int
48    */
49   protected static $maxExecTime;
50
51   /**
52    * MigrateMessage instance to capture messages during the migration process.
53    *
54    * @var \Drupal\migrate_drupal_ui\Batch\MigrateMessageCapture
55    */
56   protected static $messages;
57
58   /**
59    * Runs a single migrate batch import.
60    *
61    * @param int[] $initial_ids
62    *   The full set of migration IDs to import.
63    * @param array $config
64    *   An array of additional configuration from the form.
65    * @param array $context
66    *   The batch context.
67    */
68   public static function run($initial_ids, $config, &$context) {
69     if (!static::$listenersAdded) {
70       $event_dispatcher = \Drupal::service('event_dispatcher');
71       $event_dispatcher->addListener(MigrateEvents::POST_ROW_SAVE, [static::class, 'onPostRowSave']);
72       $event_dispatcher->addListener(MigrateEvents::MAP_SAVE, [static::class, 'onMapSave']);
73       $event_dispatcher->addListener(MigrateEvents::IDMAP_MESSAGE, [static::class, 'onIdMapMessage']);
74
75       static::$maxExecTime = ini_get('max_execution_time');
76       if (static::$maxExecTime <= 0) {
77         static::$maxExecTime = 60;
78       }
79       // Set an arbitrary threshold of 3 seconds (e.g., if max_execution_time is
80       // 45 seconds, we will quit at 42 seconds so a slow item or cleanup
81       // overhead don't put us over 45).
82       static::$maxExecTime -= 3;
83       static::$listenersAdded = TRUE;
84     }
85     if (!isset($context['sandbox']['migration_ids'])) {
86       $context['sandbox']['max'] = count($initial_ids);
87       $context['sandbox']['current'] = 1;
88       // Total number processed for this migration.
89       $context['sandbox']['num_processed'] = 0;
90       // migration_ids will be the list of IDs remaining to run.
91       $context['sandbox']['migration_ids'] = $initial_ids;
92       $context['sandbox']['messages'] = [];
93       $context['results']['failures'] = 0;
94       $context['results']['successes'] = 0;
95     }
96
97     // Number processed in this batch.
98     static::$numProcessed = 0;
99
100     $migration_id = reset($context['sandbox']['migration_ids']);
101     $definition = \Drupal::service('plugin.manager.migration')->getDefinition($migration_id);
102     $configuration = [];
103
104     // @todo Find a way to avoid this in https://www.drupal.org/node/2804611.
105     if ($definition['destination']['plugin'] === 'entity:file') {
106       // Make sure we have a single trailing slash.
107       $configuration['source']['constants']['source_base_path'] = rtrim($config['source_base_path'], '/') . '/';
108     }
109
110     /** @var \Drupal\migrate\Plugin\Migration $migration */
111     $migration = \Drupal::service('plugin.manager.migration')->createInstance($migration_id, $configuration);
112
113     if ($migration) {
114       static::$messages = new MigrateMessageCapture();
115       $executable = new MigrateExecutable($migration, static::$messages);
116
117       $migration_name = $migration->label() ? $migration->label() : $migration_id;
118
119       try {
120         $migration_status = $executable->import();
121       }
122       catch (\Exception $e) {
123         \Drupal::logger('migrate_drupal_ui')->error($e->getMessage());
124         $migration_status = MigrationInterface::RESULT_FAILED;
125       }
126
127       switch ($migration_status) {
128         case MigrationInterface::RESULT_COMPLETED:
129           // Store the number processed in the sandbox.
130           $context['sandbox']['num_processed'] += static::$numProcessed;
131           $message = new PluralTranslatableMarkup(
132             $context['sandbox']['num_processed'], 'Upgraded @migration (processed 1 item total)', 'Upgraded @migration (processed @count items total)',
133             ['@migration' => $migration_name]);
134           $context['sandbox']['messages'][] = (string) $message;
135           \Drupal::logger('migrate_drupal_ui')->notice($message);
136           $context['sandbox']['num_processed'] = 0;
137           $context['results']['successes']++;
138           break;
139
140         case MigrationInterface::RESULT_INCOMPLETE:
141           $context['sandbox']['messages'][] = (string) new PluralTranslatableMarkup(
142             static::$numProcessed, 'Continuing with @migration (processed 1 item)', 'Continuing with @migration (processed @count items)',
143             ['@migration' => $migration_name]);
144           $context['sandbox']['num_processed'] += static::$numProcessed;
145           break;
146
147         case MigrationInterface::RESULT_STOPPED:
148           $context['sandbox']['messages'][] = (string) new TranslatableMarkup('Operation stopped by request');
149           break;
150
151         case MigrationInterface::RESULT_FAILED:
152           $context['sandbox']['messages'][] = (string) new TranslatableMarkup('Operation on @migration failed', ['@migration' => $migration_name]);
153           $context['results']['failures']++;
154           \Drupal::logger('migrate_drupal_ui')->error('Operation on @migration failed', ['@migration' => $migration_name]);
155           break;
156
157         case MigrationInterface::RESULT_SKIPPED:
158           $context['sandbox']['messages'][] = (string) new TranslatableMarkup('Operation on @migration skipped due to unfulfilled dependencies', ['@migration' => $migration_name]);
159           \Drupal::logger('migrate_drupal_ui')->error('Operation on @migration skipped due to unfulfilled dependencies', ['@migration' => $migration_name]);
160           break;
161
162         case MigrationInterface::RESULT_DISABLED:
163           // Skip silently if disabled.
164           break;
165       }
166
167       // Unless we're continuing on with this migration, take it off the list.
168       if ($migration_status != MigrationInterface::RESULT_INCOMPLETE) {
169         array_shift($context['sandbox']['migration_ids']);
170         $context['sandbox']['current']++;
171       }
172
173       // Add and log any captured messages.
174       foreach (static::$messages->getMessages() as $message) {
175         $context['sandbox']['messages'][] = (string) $message;
176         \Drupal::logger('migrate_drupal_ui')->error($message);
177       }
178
179       // Only display the last MESSAGE_LENGTH messages, in reverse order.
180       $message_count = count($context['sandbox']['messages']);
181       $context['message'] = '';
182       for ($index = max(0, $message_count - self::MESSAGE_LENGTH); $index < $message_count; $index++) {
183         $context['message'] = $context['sandbox']['messages'][$index] . "<br />\n" . $context['message'];
184       }
185       if ($message_count > self::MESSAGE_LENGTH) {
186         // Indicate there are earlier messages not displayed.
187         $context['message'] .= '&hellip;';
188       }
189       // At the top of the list, display the next one (which will be the one
190       // that is running while this message is visible).
191       if (!empty($context['sandbox']['migration_ids'])) {
192         $migration_id = reset($context['sandbox']['migration_ids']);
193         $migration = \Drupal::service('plugin.manager.migration')->createInstance($migration_id);
194         $migration_name = $migration->label() ? $migration->label() : $migration_id;
195         $context['message'] = (string) new TranslatableMarkup('Currently upgrading @migration (@current of @max total tasks)', [
196             '@migration' => $migration_name,
197             '@current' => $context['sandbox']['current'],
198             '@max' => $context['sandbox']['max'],
199           ]) . "<br />\n" . $context['message'];
200       }
201     }
202     else {
203       array_shift($context['sandbox']['migration_ids']);
204       $context['sandbox']['current']++;
205     }
206
207     $context['finished'] = 1 - count($context['sandbox']['migration_ids']) / $context['sandbox']['max'];
208   }
209
210   /**
211    * Callback executed when the Migrate Upgrade Import batch process completes.
212    *
213    * @param bool $success
214    *   TRUE if batch successfully completed.
215    * @param array $results
216    *   Batch results.
217    * @param array $operations
218    *   An array of methods run in the batch.
219    * @param string $elapsed
220    *   The time to run the batch.
221    */
222   public static function finished($success, $results, $operations, $elapsed) {
223     $successes = $results['successes'];
224     $failures = $results['failures'];
225
226     // If we had any successes log that for the user.
227     if ($successes > 0) {
228       drupal_set_message(\Drupal::translation()
229         ->formatPlural($successes, 'Completed 1 upgrade task successfully', 'Completed @count upgrade tasks successfully'));
230     }
231     // If we had failures, log them and show the migration failed.
232     if ($failures > 0) {
233       drupal_set_message(\Drupal::translation()
234         ->formatPlural($failures, '1 upgrade failed', '@count upgrades failed'));
235       drupal_set_message(t('Upgrade process not completed'), 'error');
236     }
237     else {
238       // Everything went off without a hitch. We may not have had successes
239       // but we didn't have failures so this is fine.
240       drupal_set_message(t('Congratulations, you upgraded Drupal!'));
241     }
242
243     if (\Drupal::moduleHandler()->moduleExists('dblog')) {
244       $url = Url::fromRoute('migrate_drupal_ui.log');
245       drupal_set_message(Link::fromTextAndUrl(new TranslatableMarkup('Review the detailed upgrade log'), $url), $failures ? 'error' : 'status');
246     }
247   }
248
249   /**
250    * Reacts to item import.
251    *
252    * @param \Drupal\migrate\Event\MigratePostRowSaveEvent $event
253    *   The post-save event.
254    */
255   public static function onPostRowSave(MigratePostRowSaveEvent $event) {
256     // We want to interrupt this batch and start a fresh one.
257     if ((time() - REQUEST_TIME) > static::$maxExecTime) {
258       $event->getMigration()->interruptMigration(MigrationInterface::RESULT_INCOMPLETE);
259     }
260   }
261
262   /**
263    * Reacts to item deletion.
264    *
265    * @param \Drupal\migrate\Event\MigrateRowDeleteEvent $event
266    *   The post-save event.
267    */
268   public static function onPostRowDelete(MigrateRowDeleteEvent $event) {
269     // We want to interrupt this batch and start a fresh one.
270     if ((time() - REQUEST_TIME) > static::$maxExecTime) {
271       $event->getMigration()->interruptMigration(MigrationInterface::RESULT_INCOMPLETE);
272     }
273   }
274
275   /**
276    * Counts up any map save events.
277    *
278    * @param \Drupal\migrate\Event\MigrateMapSaveEvent $event
279    *   The map event.
280    */
281   public static function onMapSave(MigrateMapSaveEvent $event) {
282     static::$numProcessed++;
283   }
284
285   /**
286    * Counts up any map delete events.
287    *
288    * @param \Drupal\migrate\Event\MigrateMapDeleteEvent $event
289    *   The map event.
290    */
291   public static function onMapDelete(MigrateMapDeleteEvent $event) {
292     static::$numProcessed++;
293   }
294
295   /**
296    * Displays any messages being logged to the ID map.
297    *
298    * @param \Drupal\migrate\Event\MigrateIdMapMessageEvent $event
299    *   The message event.
300    */
301   public static function onIdMapMessage(MigrateIdMapMessageEvent $event) {
302     if ($event->getLevel() == MigrationInterface::MESSAGE_NOTICE || $event->getLevel() == MigrationInterface::MESSAGE_INFORMATIONAL) {
303       $type = 'status';
304     }
305     else {
306       $type = 'error';
307     }
308     $source_id_string = implode(',', $event->getSourceIdValues());
309     $message = t('Source ID @source_id: @message', ['@source_id' => $source_id_string, '@message' => $event->getMessage()]);
310     static::$messages->display($message, $type);
311   }
312
313 }