Updated all the contrib modules to their latest versions.
[yaffs-website] / web / modules / contrib / migrate_tools / src / MigrateExecutable.php
1 <?php
2
3 namespace Drupal\migrate_tools;
4
5 use Drupal\migrate\Event\MigratePreRowSaveEvent;
6 use Drupal\migrate\Event\MigrateRollbackEvent;
7 use Drupal\migrate\Event\MigrateRowDeleteEvent;
8 use Drupal\migrate\MigrateExecutable as MigrateExecutableBase;
9 use Drupal\migrate\MigrateMessageInterface;
10 use Drupal\migrate\Plugin\MigrationInterface;
11 use Drupal\migrate\MigrateSkipRowException;
12 use Drupal\migrate\Plugin\MigrateIdMapInterface;
13 use Drupal\migrate\Event\MigrateEvents;
14 use Drupal\migrate_plus\Event\MigrateEvents as MigratePlusEvents;
15 use Drupal\migrate\Event\MigrateMapSaveEvent;
16 use Drupal\migrate\Event\MigrateMapDeleteEvent;
17 use Drupal\migrate\Event\MigrateImportEvent;
18 use Drupal\migrate_plus\Event\MigratePrepareRowEvent;
19
20 /**
21  * Defines a migrate executable class for drush.
22  */
23 class MigrateExecutable extends MigrateExecutableBase {
24
25   /**
26    * Counters of map statuses.
27    *
28    * @var array
29    *   Set of counters, keyed by MigrateIdMapInterface::STATUS_* constant.
30    */
31   protected $saveCounters = [
32     MigrateIdMapInterface::STATUS_FAILED => 0,
33     MigrateIdMapInterface::STATUS_IGNORED => 0,
34     MigrateIdMapInterface::STATUS_IMPORTED => 0,
35     MigrateIdMapInterface::STATUS_NEEDS_UPDATE => 0,
36   ];
37
38   /**
39    * Counter of map saves, used to detect the item limit threshold.
40    *
41    * @var int
42    */
43   protected $itemLimitCounter = 0;
44
45   /**
46    * Counter of map deletions.
47    *
48    * @var int
49    */
50   protected $deleteCounter = 0;
51
52   /**
53    * Maximum number of items to process in this migration.
54    *
55    * 0 indicates no limit is to be applied.
56    *
57    * @var int
58    */
59   protected $itemLimit = 0;
60
61   /**
62    * Frequency (in items) at which progress messages should be emitted.
63    *
64    * @var int
65    */
66   protected $feedback = 0;
67
68   /**
69    * List of specific source IDs to import.
70    *
71    * @var array
72    */
73   protected $idlist = [];
74
75   /**
76    * Count of number of items processed so far in this migration.
77    *
78    * @var int
79    */
80   protected $counter = 0;
81
82   /**
83    * Whether the destination item exists before saving.
84    *
85    * @var bool
86    */
87   protected $preExistingItem = FALSE;
88
89   /**
90    * List of event listeners we have registered.
91    *
92    * @var array
93    */
94   protected $listeners = [];
95
96   /**
97    * {@inheritdoc}
98    */
99   public function __construct(MigrationInterface $migration, MigrateMessageInterface $message, array $options = []) {
100     parent::__construct($migration, $message);
101     if (isset($options['limit'])) {
102       $this->itemLimit = $options['limit'];
103     }
104     if (isset($options['feedback'])) {
105       $this->feedback = $options['feedback'];
106     }
107     if (isset($options['idlist'])) {
108       if (is_string($options['idlist'])) {
109         $this->idlist = explode(',', $options['idlist']);
110         array_walk($this->idlist, function (&$value, $key) {
111           $value = explode(':', $value);
112         });
113       }
114     }
115
116     $this->listeners[MigrateEvents::MAP_SAVE] = [$this, 'onMapSave'];
117     $this->listeners[MigrateEvents::MAP_DELETE] = [$this, 'onMapDelete'];
118     $this->listeners[MigrateEvents::POST_IMPORT] = [$this, 'onPostImport'];
119     $this->listeners[MigrateEvents::POST_ROLLBACK] = [$this, 'onPostRollback'];
120     $this->listeners[MigrateEvents::PRE_ROW_SAVE] = [$this, 'onPreRowSave'];
121     $this->listeners[MigrateEvents::POST_ROW_DELETE] = [$this, 'onPostRowDelete'];
122     $this->listeners[MigratePlusEvents::PREPARE_ROW] = [$this, 'onPrepareRow'];
123     foreach ($this->listeners as $event => $listener) {
124       \Drupal::service('event_dispatcher')->addListener($event, $listener);
125     }
126   }
127
128   /**
129    * Count up any map save events.
130    *
131    * @param \Drupal\migrate\Event\MigrateMapSaveEvent $event
132    *   The map event.
133    */
134   public function onMapSave(MigrateMapSaveEvent $event) {
135     // Only count saves for this migration.
136     if ($event->getMap()->getQualifiedMapTableName() == $this->migration->getIdMap()->getQualifiedMapTableName()) {
137       $fields = $event->getFields();
138       $this->itemLimitCounter++;
139       // Distinguish between creation and update.
140       if ($fields['source_row_status'] == MigrateIdMapInterface::STATUS_IMPORTED &&
141         $this->preExistingItem
142       ) {
143         $this->saveCounters[MigrateIdMapInterface::STATUS_NEEDS_UPDATE]++;
144       }
145       else {
146         $this->saveCounters[$fields['source_row_status']]++;
147       }
148     }
149   }
150
151   /**
152    * Count up any rollback events.
153    *
154    * @param \Drupal\migrate\Event\MigrateMapDeleteEvent $event
155    *   The map event.
156    */
157   public function onMapDelete(MigrateMapDeleteEvent $event) {
158     $this->deleteCounter++;
159   }
160
161   /**
162    * Return the number of items created.
163    *
164    * @return int
165    *   The number of items created.
166    */
167   public function getCreatedCount() {
168     return $this->saveCounters[MigrateIdMapInterface::STATUS_IMPORTED];
169   }
170
171   /**
172    * Return the number of items updated.
173    *
174    * @return int
175    *   The updated count.
176    */
177   public function getUpdatedCount() {
178     return $this->saveCounters[MigrateIdMapInterface::STATUS_NEEDS_UPDATE];
179   }
180
181   /**
182    * Return the number of items ignored.
183    *
184    * @return int
185    *   The ignored count.
186    */
187   public function getIgnoredCount() {
188     return $this->saveCounters[MigrateIdMapInterface::STATUS_IGNORED];
189   }
190
191   /**
192    * Return the number of items that failed.
193    *
194    * @return int
195    *   The failed count.
196    */
197   public function getFailedCount() {
198     return $this->saveCounters[MigrateIdMapInterface::STATUS_FAILED];
199   }
200
201   /**
202    * Return the total number of items processed.
203    *
204    * Note that STATUS_NEEDS_UPDATE is not counted, since this is typically set
205    * on stubs created as side effects, not on the primary item being imported.
206    *
207    * @return int
208    *   The processed count.
209    */
210   public function getProcessedCount() {
211     return $this->saveCounters[MigrateIdMapInterface::STATUS_IMPORTED] +
212       $this->saveCounters[MigrateIdMapInterface::STATUS_NEEDS_UPDATE] +
213       $this->saveCounters[MigrateIdMapInterface::STATUS_IGNORED] +
214       $this->saveCounters[MigrateIdMapInterface::STATUS_FAILED];
215   }
216
217   /**
218    * Return the number of items rolled back.
219    *
220    * @return int
221    *   The rollback count.
222    */
223   public function getRollbackCount() {
224     return $this->deleteCounter;
225   }
226
227   /**
228    * Reset all the per-status counters to 0.
229    */
230   protected function resetCounters() {
231     foreach ($this->saveCounters as $status => $count) {
232       $this->saveCounters[$status] = 0;
233     }
234     $this->deleteCounter = 0;
235   }
236
237   /**
238    * React to migration completion.
239    *
240    * @param \Drupal\migrate\Event\MigrateImportEvent $event
241    *   The map event.
242    */
243   public function onPostImport(MigrateImportEvent $event) {
244     $migrate_last_imported_store = \Drupal::keyValue('migrate_last_imported');
245     $migrate_last_imported_store->set($event->getMigration()->id(), round(microtime(TRUE) * 1000));
246     $this->progressMessage();
247     $this->removeListeners();
248   }
249
250   /**
251    * Clean up all our event listeners.
252    */
253   protected function removeListeners() {
254     foreach ($this->listeners as $event => $listener) {
255       \Drupal::service('event_dispatcher')->removeListener($event, $listener);
256     }
257   }
258
259   /**
260    * Emit information on what we've done.
261    *
262    * Either since the last feedback or the beginning of this migration.
263    *
264    * @param bool $done
265    *   TRUE if this is the last items to process. Otherwise FALSE.
266    */
267   protected function progressMessage($done = TRUE) {
268     $processed = $this->getProcessedCount();
269     if ($done) {
270       $singular_message = "Processed 1 item (@created created, @updated updated, @failures failed, @ignored ignored) - done with '@name'";
271       $plural_message = "Processed @numitems items (@created created, @updated updated, @failures failed, @ignored ignored) - done with '@name'";
272     }
273     else {
274       $singular_message = "Processed 1 item (@created created, @updated updated, @failures failed, @ignored ignored) - continuing with '@name'";
275       $plural_message = "Processed @numitems items (@created created, @updated updated, @failures failed, @ignored ignored) - continuing with '@name'";
276     }
277     $this->message->display(\Drupal::translation()->formatPlural($processed,
278       $singular_message, $plural_message,
279         [
280           '@numitems' => $processed,
281           '@created' => $this->getCreatedCount(),
282           '@updated' => $this->getUpdatedCount(),
283           '@failures' => $this->getFailedCount(),
284           '@ignored' => $this->getIgnoredCount(),
285           '@name' => $this->migration->id(),
286         ]
287     ));
288   }
289
290   /**
291    * React to rollback completion.
292    *
293    * @param \Drupal\migrate\Event\MigrateRollbackEvent $event
294    *   The map event.
295    */
296   public function onPostRollback(MigrateRollbackEvent $event) {
297     $this->rollbackMessage();
298     $this->removeListeners();
299   }
300
301   /**
302    * Emit information on what we've done.
303    *
304    * Either since the last feedback or the beginning of this migration.
305    *
306    * @param bool $done
307    *   TRUE if this is the last items to rollback. Otherwise FALSE.
308    */
309   protected function rollbackMessage($done = TRUE) {
310     $rolled_back = $this->getRollbackCount();
311     if ($done) {
312       $singular_message = "Rolled back 1 item - done with '@name'";
313       $plural_message = "Rolled back @numitems items - done with '@name'";
314     }
315     else {
316       $singular_message = "Rolled back 1 item - continuing with '@name'";
317       $plural_message = "Rolled back @numitems items - continuing with '@name'";
318     }
319     $this->message->display(\Drupal::translation()->formatPlural($rolled_back,
320       $singular_message, $plural_message,
321       [
322         '@numitems' => $rolled_back,
323         '@name' => $this->migration->id(),
324       ]
325     ));
326   }
327
328   /**
329    * React to an item about to be imported.
330    *
331    * @param \Drupal\migrate\Event\MigratePreRowSaveEvent $event
332    *   The pre-save event.
333    */
334   public function onPreRowSave(MigratePreRowSaveEvent $event) {
335     $id_map = $event->getRow()->getIdMap();
336     if (!empty($id_map['destid1'])) {
337       $this->preExistingItem = TRUE;
338     }
339     else {
340       $this->preExistingItem = FALSE;
341     }
342   }
343
344   /**
345    * React to item rollback.
346    *
347    * @param \Drupal\migrate\Event\MigrateRowDeleteEvent $event
348    *   The post-save event.
349    */
350   public function onPostRowDelete(MigrateRowDeleteEvent $event) {
351     if ($this->feedback && ($this->deleteCounter) && $this->deleteCounter % $this->feedback == 0) {
352       $this->rollbackMessage(FALSE);
353       $this->resetCounters();
354     }
355   }
356
357   /**
358    * React to a new row.
359    *
360    * @param \Drupal\migrate_plus\Event\MigratePrepareRowEvent $event
361    *   The prepare-row event.
362    *
363    * @throws \Drupal\migrate\MigrateSkipRowException
364    */
365   public function onPrepareRow(MigratePrepareRowEvent $event) {
366     if (!empty($this->idlist)) {
367       $row = $event->getRow();
368       // TODO: replace for $source_id = $row->getSourceIdValues();
369       // when https://www.drupal.org/node/2698023 is fixed.
370       $migration = $event->getMigration();
371       $source_id = array_merge(array_flip(array_keys($migration->getSourcePlugin()
372         ->getIds())), $row->getSourceIdValues());
373       $skip = TRUE;
374       foreach ($this->idlist as $item) {
375         if (array_values($source_id) == $item) {
376           $skip = FALSE;
377           break;
378         }
379       }
380       if ($skip) {
381         throw new MigrateSkipRowException(NULL, FALSE);
382       }
383     }
384     if ($this->feedback && ($this->counter) && $this->counter % $this->feedback == 0) {
385       $this->progressMessage(FALSE);
386       $this->resetCounters();
387     }
388     $this->counter++;
389     if ($this->itemLimit && ($this->itemLimitCounter + 1) >= $this->itemLimit) {
390       $event->getMigration()->interruptMigration(MigrationInterface::RESULT_COMPLETED);
391     }
392
393   }
394
395 }