SU-SWS/stanford_migrate

View on GitHub
src/StanfordMigrate.php

Summary

Maintainability
A
2 hrs
Test Coverage
A
96%
<?php

namespace Drupal\stanford_migrate;

use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Installer\InstallerKernel;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Messenger\MessengerTrait;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\migrate\MigrateMessage;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
use Drupal\migrate\Plugin\MigrationInterface as MigrationPluginInterface;
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
use Drupal\migrate\Plugin\RequirementsInterface;
use Drupal\migrate_plus\Entity\MigrationInterface as MigrationEntityInterface;
use Drupal\migrate_tools\MigrateExecutable;
use Drupal\node\NodeInterface;

/**
 * Stanford Migrate service to do various migration actions.
 */
class StanfordMigrate implements StanfordMigrateInterface {

  use MessengerTrait;

  /**
   * Logger channel service.
   *
   * @var \Drupal\Core\Logger\LoggerChannelInterface
   */
  protected $logger;

  /**
   * Flag to execute any migrations using batch process.
   *
   * @var bool
   */
  protected $batchExecuteMigrations = FALSE;

  /**
   * Already executed migrations.
   *
   * @var string[]
   */
  protected $executedMigrations = [];


  /**
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migrationPluginManager
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
   */
  public function __construct(protected EntityTypeManagerInterface $entityTypeManager, protected MigrationPluginManagerInterface $migrationPluginManager, LoggerChannelFactoryInterface $logger_factory) {
    $this->logger = $logger_factory->get('stanford_migrate');
  }

  /**
   * {@inheritDoc}
   */
  public function setBatchExecution(bool $batch_execution): self {
    $this->batchExecuteMigrations = $batch_execution;
    return $this;
  }

  /**
   * {@inheritDoc}
   */
  public function executeMigrationId(string $migration_id): void {
    $migrations = $this->getMigrationList();
    foreach ($migrations as $migration_list) {
      if (isset($migration_list[$migration_id])) {
        $this->executeMigration($migration_list[$migration_id], $migration_id);
        return;
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  public function executeMigration(MigrationPluginInterface $migration, string $migration_id, array $options = []): void {

    // Reset migration status so that it can be executed again.
    $migration->interruptMigration(MigrationPluginInterface::RESULT_STOPPED);
    $migration->setStatus(MigrationPluginInterface::STATUS_IDLE);

    // Keep track of all migrations run during this command so the same
    // migration is not run multiple times.
    $executed_migrations = &$this->executedMigrations;

    // Execute all the required migrations first before running this one.
    $definition = $migration->getPluginDefinition();
    $required_migrations = $definition['migration_dependencies']['required'] ?? [];

    $required_migrations = array_filter($required_migrations, function ($value) use ($executed_migrations) {
      return !isset($executed_migrations[$value]);
    });

    if (!empty($required_migrations)) {
      $required_migrations = $this->migrationPluginManager->createInstances($required_migrations);
      $dependency_options = array_merge($options, ['is_dependency' => TRUE]);
      array_walk($required_migrations, [
        $this,
        'executeMigration',
      ], $dependency_options);
      $executed_migrations += $required_migrations;
    }

    // Finally run this migration.

    $log = new MigrateMessage();

    if ($this->batchExecuteMigrations) {
      $executable = new StanfordMigrateBatchExecutable($migration, $log, $options);
      $executable->batchImport();
    }
    else {
      $executable = new MigrateExecutable($migration, $log, $options);
      $executable->import();
    }

    $executed_migrations[$migration_id] = $migration_id;
  }

  /**
   * {@inheritDoc}
   */
  public function getMigrationList(): array {
    $migrations = [];
    // Don't run the migrations when drupal is being installed.
    if (InstallerKernel::installationAttempted()) {
      return $migrations;
    }

    $matched_migrations = $this->migrationPluginManager->createInstances(array_keys($this->getMigrationEntities()));
    // Do not return any migrations which fail to meet requirements.
    foreach ($matched_migrations as $id => $migration) {
      if ($migration->getSourcePlugin() instanceof RequirementsInterface) {
        try {
          $migration->getSourcePlugin()->checkRequirements();
        }
        catch (RequirementsException $e) {
          $this->logger->error('Unable to execute migration @name: @message', [
            '@name' => $migration->label(),
            '@message' => $e->getMessage(),
          ]);
          unset($matched_migrations[$id]);
        }
      }
    }

    // Sort the matched migrations by group.
    /** @var \Drupal\migrate\Plugin\Migration $migration */
    foreach ($matched_migrations as $id => $migration) {
      $definition = $migration->getPluginDefinition();
      $configured_group_id = $definition['migration_group'] ?? 'default';
      $migrations[$configured_group_id][$id] = $migration;
    }

    return $migrations;
  }


  /**
   * Get the migration that imported the given node.
   *
   * @param \Drupal\node\NodeInterface $node
   *   Node entity.
   *
   * @return \Drupal\migrate_plus\Entity\MigrationInterface|null
   *   Migration entity or null if none found.
   */
  public function getNodesMigration(NodeInterface $node): ?MigrationEntityInterface {
    // Use a static variable so that it doesn't look up the migrations multiple
    // times.
    $node_migration = &drupal_static(__CLASS__ . __FUNCTION__ . '_' . $node->id());

    if (!is_null($node_migration)) {
      // If the given node was previously looked up, but it doesn't exist from a
      // migration, it's value will be false. But we have to return NULL due to
      // the return type declaration.
      return $node_migration ?: NULL;
    }
    // Set the static to false and check for null above. If the first attempt to
    // find a migration doesn't show anything, we don't want to continue
    // checking.
    $node_migration = FALSE;

    $migrations = $this->getMigrationEntities();

    // Loop through the migration entities, build their migration plugins so
    // that we can dig into their source mapping data.
    foreach ($migrations as $migration) {

      // This migration entity has methods that allow easy queries on the
      // migrate_map tables.
      /** @var \Drupal\migrate\Plugin\MigrationInterface $migrate */
      $migrate = $this->migrationPluginManager->createInstance($migration->id());

      // CSV Imported content can be ignored since it's normally a one time thing.
      if (!$migrate || $migrate->getSourcePlugin()->getPluginId() == 'csv') {
        continue;
      }

      $destination_ids = $migrate->getDestinationPlugin()->getIds();

      // Ignore any migrate plugin that doesn't map to nodes.
      if (isset($destination_ids['nid'])) {

        // If the migrate id map returns something, that means this node is tied
        // to this migration. Set the static variable for later references and
        // get out of here.
        $row_data = $migrate->getIdMap()
          ->getRowByDestination(['nid' => $node->id()]);
        if (!empty($row_data) && $row_data['source_row_status'] != MigrateIdMapInterface::STATUS_IGNORED) {
          $node_migration = $migration;
          return $migration;
        }
      }
    }
    return NULL;
  }

  /**
   * {@inheritDoc}
   */
  public function deleteEntityFromMigration(EntityInterface $entity): void {
    foreach ($this->getMigrationList() as $migrations) {
      foreach ($migrations as $migration) {
        $destination = $migration->getDestinationConfiguration();

        // It should always be set. but this is just a safety valve.
        if (!isset($destination['plugin'])) {
          continue;
        }

        if (
          str_starts_with($destination['plugin'], 'entity:') ||
          str_starts_with($destination['plugin'], 'entity_reference_revisions:')
        ) {

          [, $type] = explode(':', $destination['plugin']);

          if ($type == $entity->getEntityTypeId()) {
            $lookup_values = [];
            $lookup_ids = array_keys($migration->getDestinationPlugin()
              ->getIds());

            foreach ($lookup_ids as $id_key) {
              $lookup_values[$id_key] = $entity->get($id_key)->getString();
            }
            $migration->getIdMap()->deleteDestination($lookup_values);
          }
        }
      }
    }
  }

  /**
   * Get the migration entities.
   *
   * @return \Drupal\migrate_plus\Entity\MigrationInterface[]
   *   Keyed array of entities.
   */
  protected function getMigrationEntities(): array {
    // Load only migrations that are enabled because when getting the migration
    // plugins, it will only create instances from active entities.
    $storage = $this->entityTypeManager->getStorage('migration');
    return $storage->loadByProperties(['status' => 1]);
  }

}