You are here

class Migration in CMS Content Sync 2.0.x

Same name and namespace in other branches
  1. 2.1.x src/Controller/Migration.php \Drupal\cms_content_sync\Controller\Migration

Migration Embed provides methods and a UI to migrate from Content Sync v1 to Content Sync v2.

Hierarchy

Expanded class hierarchy of Migration

13 files declare their use of Migration
CopyRemoteFlow.php in src/Form/CopyRemoteFlow.php
EntityHandlerBase.php in src/Plugin/EntityHandlerBase.php
Flow.php in src/Entity/Flow.php
FlowForm.php in src/Form/FlowForm.php
MigrationForm.php in src/Form/MigrationForm.php

... See full list

File

src/Controller/Migration.php, line 18

Namespace

Drupal\cms_content_sync\Controller
View source
class Migration extends ControllerBase {
  public const STEP_EXPORT_POOLS = 'export-pools';
  public const STEP_EXPORT_FLOWS = 'export-flows';
  public const STEP_TEST_MANUAL_PUSH = 'push-content-manually';
  public const STEP_TEST_AUTOMATED_PULL = 'pull-content-automatically';
  public const STEP_TEST_MANUAL_PULL = 'pull-content-manually';

  // TODO: Add this step to the migration as an automated step that creates one migration entity per Flow of this site. Allow users to view the progress and retry if anything fails (optimized).

  //const STEP_TEST_PUSH_ALL = 'push-all-content';
  public const STEP_SWITCH = 'switch';
  public const STEP_DONE = 'done';
  public static $pool_statuses = null;
  protected static $flow_statuses = [];
  public static function useV2($set_runtime = null) {
    static $value = null;
    if (null !== $set_runtime) {
      $value = $set_runtime;

      // Clear cache as the Pools with now return a different Sync Core URL.
      SyncCoreFactory::clearCache();
      foreach (Pool::getAll() as $pool) {
        $pool
          ->getClient(true);
      }
    }
    if (null === $value) {
      $value = self::alwaysUseV2();
    }
    return $value;
  }
  public static function alwaysUseV2() {
    static $value = null;

    // TODO: When switching, cache statically that we're using v2. When a user
    //   creates a Flow with v2 and there is no other Flow, set the value
    //   statically as well to cover new installations. We can't set it on
    //   module install though as customers who import flow and pool config
    //   afterwards would incorrectly use v2 and we might mess up deployments.
    if (null === $value) {
      $active_step = self::getActiveStep(true);
      $value = self::STEP_DONE === $active_step;
    }
    return $value;
  }
  public static function didMigrate() {
    return self::alwaysUseV2() && !empty(\Drupal::service('config.factory')
      ->get('cms_content_sync.migration')
      ->get('cms_content_sync_v2_pool_statuses'));
  }
  public static function getPoolStatus($pool) {
    if (!self::$pool_statuses) {
      self::$pool_statuses = \Drupal::service('config.factory')
        ->get('cms_content_sync.migration')
        ->get('cms_content_sync_v2_pool_statuses');
      if (null === self::$pool_statuses) {
        self::$pool_statuses = [];
      }
    }
    if (!isset(self::$pool_statuses[$pool->id])) {
      if (!$pool->backend_url) {
        return Pool::V2_STATUS_ACTIVE;
      }
      return Pool::V2_STATUS_NONE;
    }
    return self::$pool_statuses[$pool->id];
  }
  public static function entityUsedV2(string $flow, string $type, string $bundle, ?string $uuid, ?string $shared_id, bool $pushed) {
    $statuses = self::getStoredFlowStatus($flow);
    foreach ($statuses['types'] as &$status) {
      if ($status['namespaceMachineName'] === $type && $status['machineName'] === $bundle) {
        $status[$pushed ? 'pushedEntity' : 'pulledEntity'] = [
          'remoteUuid' => $uuid,
          'remoteUniqueId' => $shared_id,
          'verified' => false,
        ];
        break;
      }
    }
    self::setFlowStatus($flow, $statuses);
  }
  public static function getFullFlowStatus($flow) {
    $status = self::getFlowStatus($flow);
    if (Flow::V2_STATUS_EXPORTED === $status) {
      return self::getStoredFlowStatus($flow->id);
    }
    return [
      'exported' => Flow::V2_STATUS_ACTIVE === $status,
      'active' => Flow::V2_STATUS_ACTIVE === $status,
      'skipTest' => false,
      'skipPush' => false,
      'skipPull' => false,
      'types' => [],
    ];
  }
  public static function getFlowStatus($flow) {
    $status = self::getStoredFlowStatus($flow->id);
    if (empty($status) || !isset($status['active'])) {
      foreach ($flow
        ->getUsedPools() as $pool) {
        if ((bool) $pool->backend_url) {
          return Flow::V2_STATUS_NONE;
        }
      }
      return Flow::V2_STATUS_ACTIVE;
    }
    if ($status['active']) {
      return Flow::V2_STATUS_ACTIVE;
    }
    return Flow::V2_STATUS_EXPORTED;
  }
  public static function getActiveStep($avoid_recursion = false) {
    $pools = Pool::getAll();
    foreach ($pools as $pool) {
      if (!$pool
        ->v2Ready()) {
        return self::STEP_EXPORT_POOLS;
      }
    }
    $flows = Flow::getAll();
    foreach ($flows as $flow) {
      if (!$flow
        ->v2Ready()) {
        return self::STEP_EXPORT_FLOWS;
      }
    }
    if ($avoid_recursion) {
      foreach ($pools as $pool) {
        if ($pool->backend_url) {
          return self::STEP_SWITCH;
        }
      }
      return self::STEP_DONE;
    }
    foreach ($pools as $pool) {
      if (!$pool
        ->useV2()) {
        return self::STEP_SWITCH;
      }
    }
    return self::STEP_DONE;
  }
  public static function runPoolExport($machine_names = null) {
    self::useV2(true);
    foreach (Pool::getAll() as $pool) {
      if ($machine_names ? in_array($pool
        ->id(), $machine_names) : !$pool
        ->v2Ready()) {
        $exporter = new SyncCorePoolExport($pool);
        $batch = $exporter
          ->prepareBatch(true);
        $batch
          ->executeAll();
        self::setPoolV2Ready($pool);
      }
    }
    \Drupal::messenger()
      ->addMessage(t('Successfully exported your Pools to the new Sync Core v2.'));
  }
  public static function runFlowExport($machine_names = null) {
    self::useV2(true);

    // TODO: Use batch operation.
    foreach (Flow::getAll() as $flow) {
      if ($machine_names ? in_array($flow
        ->id(), $machine_names) : !$flow
        ->v2Ready()) {
        $exporter = new SyncCoreFlowExport($flow, true);
        $batch = $exporter
          ->prepareBatch();
        $batch
          ->executeAll();
        self::setFlowV2Ready($flow);
      }
    }
    \Drupal::messenger()
      ->addMessage(t('Successfully exported your Flows to the new Sync Core v2.'));
  }
  public static function skipFlowsTest($machine_names) {
    self::useV2(true);

    // TODO: Use batch operation.
    foreach (Flow::getAll() as $flow) {
      if (in_array($flow
        ->id(), $machine_names)) {
        $status = self::getStoredFlowStatus($flow
          ->id());
        $status['skipTest'] = true;
        self::setFlowStatus($flow
          ->id(), $status);
      }
    }
    \Drupal::messenger()
      ->addMessage(t('Successfully skipped testing your Flow(s).'));
  }
  public static function skipFlowsPush($machine_names) {
    self::useV2(true);

    // TODO: Use batch operation.
    foreach (Flow::getAll() as $flow) {
      if (in_array($flow
        ->id(), $machine_names)) {
        $status = self::getStoredFlowStatus($flow
          ->id());
        $status['skipPush'] = true;
        self::setFlowStatus($flow
          ->id(), $status);
      }
    }
    \Drupal::messenger()
      ->addMessage(t('Successfully skipped pushing your Flow(s).'));
  }
  public static function skipFlowsPull($machine_names) {
    self::useV2(true);

    // TODO: Use batch operation.
    foreach (Flow::getAll() as $flow) {
      if (in_array($flow
        ->id(), $machine_names)) {
        $status = self::getStoredFlowStatus($flow
          ->id());
        $status['skipPull'] = true;
        self::setFlowStatus($flow
          ->id(), $status);
      }
    }
    \Drupal::messenger()
      ->addMessage(t('Successfully skipped mapping your Flow(s).'));
  }
  public static function runSwitch() {
    self::useV2(true);
    foreach (Pool::getAll() as $pool) {
      self::setPoolV2Ready($pool, true);
    }
    \Drupal::messenger()
      ->addMessage(t('Successfully switched to always use the new Sync Core v2. Congratulations!'));
  }
  public static function runActiveStep() {
    $step = self::getActiveStep();
    if (self::STEP_EXPORT_POOLS === $step) {
      self::runPoolExport();
    }
    elseif (self::STEP_EXPORT_FLOWS === $step) {
      self::runFlowExport();
    }
    elseif (self::STEP_SWITCH === $step) {
      self::runSwitch();
    }
    else {
      throw new \Exception("This migration step can't be automated.");
    }
  }
  public static function getSteps() {
    return [
      self::STEP_EXPORT_POOLS => t('Export pools'),
      self::STEP_EXPORT_FLOWS => t('Export flows'),
      // TODO:

      //self::STEP_TEST_MANUAL_PUSH => t("Push manually"),

      //self::STEP_TEST_AUTOMATED_PULL => t("Push automatically"),

      //self::STEP_TEST_MANUAL_PULL => t("Pull manually"),
      self::STEP_SWITCH => t('Switch over'),
      self::STEP_DONE => t('Done'),
    ];
  }
  public static function getStepDescriptions() {
    return [
      self::STEP_EXPORT_POOLS => [
        'status' => t('All pool configuration will be exported to the new Sync Core. You can still syndicate content using the old Sync Core.'),
      ],
      self::STEP_EXPORT_FLOWS => [
        'status' => t('All flow configuration will be exported to the new Sync Core. You can still syndicate content using the old Sync Core.'),
      ],
      self::STEP_TEST_MANUAL_PUSH => [
        'status' => t("Please install the 'Content Sync Health' submodule, then go to Content > Sync health > Entity status. Select one entity per Flow and use the 'Migrate to V2' action. You can still syndicate content using the old Sync Core."),
        'warning' => t('This will push the content and trigger updates on remote sites as usual.'),
      ],
      self::STEP_TEST_AUTOMATED_PULL => [
        'status' => t("Please migrate your other sites first; make sure to use the 'Migrate to V2' action on an entity that is configured with 'Pull: All' on this site. You can still syndicate content using the old Sync Core."),
        'warning' => t('This will make changes on the content you pull.'),
      ],
      self::STEP_TEST_MANUAL_PULL => [
        'status' => t("Please migrate your other sites first; make sure to use the 'Migrate to V2' action on an entity that is configured with 'Pull: Manually' on this site. You can still syndicate content using the old Sync Core."),
        'warning' => t('This will make changes on the content you pull.'),
      ],
      self::STEP_SWITCH => [
        'status' => t('Switch over'),
        'warning' => t("This action can't be undone. The old Sync Core will no longer have access to this site and all future push and pull operations to/from this site will be done by the new Sync Core."),
      ],
      self::STEP_DONE => [
        'status' => t('Congratulations! You have finished migrating your site to v2. Enjoy our new features and let us know if anything is missing!'),
      ],
    ];
  }
  public static function getAutomatedSteps() {
    return [
      self::STEP_EXPORT_POOLS,
      self::STEP_EXPORT_FLOWS,
      self::STEP_SWITCH,
    ];
  }
  protected static function setPoolV2Ready($pool, $switch = false) {

    // TODO: Change all usages of config.factory in this class to use local
    //  key value store instead.
    $settings = \Drupal::service('config.factory')
      ->getEditable('cms_content_sync.migration');
    self::$pool_statuses = $settings
      ->get('cms_content_sync_v2_pool_statuses');
    if (!self::$pool_statuses) {
      self::$pool_statuses = [];
    }
    if ($switch) {
      self::$pool_statuses[$pool->id] = Pool::V2_STATUS_ACTIVE;
      unset($pool->backend_url);
      $pool
        ->save();
    }
    else {
      self::$pool_statuses[$pool->id] = Pool::V2_STATUS_EXPORTED;
    }
    $settings
      ->set('cms_content_sync_v2_pool_statuses', self::$pool_statuses)
      ->save();
  }
  protected static function getStoredFlowStatus($id) {
    if (!empty(self::$flow_statuses[$id])) {
      return self::$flow_statuses[$id];
    }
    $status = \Drupal::service('config.factory')
      ->get('cms_content_sync.migration')
      ->get('flow.' . $id);
    if ($status) {
      self::$flow_statuses[$id] = $status;
    }
    return $status;
  }
  protected static function setFlowStatus($id, $status) {
    \Drupal::service('config.factory')
      ->getEditable('cms_content_sync.migration')
      ->set('flow.' . $id, $status)
      ->save();
    self::$flow_statuses[$id] = $status;
  }
  protected static function setFlowV2Ready($flow) {
    $previous = self::getStoredFlowStatus($flow
      ->id()) ?? [];
    $all_types = $flow
      ->getEntityTypeConfig(null, null, true);
    $types = [];
    foreach ($all_types as $config) {
      $type = $config['entity_type_name'];
      $bundle = $config['bundle_name'];
      $version = $config['version'];
      $existing = [];
      if (isset($previous['types'])) {
        foreach ($previous['types'] as $existing_type) {
          if ($existing_type['namespaceMachineName'] === $type && $existing_type['machineName'] === $bundle) {
            $existing = $existing_type;
            break;
          }
        }
      }
      $types[] = [
        'namespaceMachineName' => $type,
        'machineName' => $bundle,
        'versionId' => $version,
        'pushMode' => PushIntent::PUSH_AUTOMATICALLY === $config['export'] ? FlowSyndicationMode::ALL : (PushIntent::PUSH_MANUALLY === $config['export'] ? FlowSyndicationMode::MANUALLY : (PushIntent::PUSH_AS_DEPENDENCY === $config['export'] ? FlowSyndicationMode::DEPENDENT : null)),
        'pullMode' => PullIntent::PULL_AUTOMATICALLY === $config['import'] ? FlowSyndicationMode::ALL : (PullIntent::PULL_MANUALLY === $config['import'] ? FlowSyndicationMode::MANUALLY : (PullIntent::PULL_AS_DEPENDENCY === $config['import'] ? FlowSyndicationMode::DEPENDENT : null)),
        'pushedEntity' => isset($existing['pushedEntity']) ? $existing['pushedEntity'] : null,
        'pulledEntity' => isset($existing['pulledEntity']) ? $existing['pulledEntity'] : null,
        'skipTest' => !empty($existing['skipTest']) ? $existing['skipTest'] : false,
        'skipPush' => !empty($existing['skipPush']) ? $existing['skipPush'] : false,
        'skipPull' => !empty($existing['skipPull']) ? $existing['skipPull'] : false,
      ];
    }
    self::setFlowStatus($flow->id, [
      'exported' => true,
      'active' => false,
      'skipTest' => !empty($previous['skipTest']) ? $previous['skipTest'] : false,
      'skipPush' => !empty($previous['skipPush']) ? $previous['skipPush'] : false,
      'skipPull' => !empty($previous['skipPull']) ? $previous['skipPull'] : false,
      'types' => $types,
    ]);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
ControllerBase::$configFactory protected property The configuration factory.
ControllerBase::$currentUser protected property The current user service. 1
ControllerBase::$entityFormBuilder protected property The entity form builder.
ControllerBase::$entityTypeManager protected property The entity type manager.
ControllerBase::$formBuilder protected property The form builder. 2
ControllerBase::$keyValue protected property The key-value storage. 1
ControllerBase::$languageManager protected property The language manager. 1
ControllerBase::$moduleHandler protected property The module handler. 2
ControllerBase::$stateService protected property The state service.
ControllerBase::cache protected function Returns the requested cache bin.
ControllerBase::config protected function Retrieves a configuration object.
ControllerBase::container private function Returns the service container.
ControllerBase::create public static function Instantiates a new instance of this class. Overrides ContainerInjectionInterface::create 46
ControllerBase::currentUser protected function Returns the current user. 1
ControllerBase::entityFormBuilder protected function Retrieves the entity form builder.
ControllerBase::entityTypeManager protected function Retrieves the entity type manager.
ControllerBase::formBuilder protected function Returns the form builder service. 2
ControllerBase::keyValue protected function Returns a key/value storage collection. 1
ControllerBase::languageManager protected function Returns the language manager service. 1
ControllerBase::moduleHandler protected function Returns the module handler. 2
ControllerBase::redirect protected function Returns a redirect response object for the specified route.
ControllerBase::state protected function Returns the state storage service.
LoggerChannelTrait::$loggerFactory protected property The logger channel factory service.
LoggerChannelTrait::getLogger protected function Gets the logger for a specific channel.
LoggerChannelTrait::setLoggerFactory public function Injects the logger channel factory.
MessengerTrait::$messenger protected property The messenger. 27
MessengerTrait::messenger public function Gets the messenger. 27
MessengerTrait::setMessenger public function Sets the messenger.
Migration::$flow_statuses protected static property
Migration::$pool_statuses public static property
Migration::alwaysUseV2 public static function
Migration::didMigrate public static function
Migration::entityUsedV2 public static function
Migration::getActiveStep public static function
Migration::getAutomatedSteps public static function
Migration::getFlowStatus public static function
Migration::getFullFlowStatus public static function
Migration::getPoolStatus public static function
Migration::getStepDescriptions public static function
Migration::getSteps public static function
Migration::getStoredFlowStatus protected static function
Migration::runActiveStep public static function
Migration::runFlowExport public static function
Migration::runPoolExport public static function
Migration::runSwitch public static function
Migration::setFlowStatus protected static function
Migration::setFlowV2Ready protected static function
Migration::setPoolV2Ready protected static function
Migration::skipFlowsPull public static function
Migration::skipFlowsPush public static function
Migration::skipFlowsTest public static function
Migration::STEP_DONE public constant
Migration::STEP_EXPORT_FLOWS public constant
Migration::STEP_EXPORT_POOLS public constant
Migration::STEP_SWITCH public constant
Migration::STEP_TEST_AUTOMATED_PULL public constant
Migration::STEP_TEST_MANUAL_PULL public constant
Migration::STEP_TEST_MANUAL_PUSH public constant
Migration::useV2 public static function
RedirectDestinationTrait::$redirectDestination protected property The redirect destination service. 1
RedirectDestinationTrait::getDestinationArray protected function Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url.
RedirectDestinationTrait::getRedirectDestination protected function Returns the redirect destination service.
RedirectDestinationTrait::setRedirectDestination public function Sets the redirect destination service.
StringTranslationTrait::$stringTranslation protected property The string translation service. 4
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.