Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / web / core / modules / migrate / src / Plugin / migrate / id_map / Sql.php
index 27b756904a97934655a35d65f1943fd0f229c87e..0de83485ffa9a1c9f1bc38f1bb732ace7d4a637e 100644 (file)
@@ -2,10 +2,12 @@
 
 namespace Drupal\migrate\Plugin\migrate\id_map;
 
-use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Database\DatabaseException;
 use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Plugin\PluginBase;
+use Drupal\migrate\MigrateMessage;
+use Drupal\migrate\Audit\HighestIdInterface;
 use Drupal\migrate\Plugin\MigrationInterface;
 use Drupal\migrate\Event\MigrateIdMapMessageEvent;
 use Drupal\migrate\MigrateException;
@@ -26,7 +28,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  *
  * @PluginID("sql")
  */
-class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryPluginInterface {
+class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryPluginInterface, HighestIdInterface {
 
   /**
    * Column name of hashed source id values.
@@ -55,7 +57,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
   protected $messageTableName;
 
   /**
-   * The migrate message.
+   * The migrate message service.
    *
    * @var \Drupal\migrate\MigrateMessageInterface
    */
@@ -151,11 +153,26 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
    *   The configuration for the plugin.
    * @param \Drupal\migrate\Plugin\MigrationInterface $migration
    *   The migration to do.
+   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
+   *   The event dispatcher.
    */
   public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EventDispatcherInterface $event_dispatcher) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
     $this->migration = $migration;
     $this->eventDispatcher = $event_dispatcher;
+    $this->message = new MigrateMessage();
+
+    if (!isset($this->database)) {
+      $this->database = \Drupal::database();
+    }
+
+    // Default generated table names, limited to 63 characters.
+    $machine_name = str_replace(':', '__', $this->migration->id());
+    $prefix_length = strlen($this->database->tablePrefix());
+    $this->mapTableName = 'migrate_map_' . mb_strtolower($machine_name);
+    $this->mapTableName = mb_substr($this->mapTableName, 0, 63 - $prefix_length);
+    $this->messageTableName = 'migrate_message_' . mb_strtolower($machine_name);
+    $this->messageTableName = mb_substr($this->messageTableName, 0, 63 - $prefix_length);
   }
 
   /**
@@ -182,7 +199,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
    * @return string
    *   An hash containing the hashed values of the source identifiers.
    */
-  public function getSourceIDsHash(array $source_id_values) {
+  public function getSourceIdsHash(array $source_id_values) {
     // When looking up the destination ID we require an array with both the
     // source key and value, e.g. ['nid' => 41]. In this case, $source_id_values
     // need to be ordered the same order as $this->sourceIdFields().
@@ -241,7 +258,6 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
    *   The map table name.
    */
   public function mapTableName() {
-    $this->init();
     return $this->mapTableName;
   }
 
@@ -252,7 +268,6 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
    *   The message table name.
    */
   public function messageTableName() {
-    $this->init();
     return $this->messageTableName;
   }
 
@@ -273,9 +288,6 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
    *   The database connection object.
    */
   public function getDatabase() {
-    if (!isset($this->database)) {
-      $this->database = \Drupal::database();
-    }
     $this->init();
     return $this->database;
   }
@@ -286,13 +298,6 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
   protected function init() {
     if (!$this->initialized) {
       $this->initialized = TRUE;
-      // Default generated table names, limited to 63 characters.
-      $machine_name = str_replace(':', '__', $this->migration->id());
-      $prefix_length = strlen($this->getDatabase()->tablePrefix());
-      $this->mapTableName = 'migrate_map_' . Unicode::strtolower($machine_name);
-      $this->mapTableName = Unicode::substr($this->mapTableName, 0, 63 - $prefix_length);
-      $this->messageTableName = 'migrate_message_' . Unicode::strtolower($machine_name);
-      $this->messageTableName = Unicode::substr($this->messageTableName, 0, 63 - $prefix_length);
       $this->ensureTables();
     }
   }
@@ -486,7 +491,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
   public function getRowBySource(array $source_id_values) {
     $query = $this->getDatabase()->select($this->mapTableName(), 'map')
       ->fields('map');
-    $query->condition(static::SOURCE_IDS_HASH, $this->getSourceIDsHash($source_id_values));
+    $query->condition(static::SOURCE_IDS_HASH, $this->getSourceIdsHash($source_id_values));
     $result = $query->execute();
     return $result->fetchAssoc();
   }
@@ -523,7 +528,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
   /**
    * {@inheritdoc}
    */
-  public function lookupSourceID(array $destination_id_values) {
+  public function lookupSourceId(array $destination_id_values) {
     $source_id_fields = $this->sourceIdFields();
     $query = $this->getDatabase()->select($this->mapTableName(), 'map');
     foreach ($source_id_fields as $source_field_name => $idmap_field_name) {
@@ -557,9 +562,13 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
     $conditions = [];
     foreach ($this->sourceIdFields() as $field_name => $db_field) {
       if ($is_associative) {
-        // Associative $source_id_values can have fields out of order.
-        if (isset($source_id_values[$field_name])) {
-          $conditions[$db_field] = $source_id_values[$field_name];
+        // Ensure to handle array elements with a NULL value.
+        if (array_key_exists($field_name, $source_id_values)) {
+          // Associative $source_id_values can have fields out of order.
+          if (isset($source_id_values[$field_name])) {
+            // Only add a condition if the value is not NULL.
+            $conditions[$db_field] = $source_id_values[$field_name];
+          }
           unset($source_id_values[$field_name]);
         }
       }
@@ -574,14 +583,15 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
     }
 
     if (!empty($source_id_values)) {
-      throw new MigrateException("Extra unknown items in source IDs");
+      $var_dump = var_export($source_id_values, TRUE);
+      throw new MigrateException(sprintf("Extra unknown items in source IDs: %s", $var_dump));
     }
 
     $query = $this->getDatabase()->select($this->mapTableName(), 'map')
       ->fields('map', $this->destinationIdFields());
     if (count($this->sourceIdFields()) === count($conditions)) {
       // Optimization: Use the primary key.
-      $query->condition(self::SOURCE_IDS_HASH, $this->getSourceIDsHash(array_values($conditions)));
+      $query->condition(self::SOURCE_IDS_HASH, $this->getSourceIdsHash(array_values($conditions)));
     }
     else {
       foreach ($conditions as $db_field => $value) {
@@ -631,7 +641,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
     if ($this->migration->getTrackLastImported()) {
       $fields['last_imported'] = time();
     }
-    $keys = [static::SOURCE_IDS_HASH => $this->getSourceIDsHash($source_id_values)];
+    $keys = [static::SOURCE_IDS_HASH => $this->getSourceIdsHash($source_id_values)];
     // Notify anyone listening of the map row we're about to save.
     $this->eventDispatcher->dispatch(MigrateEvents::MAP_SAVE, new MigrateMapSaveEvent($this, $fields));
     $this->getDatabase()->merge($this->mapTableName())
@@ -650,7 +660,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
         return;
       }
     }
-    $fields[static::SOURCE_IDS_HASH] = $this->getSourceIDsHash($source_id_values);
+    $fields[static::SOURCE_IDS_HASH] = $this->getSourceIdsHash($source_id_values);
     $fields['level'] = $level;
     $fields['message'] = $message;
     $this->getDatabase()->insert($this->messageTableName())
@@ -669,7 +679,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
     $query = $this->getDatabase()->select($this->messageTableName(), 'msg')
       ->fields('msg');
     if ($source_id_values) {
-      $query->condition(static::SOURCE_IDS_HASH, $this->getSourceIDsHash($source_id_values));
+      $query->condition(static::SOURCE_IDS_HASH, $this->getSourceIdsHash($source_id_values));
     }
 
     if ($level) {
@@ -691,21 +701,17 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
    * {@inheritdoc}
    */
   public function processedCount() {
-    return $this->getDatabase()->select($this->mapTableName())
-      ->countQuery()
-      ->execute()
-      ->fetchField();
+    return $this->countHelper(NULL, $this->mapTableName());
   }
 
   /**
    * {@inheritdoc}
    */
   public function importedCount() {
-    return $this->getDatabase()->select($this->mapTableName())
-      ->condition('source_row_status', [MigrateIdMapInterface::STATUS_IMPORTED, MigrateIdMapInterface::STATUS_NEEDS_UPDATE], 'IN')
-      ->countQuery()
-      ->execute()
-      ->fetchField();
+    return $this->countHelper([
+      MigrateIdMapInterface::STATUS_IMPORTED,
+      MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
+    ]);
   }
 
   /**
@@ -732,20 +738,28 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
   /**
    * Counts records in a table.
    *
-   * @param int $status
-   *   An integer for the source_row_status column.
+   * @param int|array $status
+   *   (optional) Status code(s) to filter the source_row_status column.
    * @param string $table
    *   (optional) The table to work. Defaults to NULL.
    *
    * @return int
    *   The number of records.
    */
-  protected function countHelper($status, $table = NULL) {
-    $query = $this->getDatabase()->select($table ?: $this->mapTableName());
+  protected function countHelper($status = NULL, $table = NULL) {
+    // Use database directly to avoid creating tables.
+    $query = $this->database->select($table ?: $this->mapTableName());
     if (isset($status)) {
-      $query->condition('source_row_status', $status);
+      $query->condition('source_row_status', $status, is_array($status) ? 'IN' : '=');
     }
-    return $query->countQuery()->execute()->fetchField();
+    try {
+      $count = (int) $query->countQuery()->execute()->fetchField();
+    }
+    catch (DatabaseException $e) {
+      // The table does not exist, therefore there are no records.
+      $count = 0;
+    }
+    return $count;
   }
 
   /**
@@ -758,13 +772,13 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
 
     if (!$messages_only) {
       $map_query = $this->getDatabase()->delete($this->mapTableName());
-      $map_query->condition(static::SOURCE_IDS_HASH, $this->getSourceIDsHash($source_id_values));
+      $map_query->condition(static::SOURCE_IDS_HASH, $this->getSourceIdsHash($source_id_values));
       // Notify anyone listening of the map row we're about to delete.
       $this->eventDispatcher->dispatch(MigrateEvents::MAP_DELETE, new MigrateMapDeleteEvent($this, $source_id_values));
       $map_query->execute();
     }
     $message_query = $this->getDatabase()->delete($this->messageTableName());
-    $message_query->condition(static::SOURCE_IDS_HASH, $this->getSourceIDsHash($source_id_values));
+    $message_query->condition(static::SOURCE_IDS_HASH, $this->getSourceIdsHash($source_id_values));
     $message_query->execute();
   }
 
@@ -774,7 +788,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
   public function deleteDestination(array $destination_id_values) {
     $map_query = $this->getDatabase()->delete($this->mapTableName());
     $message_query = $this->getDatabase()->delete($this->messageTableName());
-    $source_id_values = $this->lookupSourceID($destination_id_values);
+    $source_id_values = $this->lookupSourceId($destination_id_values);
     if (!empty($source_id_values)) {
       foreach ($this->destinationIdFields() as $field_name => $destination_id) {
         $map_query->condition($destination_id, $destination_id_values[$field_name]);
@@ -783,7 +797,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
       $this->eventDispatcher->dispatch(MigrateEvents::MAP_DELETE, new MigrateMapDeleteEvent($this, $source_id_values));
       $map_query->execute();
 
-      $message_query->condition(static::SOURCE_IDS_HASH, $this->getSourceIDsHash($source_id_values));
+      $message_query->condition(static::SOURCE_IDS_HASH, $this->getSourceIdsHash($source_id_values));
       $message_query->execute();
     }
   }
@@ -923,4 +937,70 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
     return $this->currentRow !== FALSE;
   }
 
+  /**
+   * Returns the migration plugin manager.
+   *
+   * @todo Inject as a dependency in https://www.drupal.org/node/2919158.
+   *
+   * @return \Drupal\migrate\Plugin\MigrationPluginManagerInterface
+   *   The migration plugin manager.
+   */
+  protected function getMigrationPluginManager() {
+    return \Drupal::service('plugin.manager.migration');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getHighestId() {
+    array_filter(
+      $this->migration->getDestinationPlugin()->getIds(),
+      function (array $id) {
+        if ($id['type'] !== 'integer') {
+          throw new \LogicException('Cannot determine the highest migrated ID without an integer ID column');
+        }
+      }
+    );
+
+    // List of mapping tables to look in for the highest ID.
+    $map_tables = [
+      $this->migration->id() => $this->mapTableName(),
+    ];
+
+    // If there's a bundle, it means we have a derived migration and we need to
+    // find all the mapping tables from the related derived migrations.
+    if ($base_id = substr($this->migration->id(), 0, strpos($this->migration->id(), static::DERIVATIVE_SEPARATOR))) {
+      $migration_manager = $this->getMigrationPluginManager();
+      $migrations = $migration_manager->getDefinitions();
+      foreach ($migrations as $migration_id => $migration) {
+        if ($migration['id'] === $base_id) {
+          // Get this derived migration's mapping table and add it to the list
+          // of mapping tables to look in for the highest ID.
+          $stub = $migration_manager->createInstance($migration_id);
+          $map_tables[$migration_id] = $stub->getIdMap()->mapTableName();
+        }
+      }
+    }
+
+    // Get the highest id from the list of map tables.
+    $ids = [0];
+    foreach ($map_tables as $map_table) {
+      // If the map_table does not exist then continue on to the next map_table.
+      if (!$this->getDatabase()->schema()->tableExists($map_table)) {
+        continue;
+      }
+
+      $query = $this->getDatabase()->select($map_table, 'map')
+        ->fields('map', $this->destinationIdFields())
+        ->range(0, 1);
+      foreach (array_values($this->destinationIdFields()) as $order_field) {
+        $query->orderBy($order_field, 'DESC');
+      }
+      $ids[] = $query->execute()->fetchField();
+    }
+
+    // Return the highest of all the mapped IDs.
+    return (int) max($ids);
+  }
+
 }