Chris@0: array( Chris@0: * // An array of migration IDs that must be run before this migration. Chris@0: * ), Chris@0: * 'optional' => array( Chris@0: * // An array of migration IDs that, if they exist, must be run before Chris@0: * // this migration. Chris@0: * ), Chris@0: * ); Chris@0: * @endcode Chris@0: * Chris@0: * @var array Chris@0: */ Chris@0: protected $migration_dependencies = []; Chris@0: Chris@0: /** Chris@0: * The migration's configuration dependencies. Chris@0: * Chris@0: * These store any dependencies on modules or other configuration (including Chris@0: * other migrations) that must be available before the migration can be Chris@0: * created. Chris@0: * Chris@0: * @see \Drupal\Core\Config\Entity\ConfigDependencyManager Chris@0: * Chris@0: * @var array Chris@0: */ Chris@0: protected $dependencies = []; Chris@0: Chris@0: /** Chris@0: * The migration plugin manager for loading other migration plugins. Chris@0: * Chris@0: * @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface Chris@0: */ Chris@0: protected $migrationPluginManager; Chris@0: Chris@0: /** Chris@0: * The source plugin manager. Chris@0: * Chris@0: * @var \Drupal\migrate\Plugin\MigratePluginManager Chris@0: */ Chris@0: protected $sourcePluginManager; Chris@0: Chris@0: /** Chris@17: * The process plugin manager. Chris@0: * Chris@0: * @var \Drupal\migrate\Plugin\MigratePluginManager Chris@0: */ Chris@0: protected $processPluginManager; Chris@0: Chris@0: /** Chris@0: * The destination plugin manager. Chris@0: * Chris@0: * @var \Drupal\migrate\Plugin\MigrateDestinationPluginManager Chris@0: */ Chris@0: protected $destinationPluginManager; Chris@0: Chris@0: /** Chris@0: * The ID map plugin manager. Chris@0: * Chris@0: * @var \Drupal\migrate\Plugin\MigratePluginManager Chris@0: */ Chris@0: protected $idMapPluginManager; Chris@0: Chris@0: /** Chris@0: * Labels corresponding to each defined status. Chris@0: * Chris@0: * @var array Chris@0: */ Chris@0: protected $statusLabels = [ Chris@0: self::STATUS_IDLE => 'Idle', Chris@0: self::STATUS_IMPORTING => 'Importing', Chris@0: self::STATUS_ROLLING_BACK => 'Rolling back', Chris@0: self::STATUS_STOPPING => 'Stopping', Chris@0: self::STATUS_DISABLED => 'Disabled', Chris@0: ]; Chris@0: Chris@0: /** Chris@0: * Constructs a Migration. Chris@0: * Chris@0: * @param array $configuration Chris@0: * Plugin configuration. Chris@0: * @param string $plugin_id Chris@0: * The plugin ID. Chris@0: * @param mixed $plugin_definition Chris@0: * The plugin definition. Chris@0: * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager Chris@0: * The migration plugin manager. Chris@0: * @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $source_plugin_manager Chris@0: * The source migration plugin manager. Chris@0: * @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $process_plugin_manager Chris@0: * The process migration plugin manager. Chris@0: * @param \Drupal\migrate\Plugin\MigrateDestinationPluginManager $destination_plugin_manager Chris@0: * The destination migration plugin manager. Chris@0: * @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $idmap_plugin_manager Chris@0: * The ID map migration plugin manager. Chris@0: */ Chris@0: public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationPluginManagerInterface $migration_plugin_manager, MigratePluginManagerInterface $source_plugin_manager, MigratePluginManagerInterface $process_plugin_manager, MigrateDestinationPluginManager $destination_plugin_manager, MigratePluginManagerInterface $idmap_plugin_manager) { Chris@0: parent::__construct($configuration, $plugin_id, $plugin_definition); Chris@0: $this->migrationPluginManager = $migration_plugin_manager; Chris@0: $this->sourcePluginManager = $source_plugin_manager; Chris@0: $this->processPluginManager = $process_plugin_manager; Chris@0: $this->destinationPluginManager = $destination_plugin_manager; Chris@0: $this->idMapPluginManager = $idmap_plugin_manager; Chris@0: Chris@0: foreach (NestedArray::mergeDeep($plugin_definition, $configuration) as $key => $value) { Chris@0: $this->$key = $value; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { Chris@0: return new static( Chris@0: $configuration, Chris@0: $plugin_id, Chris@0: $plugin_definition, Chris@0: $container->get('plugin.manager.migration'), Chris@0: $container->get('plugin.manager.migrate.source'), Chris@0: $container->get('plugin.manager.migrate.process'), Chris@0: $container->get('plugin.manager.migrate.destination'), Chris@0: $container->get('plugin.manager.migrate.id_map') Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function id() { Chris@0: return $this->pluginId; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function label() { Chris@0: return $this->label; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets any arbitrary property's value. Chris@0: * Chris@0: * @param string $property Chris@0: * The property to retrieve. Chris@0: * Chris@0: * @return mixed Chris@0: * The value for that property, or NULL if the property does not exist. Chris@0: * Chris@0: * @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.x. Use Chris@0: * more specific getters instead. Chris@0: * Chris@0: * @see https://www.drupal.org/node/2873795 Chris@0: */ Chris@0: public function get($property) { Chris@18: @trigger_error('\Drupal\migrate\Plugin\Migration::get() is deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.x. Use more specific getters instead. See https://www.drupal.org/node/2873795', E_USER_DEPRECATED); Chris@0: return isset($this->$property) ? $this->$property : NULL; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Retrieves the ID map plugin. Chris@0: * Chris@0: * @return \Drupal\migrate\Plugin\MigrateIdMapInterface Chris@0: * The ID map plugin. Chris@0: */ Chris@0: public function getIdMapPlugin() { Chris@0: return $this->idMapPlugin; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getSourcePlugin() { Chris@0: if (!isset($this->sourcePlugin)) { Chris@0: $this->sourcePlugin = $this->sourcePluginManager->createInstance($this->source['plugin'], $this->source, $this); Chris@0: } Chris@0: return $this->sourcePlugin; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getProcessPlugins(array $process = NULL) { Chris@0: if (!isset($process)) { Chris@0: $process = $this->getProcess(); Chris@0: } Chris@0: $index = serialize($process); Chris@0: if (!isset($this->processPlugins[$index])) { Chris@0: $this->processPlugins[$index] = []; Chris@0: foreach ($this->getProcessNormalized($process) as $property => $configurations) { Chris@0: $this->processPlugins[$index][$property] = []; Chris@0: foreach ($configurations as $configuration) { Chris@0: if (isset($configuration['source'])) { Chris@0: $this->processPlugins[$index][$property][] = $this->processPluginManager->createInstance('get', $configuration, $this); Chris@0: } Chris@0: // Get is already handled. Chris@0: if ($configuration['plugin'] != 'get') { Chris@0: $this->processPlugins[$index][$property][] = $this->processPluginManager->createInstance($configuration['plugin'], $configuration, $this); Chris@0: } Chris@0: if (!$this->processPlugins[$index][$property]) { Chris@0: throw new MigrateException("Invalid process configuration for $property"); Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: return $this->processPlugins[$index]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Resolve shorthands into a list of plugin configurations. Chris@0: * Chris@0: * @param array $process Chris@0: * A process configuration array. Chris@0: * Chris@0: * @return array Chris@0: * The normalized process configuration. Chris@0: */ Chris@0: protected function getProcessNormalized(array $process) { Chris@0: $normalized_configurations = []; Chris@0: foreach ($process as $destination => $configuration) { Chris@0: if (is_string($configuration)) { Chris@0: $configuration = [ Chris@0: 'plugin' => 'get', Chris@0: 'source' => $configuration, Chris@0: ]; Chris@0: } Chris@0: if (isset($configuration['plugin'])) { Chris@0: $configuration = [$configuration]; Chris@0: } Chris@0: $normalized_configurations[$destination] = $configuration; Chris@0: } Chris@0: return $normalized_configurations; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getDestinationPlugin($stub_being_requested = FALSE) { Chris@0: if ($stub_being_requested && !empty($this->destination['no_stub'])) { Chris@17: throw new MigrateSkipRowException('Stub requested but not made because no_stub configuration is set.'); Chris@0: } Chris@0: if (!isset($this->destinationPlugin)) { Chris@0: $this->destinationPlugin = $this->destinationPluginManager->createInstance($this->destination['plugin'], $this->destination, $this); Chris@0: } Chris@0: return $this->destinationPlugin; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getIdMap() { Chris@0: if (!isset($this->idMapPlugin)) { Chris@0: $configuration = $this->idMap; Chris@0: $plugin = isset($configuration['plugin']) ? $configuration['plugin'] : 'sql'; Chris@0: $this->idMapPlugin = $this->idMapPluginManager->createInstance($plugin, $configuration, $this); Chris@0: } Chris@0: return $this->idMapPlugin; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function checkRequirements() { Chris@0: // Check whether the current migration source and destination plugin Chris@0: // requirements are met or not. Chris@0: if ($this->getSourcePlugin() instanceof RequirementsInterface) { Chris@0: $this->getSourcePlugin()->checkRequirements(); Chris@0: } Chris@0: if ($this->getDestinationPlugin() instanceof RequirementsInterface) { Chris@0: $this->getDestinationPlugin()->checkRequirements(); Chris@0: } Chris@0: Chris@0: if (empty($this->requirements)) { Chris@0: // There are no requirements to check. Chris@0: return; Chris@0: } Chris@0: /** @var \Drupal\migrate\Plugin\MigrationInterface[] $required_migrations */ Chris@0: $required_migrations = $this->getMigrationPluginManager()->createInstances($this->requirements); Chris@0: Chris@0: $missing_migrations = array_diff($this->requirements, array_keys($required_migrations)); Chris@0: // Check if the dependencies are in good shape. Chris@0: foreach ($required_migrations as $migration_id => $required_migration) { Chris@0: if (!$required_migration->allRowsProcessed()) { Chris@0: $missing_migrations[] = $migration_id; Chris@0: } Chris@0: } Chris@0: if ($missing_migrations) { Chris@0: throw new RequirementsException('Missing migrations ' . implode(', ', $missing_migrations) . '.', ['requirements' => $missing_migrations]); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the migration plugin manager. Chris@0: * Chris@0: * @return \Drupal\migrate\Plugin\MigratePluginManager Chris@0: * The plugin manager. Chris@0: */ Chris@0: protected function getMigrationPluginManager() { Chris@0: return $this->migrationPluginManager; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setStatus($status) { Chris@0: \Drupal::keyValue('migrate_status')->set($this->id(), $status); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getStatus() { Chris@0: return \Drupal::keyValue('migrate_status')->get($this->id(), static::STATUS_IDLE); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getStatusLabel() { Chris@0: $status = $this->getStatus(); Chris@0: if (isset($this->statusLabels[$status])) { Chris@0: return $this->statusLabels[$status]; Chris@0: } Chris@0: else { Chris@0: return ''; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getInterruptionResult() { Chris@0: return \Drupal::keyValue('migrate_interruption_result')->get($this->id(), static::RESULT_INCOMPLETE); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function clearInterruptionResult() { Chris@0: \Drupal::keyValue('migrate_interruption_result')->delete($this->id()); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function interruptMigration($result) { Chris@0: $this->setStatus(MigrationInterface::STATUS_STOPPING); Chris@0: \Drupal::keyValue('migrate_interruption_result')->set($this->id(), $result); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function allRowsProcessed() { Chris@0: $source_count = $this->getSourcePlugin()->count(); Chris@0: // If the source is uncountable, we have no way of knowing if it's Chris@0: // complete, so stipulate that it is. Chris@0: if ($source_count < 0) { Chris@0: return TRUE; Chris@0: } Chris@0: $processed_count = $this->getIdMap()->processedCount(); Chris@0: // We don't use == because in some circumstances (like unresolved stubs Chris@0: // being created), the processed count may be higher than the available Chris@0: // source rows. Chris@0: return $source_count <= $processed_count; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function set($property_name, $value) { Chris@0: if ($property_name == 'source') { Chris@0: // Invalidate the source plugin. Chris@0: unset($this->sourcePlugin); Chris@0: } Chris@0: elseif ($property_name === 'destination') { Chris@0: // Invalidate the destination plugin. Chris@0: unset($this->destinationPlugin); Chris@0: } Chris@0: $this->{$property_name} = $value; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getProcess() { Chris@0: return $this->getProcessNormalized($this->process); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setProcess(array $process) { Chris@0: $this->process = $process; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setProcessOfProperty($property, $process_of_property) { Chris@0: $this->process[$property] = $process_of_property; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function mergeProcessOfProperty($property, array $process_of_property) { Chris@0: // If we already have a process value then merge the incoming process array Chris@0: // otherwise simply set it. Chris@0: $current_process = $this->getProcess(); Chris@0: if (isset($current_process[$property])) { Chris@0: $this->process = NestedArray::mergeDeepArray([$current_process, $this->getProcessNormalized([$property => $process_of_property])], TRUE); Chris@0: } Chris@0: else { Chris@0: $this->setProcessOfProperty($property, $process_of_property); Chris@0: } Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function isTrackLastImported() { Chris@0: return $this->trackLastImported; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setTrackLastImported($track_last_imported) { Chris@0: $this->trackLastImported = (bool) $track_last_imported; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getMigrationDependencies() { Chris@0: $this->migration_dependencies = ($this->migration_dependencies ?: []) + ['required' => [], 'optional' => []]; Chris@0: $this->migration_dependencies['optional'] = array_unique(array_merge($this->migration_dependencies['optional'], $this->findMigrationDependencies($this->process))); Chris@0: return $this->migration_dependencies; Chris@0: } Chris@0: Chris@0: /** Chris@16: * Find migration dependencies from migration_lookup and sub_process plugins. Chris@0: * Chris@16: * @param array $process Chris@16: * A process configuration array. Chris@16: * Chris@0: * @return array Chris@16: * The migration dependencies. Chris@0: */ Chris@0: protected function findMigrationDependencies($process) { Chris@0: $return = []; Chris@0: foreach ($this->getProcessNormalized($process) as $process_pipeline) { Chris@0: foreach ($process_pipeline as $plugin_configuration) { Chris@16: if (in_array($plugin_configuration['plugin'], ['migration', 'migration_lookup'], TRUE)) { Chris@0: $return = array_merge($return, (array) $plugin_configuration['migration']); Chris@0: } Chris@16: if (in_array($plugin_configuration['plugin'], ['iterator', 'sub_process'], TRUE)) { Chris@0: $return = array_merge($return, $this->findMigrationDependencies($plugin_configuration['process'])); Chris@0: } Chris@0: } Chris@0: } Chris@0: return $return; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getPluginDefinition() { Chris@0: $definition = []; Chris@0: // While normal plugins do not change their definitions on the fly, this Chris@0: // one does so accommodate for that. Chris@0: foreach (parent::getPluginDefinition() as $key => $value) { Chris@0: $definition[$key] = isset($this->$key) ? $this->$key : $value; Chris@0: } Chris@0: return $definition; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getDestinationConfiguration() { Chris@0: return $this->destination; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getSourceConfiguration() { Chris@0: return $this->source; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getTrackLastImported() { Chris@0: return $this->trackLastImported; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getDestinationIds() { Chris@0: return $this->destinationIds; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getMigrationTags() { Chris@0: return $this->migration_tags; Chris@0: } Chris@0: Chris@14: /** Chris@14: * {@inheritdoc} Chris@14: */ Chris@14: public function isAuditable() { Chris@14: return (bool) $this->audit; Chris@14: } Chris@14: Chris@0: }