Upgraded drupal core with security updates
[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       if ($definition['source']['plugin'] === 'd7_file_private') {
108         $configuration['source']['constants']['source_base_path'] = rtrim($config['source_private_file_path'], '/') . '/';
109       }
110       $configuration['source']['constants']['source_base_path'] = rtrim($config['source_base_path'], '/') . '/';
111     }
112
113     /** @var \Drupal\migrate\Plugin\Migration $migration */
114     $migration = \Drupal::service('plugin.manager.migration')->createInstance($migration_id, $configuration);
115
116     if ($migration) {
117       static::$messages = new MigrateMessageCapture();
118       $executable = new MigrateExecutable($migration, static::$messages);
119
120       $migration_name = $migration->label() ? $migration->label() : $migration_id;
121
122       try {
123         $migration_status = $executable->import();
124       }
125       catch (\Exception $e) {
126         \Drupal::logger('migrate_drupal_ui')->error($e->getMessage());
127         $migration_status = MigrationInterface::RESULT_FAILED;
128       }
129
130       switch ($migration_status) {
131         case MigrationInterface::RESULT_COMPLETED:
132           // Store the number processed in the sandbox.
133           $context['sandbox']['num_processed'] += static::$numProcessed;
134           $message = new PluralTranslatableMarkup(
135             $context['sandbox']['num_processed'], 'Upgraded @migration (processed 1 item total)', 'Upgraded @migration (processed @count items total)',
136             ['@migration' => $migration_name]);
137           $context['sandbox']['messages'][] = (string) $message;
138           \Drupal::logger('migrate_drupal_ui')->notice($message);
139           $context['sandbox']['num_processed'] = 0;
140           $context['results']['successes']++;
141           break;
142
143         case MigrationInterface::RESULT_INCOMPLETE:
144           $context['sandbox']['messages'][] = (string) new PluralTranslatableMarkup(
145             static::$numProcessed, 'Continuing with @migration (processed 1 item)', 'Continuing with @migration (processed @count items)',
146             ['@migration' => $migration_name]);
147           $context['sandbox']['num_processed'] += static::$numProcessed;
148           break;
149
150         case MigrationInterface::RESULT_STOPPED:
151           $context['sandbox']['messages'][] = (string) new TranslatableMarkup('Operation stopped by request');
152           break;
153
154         case MigrationInterface::RESULT_FAILED:
155           $context['sandbox']['messages'][] = (string) new TranslatableMarkup('Operation on @migration failed', ['@migration' => $migration_name]);
156           $context['results']['failures']++;
157           \Drupal::logger('migrate_drupal_ui')->error('Operation on @migration failed', ['@migration' => $migration_name]);
158           break;
159
160         case MigrationInterface::RESULT_SKIPPED:
161           $context['sandbox']['messages'][] = (string) new TranslatableMarkup('Operation on @migration skipped due to unfulfilled dependencies', ['@migration' => $migration_name]);
162           \Drupal::logger('migrate_drupal_ui')->error('Operation on @migration skipped due to unfulfilled dependencies', ['@migration' => $migration_name]);
163           break;
164
165         case MigrationInterface::RESULT_DISABLED:
166           // Skip silently if disabled.
167           break;
168       }
169
170       // Unless we're continuing on with this migration, take it off the list.
171       if ($migration_status != MigrationInterface::RESULT_INCOMPLETE) {
172         array_shift($context['sandbox']['migration_ids']);
173         $context['sandbox']['current']++;
174       }
175
176       // Add and log any captured messages.
177       foreach (static::$messages->getMessages() as $message) {
178         $context['sandbox']['messages'][] = (string) $message;
179         \Drupal::logger('migrate_drupal_ui')->error($message);
180       }
181
182       // Only display the last MESSAGE_LENGTH messages, in reverse order.
183       $message_count = count($context['sandbox']['messages']);
184       $context['message'] = '';
185       for ($index = max(0, $message_count - self::MESSAGE_LENGTH); $index < $message_count; $index++) {
186         $context['message'] = $context['sandbox']['messages'][$index] . "<br />\n" . $context['message'];
187       }
188       if ($message_count > self::MESSAGE_LENGTH) {
189         // Indicate there are earlier messages not displayed.
190         $context['message'] .= '&hellip;';
191       }
192       // At the top of the list, display the next one (which will be the one
193       // that is running while this message is visible).
194       if (!empty($context['sandbox']['migration_ids'])) {
195         $migration_id = reset($context['sandbox']['migration_ids']);
196         $migration = \Drupal::service('plugin.manager.migration')->createInstance($migration_id);
197         $migration_name = $migration->label() ? $migration->label() : $migration_id;
198         $context['message'] = (string) new TranslatableMarkup('Currently upgrading @migration (@current of @max total tasks)', [
199             '@migration' => $migration_name,
200             '@current' => $context['sandbox']['current'],
201             '@max' => $context['sandbox']['max'],
202           ]) . "<br />\n" . $context['message'];
203       }
204     }
205     else {
206       array_shift($context['sandbox']['migration_ids']);
207       $context['sandbox']['current']++;
208     }
209
210     $context['finished'] = 1 - count($context['sandbox']['migration_ids']) / $context['sandbox']['max'];
211   }
212
213   /**
214    * Callback executed when the Migrate Upgrade Import batch process completes.
215    *
216    * @param bool $success
217    *   TRUE if batch successfully completed.
218    * @param array $results
219    *   Batch results.
220    * @param array $operations
221    *   An array of methods run in the batch.
222    * @param string $elapsed
223    *   The time to run the batch.
224    */
225   public static function finished($success, $results, $operations, $elapsed) {
226     $successes = $results['successes'];
227     $failures = $results['failures'];
228
229     // If we had any successes log that for the user.
230     if ($successes > 0) {
231       drupal_set_message(\Drupal::translation()
232         ->formatPlural($successes, 'Completed 1 upgrade task successfully', 'Completed @count upgrade tasks successfully'));
233     }
234     // If we had failures, log them and show the migration failed.
235     if ($failures > 0) {
236       drupal_set_message(\Drupal::translation()
237         ->formatPlural($failures, '1 upgrade failed', '@count upgrades failed'));
238       drupal_set_message(t('Upgrade process not completed'), 'error');
239     }
240     else {
241       // Everything went off without a hitch. We may not have had successes
242       // but we didn't have failures so this is fine.
243       drupal_set_message(t('Congratulations, you upgraded Drupal!'));
244     }
245
246     if (\Drupal::moduleHandler()->moduleExists('dblog')) {
247       $url = Url::fromRoute('migrate_drupal_ui.log');
248       drupal_set_message(Link::fromTextAndUrl(new TranslatableMarkup('Review the detailed upgrade log'), $url), $failures ? 'error' : 'status');
249     }
250   }
251
252   /**
253    * Reacts to item import.
254    *
255    * @param \Drupal\migrate\Event\MigratePostRowSaveEvent $event
256    *   The post-save event.
257    */
258   public static function onPostRowSave(MigratePostRowSaveEvent $event) {
259     // We want to interrupt this batch and start a fresh one.
260     if ((time() - REQUEST_TIME) > static::$maxExecTime) {
261       $event->getMigration()->interruptMigration(MigrationInterface::RESULT_INCOMPLETE);
262     }
263   }
264
265   /**
266    * Reacts to item deletion.
267    *
268    * @param \Drupal\migrate\Event\MigrateRowDeleteEvent $event
269    *   The post-save event.
270    */
271   public static function onPostRowDelete(MigrateRowDeleteEvent $event) {
272     // We want to interrupt this batch and start a fresh one.
273     if ((time() - REQUEST_TIME) > static::$maxExecTime) {
274       $event->getMigration()->interruptMigration(MigrationInterface::RESULT_INCOMPLETE);
275     }
276   }
277
278   /**
279    * Counts up any map save events.
280    *
281    * @param \Drupal\migrate\Event\MigrateMapSaveEvent $event
282    *   The map event.
283    */
284   public static function onMapSave(MigrateMapSaveEvent $event) {
285     static::$numProcessed++;
286   }
287
288   /**
289    * Counts up any map delete events.
290    *
291    * @param \Drupal\migrate\Event\MigrateMapDeleteEvent $event
292    *   The map event.
293    */
294   public static function onMapDelete(MigrateMapDeleteEvent $event) {
295     static::$numProcessed++;
296   }
297
298   /**
299    * Displays any messages being logged to the ID map.
300    *
301    * @param \Drupal\migrate\Event\MigrateIdMapMessageEvent $event
302    *   The message event.
303    */
304   public static function onIdMapMessage(MigrateIdMapMessageEvent $event) {
305     if ($event->getLevel() == MigrationInterface::MESSAGE_NOTICE || $event->getLevel() == MigrationInterface::MESSAGE_INFORMATIONAL) {
306       $type = 'status';
307     }
308     else {
309       $type = 'error';
310     }
311     $source_id_string = implode(',', $event->getSourceIdValues());
312     $message = t('Source ID @source_id: @message', ['@source_id' => $source_id_string, '@message' => $event->getMessage()]);
313     static::$messages->display($message, $type);
314   }
315
316 }