You are here

final class Migrator in Lightning Workflow 8.3

Same name and namespace in other branches
  1. 8.2 modules/lightning_scheduler/src/Migrator.php \Drupal\lightning_scheduler\Migrator

The migration is not an API and should not be extended or re-used.

Hierarchy

Expanded class hierarchy of Migrator

2 files declare their use of Migrator
MigrationCommands.php in modules/lightning_scheduler/src/Commands/MigrationCommands.php
MigrationConfirmationForm.php in modules/lightning_scheduler/src/Form/MigrationConfirmationForm.php
1 string reference to 'Migrator'
lightning_scheduler.services.yml in modules/lightning_scheduler/lightning_scheduler.services.yml
modules/lightning_scheduler/lightning_scheduler.services.yml
1 service uses Migrator
lightning_scheduler.migrator in modules/lightning_scheduler/lightning_scheduler.services.yml
Drupal\lightning_scheduler\Migrator

File

modules/lightning_scheduler/src/Migrator.php, line 21

Namespace

Drupal\lightning_scheduler
View source
final class Migrator {
  use StringTranslationTrait;

  /**
   * The entity type manager service.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The database service.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $database;

  /**
   * The state service.
   *
   * @var \Drupal\Core\State\StateInterface
   */
  protected $state;

  /**
   * The messenger service.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  protected $messenger;

  /**
   * Migrator constructor.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager service.
   * @param \Drupal\Core\Database\Connection $database
   *   The database service.
   * @param \Drupal\Core\State\StateInterface $state
   *   The state service.
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
   *   The messenger service.
   * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
   *   (optional) The string translation service.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, StateInterface $state, MessengerInterface $messenger, TranslationInterface $translation = NULL) {
    $this->entityTypeManager = $entity_type_manager;
    $this->database = $database;
    $this->state = $state;
    $this->messenger = $messenger;
    if ($translation) {
      $this
        ->setStringTranslation($translation);
    }
  }

  /**
   * Returns the entity types still needing migration.
   *
   * @return string[]
   *   The entity type IDs that still need to be migrated.
   */
  public function getMigrations() {
    return $this->state
      ->get('lightning_scheduler.migrations', []);
  }

  /**
   * Sets the entity types still needing migration.
   *
   * @param string[] $migrations
   *   The entity type IDs that still need to be migrated.
   */
  public function setMigrations(array $migrations) {
    return $this->state
      ->set('lightning_scheduler.migrations', $migrations);
  }

  /**
   * Returns all content entity types which need to be migrated.
   *
   * @param string[] $limit
   *   (optional) An array of entity type IDs. If given, only those entity types
   *   will be considered.
   *
   * @return \Drupal\Core\Entity\EntityTypeInterface[]
   *   The entity types that need to be migrated.
   */
  public function getEntityTypesToMigrate(array $limit = []) {

    // Only content entities which have been marked as needing migration are
    // considered.
    $filter = function (EntityTypeInterface $entity_type) {
      return $entity_type
        ->entityClassImplements(ContentEntityInterface::class);
    };
    $entity_types = array_filter($this->entityTypeManager
      ->getDefinitions(), $filter);
    $migrations = $limit ?: $this
      ->getMigrations();
    $migrations = array_flip($migrations);
    return array_intersect_key($entity_types, $migrations);
  }

  /**
   * Queries for all entities of a specific type which need to be migrated.
   *
   * @param string $entity_type_id
   *   The entity type ID.
   *
   * @return \Drupal\Core\Entity\Query\QueryInterface
   *   The prepared query.
   */
  public function query($entity_type_id) {
    $entity_type = $this->entityTypeManager
      ->getDefinition($entity_type_id);
    $fields = [
      $entity_type
        ->getKey('id'),
      'scheduled_publication',
      'scheduled_moderation_state',
    ];
    if ($entity_type
      ->isRevisionable()) {
      array_push($fields, $entity_type
        ->getKey('revision'));
    }
    if ($entity_type
      ->isTranslatable()) {
      array_push($fields, $entity_type
        ->getKey('langcode'));
    }
    $table = $entity_type
      ->getRevisionDataTable() ?: $entity_type
      ->getDataTable();
    return $this->database
      ->select($table)
      ->fields($table, $fields)
      ->isNotNull('scheduled_publication')
      ->isNotNull('scheduled_moderation_state');
  }

  /**
   * Migrates all entities of a specific type.
   *
   * @param string $entity_type_id
   *   The entity type ID to migrate.
   * @param callable $callback
   *   (optional) A callback to invoke after each item is migrated. It receives
   *   the entity type ID, the number of items migrated so far, the item itself
   *   (which will be an \stdClass object), and the migrator service.
   *
   * @return int
   *   The number of items that were migrated.
   */
  public function migrateAll($entity_type_id, callable $callback = NULL) {
    $items = $this
      ->query($entity_type_id)
      ->execute();
    $count = 0;
    foreach ($items as $item) {
      $this
        ->migrate($entity_type_id, $item);
      if ($callback) {
        $callback($entity_type_id, ++$count, $item, $this);
      }
    }
    $this
      ->completeMigration($entity_type_id);
    return $count;
  }

  /**
   * Migrates a single entity.
   *
   * @param string $entity_type_id
   *   The entity type ID.
   * @param object $item
   *   The relevant entity information, as returned from query(). This will
   *   include the entity ID and values of the scheduled_moderation_state and
   *   scheduled_publication fields. Will also include the revision ID and
   *   langcode, if the entity type is revisionable and translatable,
   *   respectively.
   */
  public function migrate($entity_type_id, $item) {
    $storage = $this->entityTypeManager
      ->getStorage($entity_type_id);
    $entity = $this
      ->load($storage, $item);

    // A horrible hack to work around Content Moderation's opinions being a
    // little too strong. See lightning_scheduler_entity_presave().
    $entity->existingRevisionId = $entity
      ->getRevisionId();
    $entity
      ->set('scheduled_transition_date', $item->scheduled_publication)
      ->set('scheduled_transition_state', $item->scheduled_moderation_state)->original
      ->set('scheduled_transition_date', NULL)
      ->set('scheduled_transition_state', NULL);
    $storage
      ->save($entity);
  }

  /**
   * Purges the data for a single base field field on a single entity type.
   *
   * Only entity types with SQL storage are supported.
   *
   * @param string $entity_type_id
   *   The entity type ID.
   * @param string $field_name
   *   The name of the field to purge data.
   */
  public function purge($entity_type_id, $field_name) {
    $storage = $this->entityTypeManager
      ->getStorage($entity_type_id);
    if ($storage instanceof SqlEntityStorageInterface) {
      $table_mapping = $storage
        ->getTableMapping();
      $values = [];
      foreach ($table_mapping
        ->getColumnNames($field_name) as $column) {
        $values[$column] = NULL;
      }
      $this->database
        ->update($table_mapping
        ->getFieldTableName($field_name))
        ->fields($values)
        ->execute();
    }
  }

  /**
   * Loads an entity to be migrated.
   *
   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
   *   The entity storage handler.
   * @param object $item
   *   The relevant entity information. See ::migrate() for details.
   *
   * @return \Drupal\Core\Entity\ContentEntityInterface
   *   The loaded entity, with $entity->original set.
   */
  protected function load(EntityStorageInterface $storage, $item) {
    $entity_type = $storage
      ->getEntityType();
    $id_key = $entity_type
      ->getKey('id');
    if ($entity_type
      ->isRevisionable()) {
      $vid_key = $entity_type
        ->getKey('revision');
    }
    if ($entity_type
      ->isTranslatable()) {
      $language_key = $entity_type
        ->getKey('langcode');
    }

    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
    $entity = isset($vid_key) ? $storage
      ->loadRevision($item->{$vid_key}) : $storage
      ->load($item->{$id_key});
    if (isset($language_key)) {
      $entity = $entity
        ->getTranslation($item->{$language_key});
    }
    $entity->original = $storage
      ->loadUnchanged($item->{$id_key});
    return $entity;
  }

  /**
   * Completes the migration for a single entity type.
   *
   * If the scheduled_publication or scheduled_moderation_state fields have
   * been overridden, those overrides will be deleted in order to revert the
   * fields and a message will be displayed reminding the user to remove those
   * fields from their exported config.
   *
   * @param string $entity_type_id
   *   The entity type ID.
   */
  public function completeMigration($entity_type_id) {
    $storage = $this->entityTypeManager
      ->getStorage('base_field_override');
    $overridden_fields = $storage
      ->getQuery()
      ->condition('entity_type', $entity_type_id)
      ->condition('field_name', [
      'scheduled_publication',
      'scheduled_moderation_state',
    ])
      ->execute();
    if ($overridden_fields) {
      $overridden_fields = $storage
        ->loadMultiple($overridden_fields);
      $storage
        ->delete($overridden_fields);
      $message = $this
        ->t('Overridden scheduled_publication and scheduled_moderation_state fields were detected. They have been reverted, but you must remember to remove them from your exported config.');
      $this->messenger
        ->addWarning($message);
    }

    // Base fields have been reverted, and the migration is now complete.
    $migrations = array_diff($this
      ->getMigrations(), [
      $entity_type_id,
    ]);
    $this
      ->setMigrations($migrations);
  }

  /**
   * Generates a message to be shown before migrating a set of entity types.
   *
   * @param \Drupal\Core\Entity\EntityTypeInterface[] $entity_types
   *   The entity types that will be migrated.
   * @param bool $html
   *   Whether to include HTML tags in the message.
   *
   * @return \Drupal\Core\StringTranslation\TranslatableMarkup
   *   The generated message.
   */
  public function generatePreMigrationMessage(array $entity_types, $html = TRUE) {
    $entity_types = $this
      ->entityTypeOptions($entity_types);
    $variables = [
      '@entity_types' => Element::oxford($entity_types),
      ':maintenance_mode' => Url::fromRoute('system.site_maintenance_mode')
        ->toString(),
    ];
    return $html ? $this
      ->t('You are about to migrate scheduled transitions for all @entity_types. This will modify your existing content and may take a long time if you have a huge site with tens of thousands of pieces of content. <strong>You cannot undo this</strong>, so you may want to <strong>back up your database</strong> and <a href=":maintenance_mode">switch to maintenance mode</a> before continuing.', $variables) : $this
      ->t('You are about to migrate scheduled transitions for all @entity_types. This will modify your existing content and may take a long time if you have a huge site with tens of thousands of pieces of content. You cannot undo this, so you may want to back up your database and switch to maintenance mode before continuing.', $variables);
  }

  /**
   * Converts a set of entity type definitions to key/value options.
   *
   * @param \Drupal\Core\Entity\EntityTypeInterface[] $entity_types
   *   The entity type definitions.
   *
   * @return string[]
   *   The entity type labels, keyed by entity type ID.
   */
  public function entityTypeOptions(array $entity_types) {
    $to_option = function (EntityTypeInterface $entity_type) {
      return $entity_type
        ->getPluralLabel();
    };
    return array_map($to_option, $entity_types);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
Migrator::$database protected property The database service.
Migrator::$entityTypeManager protected property The entity type manager service.
Migrator::$messenger protected property The messenger service.
Migrator::$state protected property The state service.
Migrator::completeMigration public function Completes the migration for a single entity type.
Migrator::entityTypeOptions public function Converts a set of entity type definitions to key/value options.
Migrator::generatePreMigrationMessage public function Generates a message to be shown before migrating a set of entity types.
Migrator::getEntityTypesToMigrate public function Returns all content entity types which need to be migrated.
Migrator::getMigrations public function Returns the entity types still needing migration.
Migrator::load protected function Loads an entity to be migrated.
Migrator::migrate public function Migrates a single entity.
Migrator::migrateAll public function Migrates all entities of a specific type.
Migrator::purge public function Purges the data for a single base field field on a single entity type.
Migrator::query public function Queries for all entities of a specific type which need to be migrated.
Migrator::setMigrations public function Sets the entity types still needing migration.
Migrator::__construct public function Migrator constructor.
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.