Initial Drupal 11 with DDEV setup

This commit is contained in:
gluebox
2025-10-08 11:39:17 -04:00
commit 89ef74b305
25344 changed files with 2599172 additions and 0 deletions

View File

@ -0,0 +1,85 @@
<?php
namespace Drupal\migrate_drupal\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines a field plugin annotation object.
*
* Field plugins are responsible for handling the migration of custom fields
* (provided by Field API in Drupal 7) to Drupal 8. They are allowed to alter
* fieldable entity migrations when these migrations are being generated, and
* can compute destination field types for individual fields during the actual
* migration process.
*
* Plugin Namespace: Plugin\migrate\field
*
* @Annotation
*/
class MigrateField extends Plugin {
/**
* {@inheritdoc}
*/
public function __construct($values) {
parent::__construct($values);
// Provide default value for core property, in case it's missing.
if (empty($this->definition['core'])) {
$this->definition['core'] = [6];
}
}
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* Map of D6 and D7 field types to D8 field type plugin IDs.
*
* @var string[]
*/
public $type_map = [];
/**
* The Drupal core version(s) this plugin applies to.
*
* @var int[]
*/
public $core;
/**
* Identifies the system providing the data the field plugin will read.
*
* The source_module is expected to be the name of a Drupal module that must
* be installed in the source database.
*
* @var string
*/
public $source_module;
/**
* Identifies the system handling the data the destination plugin will write.
*
* The destination_module is expected to be the name of a Drupal module on the
* destination site that must be installed.
*
* @var string
*/
public $destination_module;
/**
* The weight of this plugin relative to other plugins.
*
* The weight of this plugin relative to other plugins servicing the same
* field type and core version. The lowest weighted applicable plugin will be
* used for each field.
*
* @var int
*/
public $weight = 0;
}

View File

@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace Drupal\migrate_drupal\Attribute;
use Drupal\Component\Plugin\Attribute\Plugin;
/**
* Defines a field plugin attribute object.
*
* Field plugins are responsible for handling the migration of custom fields
* (provided by Field API in Drupal 7) to Drupal 8+. They are allowed to alter
* fieldable entity migrations when these migrations are being generated, and
* can compute destination field types for individual fields during the actual
* migration process.
*
* Plugin Namespace: Plugin\migrate\field
*
* For a working example, see
* \Drupal\datetime\Plugin\migrate\field\DateField
*
* @see \Drupal\migrate\Plugin\MigratePluginManager
* @see \Drupal\migrate_drupal\Plugin\MigrateFieldInterface;
* @see \Drupal\migrate_drupal\Plugin\migrate\field\FieldPluginBase
* @see plugin_api
*
* @ingroup migration
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
class MigrateField extends Plugin {
/**
* The plugin definition.
*
* @var array
*/
protected $definition;
/**
* Constructs a migrate field attribute object.
*
* @param string $id
* A unique identifier for the field plugin.
* @param int[] $core
* (optional) The Drupal core version(s) this plugin applies to.
* @param int $weight
* (optional) The weight of this plugin relative to other plugins servicing
* the same field type and core version. The lowest weighted applicable
* plugin will be used for each field.
* @param string[] $type_map
* (optional) Map of D6 and D7 field types to D8+ field type plugin IDs.
* @param string|null $source_module
* (optional) Identifies the system providing the data the field plugin will
* read. The source_module is expected to be the name of a Drupal module
* that must be installed in the source database.
* @param string|null $destination_module
* (optional) Identifies the system handling the data the destination plugin
* will write. The destination_module is expected to be the name of a Drupal
* module on the destination site that must be installed.
* @param class-string|null $deriver
* (optional) The deriver class.
*/
public function __construct(
public readonly string $id,
public readonly array $core = [6],
public readonly int $weight = 0,
public readonly array $type_map = [],
public readonly ?string $source_module = NULL,
public readonly ?string $destination_module = NULL,
public readonly ?string $deriver = NULL,
) {}
}

View File

@ -0,0 +1,344 @@
<?php
namespace Drupal\migrate_drupal;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
use Drupal\migrate\Plugin\RequirementsInterface;
use Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface;
use Psr\Log\LoggerInterface;
/**
* Provides field discovery for Drupal 6 & 7 migrations.
*/
class FieldDiscovery implements FieldDiscoveryInterface {
/**
* An array of already discovered field plugin information.
*
* @var array
*/
protected $fieldPluginCache;
/**
* The field plugin manager.
*
* @var \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface
*/
protected $fieldPluginManager;
/**
* The migration plugin manager.
*
* @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
*/
protected $migrationPluginManager;
/**
* The logger channel service.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* A cache of discovered fields.
*
* It is an array of arrays. If the entity type is bundleable, a third level
* of arrays is added to account for fields discovered at the bundle level.
*
* [{core}][{entity_type}][{bundle}]
*
* @var array
*/
protected $discoveredFieldsCache = [];
/**
* An array of bundle keys, keyed by drupal core version.
*
* In Drupal 6, only nodes were fieldable, and the bundles were called
* 'type_name'. In Drupal 7, everything became entities, and the more
* generic 'bundle' was used.
*
* @var array
*/
protected $bundleKeys = [
FieldDiscoveryInterface::DRUPAL_6 => 'type_name',
FieldDiscoveryInterface::DRUPAL_7 => 'bundle',
];
/**
* An array of source plugin ids, keyed by Drupal core version.
*
* @var array
*/
protected $sourcePluginIds = [
FieldDiscoveryInterface::DRUPAL_6 => 'd6_field_instance',
FieldDiscoveryInterface::DRUPAL_7 => 'd7_field_instance',
];
/**
* An array of supported Drupal core versions.
*
* @var array
*/
protected $supportedCoreVersions = [
FieldDiscoveryInterface::DRUPAL_6,
FieldDiscoveryInterface::DRUPAL_7,
];
/**
* Constructs a FieldDiscovery object.
*
* @param \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface $field_plugin_manager
* The field plugin manager.
* @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager
* The migration plugin manager.
* @param \Psr\Log\LoggerInterface $logger
* The logger channel service.
*/
public function __construct(MigrateFieldPluginManagerInterface $field_plugin_manager, MigrationPluginManagerInterface $migration_plugin_manager, LoggerInterface $logger) {
$this->fieldPluginManager = $field_plugin_manager;
$this->migrationPluginManager = $migration_plugin_manager;
$this->logger = $logger;
}
/**
* {@inheritdoc}
*/
public function addAllFieldProcesses(MigrationInterface $migration) {
$core = $this->getCoreVersion($migration);
$fields = $this->getAllFields($core);
foreach ($fields as $entity_type_id => $bundle) {
$this->addEntityFieldProcesses($migration, $entity_type_id);
}
}
/**
* {@inheritdoc}
*/
public function addEntityFieldProcesses(MigrationInterface $migration, $entity_type_id) {
$core = $this->getCoreVersion($migration);
$fields = $this->getAllFields($core);
if (!empty($fields[$entity_type_id]) && is_array($fields[$entity_type_id])) {
foreach ($fields[$entity_type_id] as $bundle => $fields) {
$this->addBundleFieldProcesses($migration, $entity_type_id, $bundle);
}
}
}
/**
* {@inheritdoc}
*/
public function addBundleFieldProcesses(MigrationInterface $migration, $entity_type_id, $bundle) {
$core = $this->getCoreVersion($migration);
$fields = $this->getAllFields($core);
$plugin_definition = $migration->getPluginDefinition();
if (empty($fields[$entity_type_id][$bundle])) {
return;
}
$bundle_fields = $fields[$entity_type_id][$bundle];
foreach ($bundle_fields as $field_name => $field_info) {
$plugin = $this->getFieldPlugin($field_info['type'], $migration);
if ($plugin) {
$method = $plugin_definition['field_plugin_method'] ?? 'defineValueProcessPipeline';
call_user_func_array([
$plugin,
$method,
], [
$migration,
$field_name,
$field_info,
]);
}
else {
// Default to a get process plugin if this is a value migration.
if ((empty($plugin_definition['field_plugin_method']) || $plugin_definition['field_plugin_method'] === 'defineValueProcessPipeline')) {
$migration->setProcessOfProperty($field_name, $field_name);
}
}
}
}
/**
* Returns the appropriate field plugin for a given field type.
*
* @param string $field_type
* The field type.
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
* The migration to retrieve the plugin for.
*
* @return \Drupal\migrate_drupal\Plugin\MigrateFieldInterface|bool
* The appropriate field plugin to process this field type.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
* @throws \InvalidArgumentException
*/
protected function getFieldPlugin($field_type, MigrationInterface $migration) {
$core = $this->getCoreVersion($migration);
if (!isset($this->fieldPluginCache[$core][$field_type])) {
try {
$plugin_id = $this->fieldPluginManager->getPluginIdFromFieldType($field_type, ['core' => $core], $migration);
$plugin = $this->fieldPluginManager->createInstance($plugin_id, ['core' => $core], $migration);
}
catch (PluginNotFoundException) {
$plugin = FALSE;
}
$this->fieldPluginCache[$core][$field_type] = $plugin;
}
return $this->fieldPluginCache[$core][$field_type];
}
/**
* Gets all field information related to this migration.
*
* @param string $core
* The Drupal core version to get fields for.
*
* @return array
* A multidimensional array of source data from the relevant field instance
* migration, keyed first by entity type, then by bundle and finally by
* field name.
*/
protected function getAllFields($core) {
if (empty($this->discoveredFieldsCache[$core])) {
$this->discoveredFieldsCache[$core] = [];
$source_plugin = $this->getSourcePlugin($core);
foreach ($source_plugin as $row) {
/** @var \Drupal\migrate\Row $row */
if ($core === FieldDiscoveryInterface::DRUPAL_7) {
$entity_type_id = $row->get('entity_type');
}
else {
$entity_type_id = 'node';
}
$bundle = $row->getSourceProperty($this->bundleKeys[$core]);
$this->discoveredFieldsCache[$core][$entity_type_id][$bundle][$row->getSourceProperty('field_name')] = $row->getSource();
}
}
return $this->discoveredFieldsCache[$core];
}
/**
* Gets all field information for a particular entity type.
*
* @param string $core
* The Drupal core version.
* @param string $entity_type_id
* The legacy entity type ID.
*
* @return array
* A multidimensional array of source data from the relevant field instance
* migration for the entity type, keyed first by bundle and then by field
* name.
*/
protected function getEntityFields($core, $entity_type_id) {
$fields = $this->getAllFields($core);
if (!empty($fields[$entity_type_id])) {
return $fields[$entity_type_id];
}
return [];
}
/**
* Gets all field information for a particular entity type and bundle.
*
* @param string $core
* The Drupal core version.
* @param string $entity_type_id
* The legacy entity type ID.
* @param string $bundle
* The legacy bundle (or content_type).
*
* @return array
* An array of source data from the relevant field instance migration for
* the bundle, keyed by field name.
*/
protected function getBundleFields($core, $entity_type_id, $bundle) {
$fields = $this->getEntityFields($core, $entity_type_id);
if (!empty($fields[$bundle])) {
return $fields[$bundle];
}
return [];
}
/**
* Gets the source plugin to use to gather field information.
*
* @param string $core
* The Drupal core version.
*
* @return array|\Drupal\migrate\Plugin\MigrateSourceInterface
* The source plugin, or an empty array if none can be found that meets
* requirements.
*/
protected function getSourcePlugin($core) {
$definition = $this->getFieldInstanceStubMigrationDefinition($core);
$source_plugin = $this->migrationPluginManager
->createStubMigration($definition)
->getSourcePlugin();
if ($source_plugin instanceof RequirementsInterface) {
try {
$source_plugin->checkRequirements();
}
catch (RequirementsException $e) {
// If checkRequirements() failed, the source database did not support
// fields (i.e., Field is not installed in D7). Therefore, $fields will
// be empty and below we'll return an empty array. The migration will
// proceed without adding fields.
$this->logger->notice('Field discovery failed for Drupal core version @core. Did this site have the Field module installed? Error: @message', [
'@core' => $core,
'@message' => $e->getMessage(),
]);
return [];
}
}
return $source_plugin;
}
/**
* Provides the stub migration definition for a given Drupal core version.
*
* @param string $core
* The Drupal core version.
*
* @return array
* The stub migration definition.
*/
protected function getFieldInstanceStubMigrationDefinition($core) {
return [
'destination' => ['plugin' => 'null'],
'idMap' => ['plugin' => 'null'],
'source' => [
'ignore_map' => TRUE,
'plugin' => $this->sourcePluginIds[$core],
],
];
}
/**
* Finds the core version of a Drupal migration.
*
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
* The migration.
*
* @return string|bool
* A string representation of the Drupal version, or FALSE.
*
* @throws \InvalidArgumentException
*/
protected function getCoreVersion(MigrationInterface $migration) {
$tags = $migration->getMigrationTags();
if (in_array('Drupal 7', $tags, TRUE)) {
return FieldDiscoveryInterface::DRUPAL_7;
}
elseif (in_array('Drupal 6', $tags, TRUE)) {
return FieldDiscoveryInterface::DRUPAL_6;
}
throw new \InvalidArgumentException("Drupal Core version not found for this migration");
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace Drupal\migrate_drupal;
use Drupal\migrate\Plugin\MigrationInterface;
/**
* Provides field discovery for Drupal 6 & 7 migrations.
*/
interface FieldDiscoveryInterface {
const DRUPAL_6 = '6';
const DRUPAL_7 = '7';
/**
* Adds the field processes to a migration.
*
* This method is used in field migrations to execute the migration process
* alter method specified by the 'field_plugin_method' key of the migration
* for all field plugins applicable to this Drupal to Drupal migration. This
* method is used internally for field, field instance, widget, and formatter
* migrations to allow field plugins to alter the process for these
* migrations.
*
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
* The migration to add process plugins to.
*
* @throws \InvalidArgumentException
*
* @internal
*/
public function addAllFieldProcesses(MigrationInterface $migration);
/**
* Adds the field processes for an entity to a migration.
*
* This method is used in field migrations to execute the migration process
* alter method specified by the 'field_plugin_method' key of the migration
* for all field plugins applicable to this Drupal to Drupal migration. This
* method is used internally for field, field instance, widget, and formatter
* migrations to allow field plugins to alter the process for these
* migrations.
*
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
* The migration to add processes to.
* @param string $entity_type_id
* The legacy entity type to add processes for.
*
* @throws \InvalidArgumentException
*/
public function addEntityFieldProcesses(MigrationInterface $migration, $entity_type_id);
/**
* Adds the field processes for a bundle to a migration.
*
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
* The migration to add processes to.
* @param string $entity_type_id
* The legacy entity type to add processes for.
* @param string $bundle
* The legacy bundle (or content_type) to add processes for.
*
* @throws \InvalidArgumentException
*/
public function addBundleFieldProcesses(MigrationInterface $migration, $entity_type_id, $bundle);
}

View File

@ -0,0 +1,144 @@
<?php
namespace Drupal\migrate_drupal\Hook;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\migrate_drupal\NodeMigrateType;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate\Plugin\RequirementsInterface;
use Drupal\Core\Url;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Hook\Attribute\Hook;
/**
* Hook implementations for migrate_drupal.
*/
class MigrateDrupalHooks {
use StringTranslationTrait;
/**
* Implements hook_help().
*/
#[Hook('help')]
public function help($route_name, RouteMatchInterface $route_match): ?string {
switch ($route_name) {
case 'help.page.migrate_drupal':
$output = '';
$output .= '<h2>' . $this->t('About') . '</h2>';
$output .= '<p>' . $this->t('The Migrate Drupal module provides a framework based on the <a href=":migrate">Migrate module</a> to facilitate migration from a Drupal (6, 7, or 8) site to your website. It does not provide a user interface. For more information, see the <a href=":migrate_drupal">online documentation for the Migrate Drupal module</a>.', [
':migrate' => Url::fromRoute('help.page', [
'name' => 'migrate',
])->toString(),
':migrate_drupal' => 'https://www.drupal.org/documentation/modules/migrate_drupal',
]) . '</p>';
return $output;
}
return NULL;
}
/**
* Implements hook_migration_plugins_alter().
*/
#[Hook('migration_plugins_alter')]
public function migrationPluginsAlter(array &$definitions): void {
$module_handler = \Drupal::service('module_handler');
$migration_plugin_manager = \Drupal::service('plugin.manager.migration');
// This is why the deriver can't do this: the 'd6_taxonomy_vocabulary'
// definition is not available to the deriver as it is running inside
// getDefinitions().
if (isset($definitions['d6_taxonomy_vocabulary'])) {
$vocabulary_migration_definition = [
'source' => [
'ignore_map' => TRUE,
'plugin' => 'd6_taxonomy_vocabulary',
],
'destination' => [
'plugin' => 'null',
],
'idMap' => [
'plugin' => 'null',
],
];
$vocabulary_migration = $migration_plugin_manager->createStubMigration($vocabulary_migration_definition);
$translation_active = $module_handler->moduleExists('content_translation');
try {
$source_plugin = $vocabulary_migration->getSourcePlugin();
if ($source_plugin instanceof RequirementsInterface) {
$source_plugin->checkRequirements();
}
$executable = new MigrateExecutable($vocabulary_migration);
$process = ['vid' => $definitions['d6_taxonomy_vocabulary']['process']['vid']];
foreach ($source_plugin as $row) {
$executable->processRow($row, $process);
$source_vid = $row->getSourceProperty('vid');
$plugin_ids = ['d6_term_node:' . $source_vid, 'd6_term_node_revision:' . $source_vid];
if ($translation_active) {
$plugin_ids[] = 'd6_term_node_translation:' . $source_vid;
}
foreach (array_intersect($plugin_ids, array_keys($definitions)) as $plugin_id) {
// Match the field name derivation in d6_vocabulary_field.yml.
$field_name = substr('field_' . $row->getDestinationProperty('vid'), 0, 32);
// The Forum module is expecting 'taxonomy_forums' as the field name
// for the forum nodes. The 'forum_vocabulary' source property is
// evaluated in Drupal\taxonomy\Plugin\migrate\source\d6\Vocabulary
// and is set to true if the vocabulary vid being migrated is the
// same as the one in the 'forum_nav_vocabulary' variable on the
// source site.
$destination_vid = $row->getSourceProperty('forum_vocabulary') ? 'taxonomy_forums' : $field_name;
$definitions[$plugin_id]['process'][$destination_vid] = 'tid';
}
}
}
catch (RequirementsException $e) {
// This code currently runs whenever the definitions are being loaded
// and if you have a Drupal 7 source site then the requirements will not
// be met for the d6_taxonomy_vocabulary migration.
}
catch (DatabaseExceptionWrapper $e) {
// When the definitions are loaded it is possible the tables will not
// exist.
}
}
if (!$module_handler->moduleExists('node')) {
return;
}
$connection = \Drupal::database();
// We need to get the version of the source database in order to check
// if the classic or complete node tables have been used in a migration.
if (isset($definitions['system_site'])) {
// Use the source plugin of the system_site migration to get the
// database connection.
$migration = $definitions['system_site'];
/** @var \Drupal\migrate\Plugin\migrate\source\SqlBase $source_plugin */
$source_plugin = $migration_plugin_manager->createStubMigration($migration)->getSourcePlugin();
try {
$source_connection = $source_plugin->getDatabase();
$version = NodeMigrateType::getLegacyDrupalVersion($source_connection);
}
catch (\Exception $e) {
\Drupal::messenger()->addError($this->t('Failed to connect to your database server. The server reports the following message: %error.<ul><li>Is the database server running?</li><li>Does the database exist, and have you entered the correct database name?</li><li>Have you entered the correct username and password?</li><li>Have you entered the correct database hostname?</li></ul>', ['%error' => $e->getMessage()]));
}
}
// If this is a complete node migration then for all migrations, except the
// classic node migrations, replace any dependency on a classic node
// migration with a dependency on the complete node migration.
if (NodeMigrateType::getNodeMigrateType($connection, $version ?? FALSE) === NodeMigrateType::NODE_MIGRATE_TYPE_COMPLETE) {
$classic_migration_match = '/d([67])_(node|node_translation|node_revision|node_entity_translation)($|:.*)/';
$replace_with_complete_migration = function (&$value, $key, $classic_migration_match) {
if (is_string($value)) {
$value = preg_replace($classic_migration_match, 'd$1_node_complete$3', $value);
}
};
foreach ($definitions as &$definition) {
$is_node_classic_migration = preg_match($classic_migration_match, $definition['id']);
if (!$is_node_classic_migration && isset($definition['migration_dependencies'])) {
array_walk_recursive($definition['migration_dependencies'], $replace_with_complete_migration, $classic_migration_match);
}
}
}
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Drupal\migrate_drupal;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Symfony\Component\DependencyInjection\Reference;
/**
* Alters container services.
*/
class MigrateDrupalServiceProvider extends ServiceProviderBase {
/**
* {@inheritdoc}
*/
public function alter(ContainerBuilder $container) {
parent::alter($container);
$container->getDefinition('plugin.manager.migration')
->setClass(MigrationPluginManager::class)
->addArgument(new Reference('plugin.manager.migrate.source'))
->addArgument(new Reference('config.factory'));
}
}

View File

@ -0,0 +1,272 @@
<?php
namespace Drupal\migrate_drupal;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\Core\Database\Statement\FetchAs;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\migrate\Plugin\RequirementsInterface;
/**
* Configures the appropriate migrations for a given source Drupal database.
*/
trait MigrationConfigurationTrait {
/**
* The config factory service.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The migration plugin manager service.
*
* @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
*/
protected $migrationPluginManager;
/**
* The state service.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* The follow-up migration tags.
*
* @var string[]
*/
protected $followUpMigrationTags;
/**
* Gets the database connection for the source Drupal database.
*
* @param array $database
* Database array representing the source Drupal database.
*
* @return \Drupal\Core\Database\Connection
* The database connection for the source Drupal database.
*/
protected function getConnection(array $database) {
// Set up the connection.
Database::addConnectionInfo('upgrade', 'default', $database);
$connection = Database::getConnection('default', 'upgrade');
return $connection;
}
/**
* Gets the system data from the system table of the source Drupal database.
*
* @param \Drupal\Core\Database\Connection $connection
* Database connection to the source Drupal database.
*
* @return array
* The system data from the system table of the source Drupal database.
*/
protected function getSystemData(Connection $connection) {
$system_data = [];
try {
$results = $connection->select('system', 's', [
'fetch' => FetchAs::Associative,
])
->fields('s')
->execute();
foreach ($results as $result) {
$system_data[$result['type']][$result['name']] = $result;
}
}
catch (DatabaseExceptionWrapper) {
// The table might not exist for example in tests.
}
return $system_data;
}
/**
* Creates the necessary state entries for SqlBase::getDatabase() to work.
*
* The state entities created here have to exist before migration plugin
* instances are created so that derivers such as
* \Drupal\taxonomy\Plugin\migrate\D6TermNodeDeriver can access the source
* database.
*
* @param array $database
* The source database settings.
* @param string $drupal_version
* The Drupal version.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase::getDatabase()
*/
protected function createDatabaseStateSettings(array $database, $drupal_version) {
$database_state['key'] = 'upgrade';
$database_state['database'] = $database;
$database_state_key = 'migrate_drupal_' . $drupal_version;
$state = $this->getState();
$state->set($database_state_key, $database_state);
$state->set('migrate.fallback_state_key', $database_state_key);
}
/**
* Gets the migrations for import.
*
* @param string $database_state_key
* The state key.
* @param int $drupal_version
* The version of Drupal we're getting the migrations for.
*
* @return \Drupal\migrate\Plugin\MigrationInterface[]
* The migrations for import.
*/
protected function getMigrations($database_state_key, $drupal_version) {
$version_tag = 'Drupal ' . $drupal_version;
/** @var \Drupal\migrate\Plugin\MigrationInterface[] $all_migrations */
$all_migrations = $this->getMigrationPluginManager()->createInstancesByTag($version_tag);
// Unset the node migrations that should not run based on the type of node
// migration. That is, if this is a complete node migration then unset the
// classic node migrations and if this is a classic node migration then
// unset the complete node migrations.
$type = NodeMigrateType::getNodeMigrateType(\Drupal::database(), $drupal_version);
switch ($type) {
case NodeMigrateType::NODE_MIGRATE_TYPE_COMPLETE:
$patterns = '/(d' . $drupal_version . '_node:)|(d' . $drupal_version . '_node_translation:)|(d' . $drupal_version . '_node_revision:)|(d7_node_entity_translation:)/';
break;
case NodeMigrateType::NODE_MIGRATE_TYPE_CLASSIC:
$patterns = '/(d' . $drupal_version . '_node_complete:)/';
break;
}
foreach ($all_migrations as $key => $migrations) {
if (preg_match($patterns, $key)) {
unset($all_migrations[$key]);
}
}
$migrations = [];
foreach ($all_migrations as $migration) {
// Skip migrations tagged with any of the follow-up migration tags. They
// will be derived and executed after the migrations on which they depend
// have been successfully executed.
// @see Drupal\migrate_drupal\Plugin\MigrationWithFollowUpInterface
if (!empty(array_intersect($migration->getMigrationTags(), $this->getFollowUpMigrationTags()))) {
continue;
}
try {
// @todo https://drupal.org/node/2681867 We should be able to validate
// the entire migration at this point.
$source_plugin = $migration->getSourcePlugin();
if ($source_plugin instanceof RequirementsInterface) {
$source_plugin->checkRequirements();
}
$destination_plugin = $migration->getDestinationPlugin();
if ($destination_plugin instanceof RequirementsInterface) {
$destination_plugin->checkRequirements();
}
$migrations[] = $migration;
}
catch (RequirementsException) {
// Migrations which are not applicable given the source and destination
// site configurations (e.g., what modules are enabled) will be silently
// ignored.
}
}
return $migrations;
}
/**
* Returns the follow-up migration tags.
*
* @return string[]
* An array of follow-up migration tags.
*/
protected function getFollowUpMigrationTags() {
if ($this->followUpMigrationTags === NULL) {
$this->followUpMigrationTags = $this->getConfigFactory()
->get('migrate_drupal.settings')
->get('follow_up_migration_tags') ?: [];
}
return $this->followUpMigrationTags;
}
/**
* Determines what version of Drupal the source database contains.
*
* @param \Drupal\Core\Database\Connection $connection
* The database connection object.
*
* @return string|false
* A string representing the major branch of Drupal core (e.g. '6' for
* Drupal 6.x), or FALSE if no valid version is matched.
*/
public static function getLegacyDrupalVersion(Connection $connection) {
// Don't assume because a table of that name exists, that it has the columns
// we're querying. Catch exceptions and report that the source database is
// not Drupal.
// Drupal 5/6/7 can be detected by the schema_version in the system table.
if ($connection->schema()->tableExists('system')) {
try {
$version_string = $connection
->query('SELECT [schema_version] FROM {system} WHERE [name] = :module', [':module' => 'system'])
->fetchField();
}
catch (DatabaseExceptionWrapper) {
// All database errors return FALSE.
}
}
return match (TRUE) {
!isset($version_string) => FALSE,
(int) $version_string >= 6000 => substr($version_string, 0, 1),
(int) $version_string >= 1000 => '5',
default => FALSE,
};
}
/**
* Gets the config factory service.
*
* @return \Drupal\Core\Config\ConfigFactoryInterface
* The config factory service.
*/
protected function getConfigFactory() {
if (!$this->configFactory) {
$this->configFactory = \Drupal::service('config.factory');
}
return $this->configFactory;
}
/**
* Gets the migration plugin manager service.
*
* @return \Drupal\migrate\Plugin\MigrationPluginManagerInterface
* The migration plugin manager service.
*/
protected function getMigrationPluginManager() {
if (!$this->migrationPluginManager) {
$this->migrationPluginManager = \Drupal::service('plugin.manager.migration');
}
return $this->migrationPluginManager;
}
/**
* Gets the state service.
*
* @return \Drupal\Core\State\StateInterface
* The state service.
*/
protected function getState() {
if (!$this->state) {
$this->state = \Drupal::service('state');
}
return $this->state;
}
}

View File

@ -0,0 +1,118 @@
<?php
namespace Drupal\migrate_drupal;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\migrate\Plugin\Exception\BadPluginDefinitionException;
use Drupal\migrate\Plugin\MigrateSourcePluginManager;
use Drupal\migrate\Plugin\MigrationPluginManager as BaseMigrationPluginManager;
/**
* Manages migration plugins.
*
* Analyzes migration definitions to ensure that the source plugin of any
* migration tagged with particular tags ('Drupal 6' or 'Drupal 7' by default)
* defines a source_module property in its plugin annotation. This is done in
* order to support the Migrate Drupal UI, which needs to know which modules
* "own" the data being migrated into Drupal 8, on both the source and
* destination sides.
*
* @todo Enforce the destination_module property too, in
* https://www.drupal.org/project/drupal/issues/2923810.
*/
class MigrationPluginManager extends BaseMigrationPluginManager {
/**
* The Migrate source plugin manager service.
*
* @var \Drupal\migrate\Plugin\MigrateSourcePluginManager
*/
protected $sourceManager;
/**
* The config factory service.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The migration tags which will trigger source_module enforcement.
*
* @var string[]
*/
protected $enforcedSourceModuleTags;
/**
* MigrationPluginManager constructor.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler service.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* The cache backend.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager service.
* @param \Drupal\migrate\Plugin\MigrateSourcePluginManager $source_manager
* The Migrate source plugin manager service.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service.
*/
public function __construct(ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager, MigrateSourcePluginManager $source_manager, ConfigFactoryInterface $config_factory) {
parent::__construct($module_handler, $cache_backend, $language_manager);
$this->sourceManager = $source_manager;
$this->configFactory = $config_factory;
}
/**
* Returns the migration tags that trigger source_module enforcement.
*
* @return string[]
* An array of migration tags that enforce source_module.
*/
protected function getEnforcedSourceModuleTags() {
if ($this->enforcedSourceModuleTags === NULL) {
$this->enforcedSourceModuleTags = $this->configFactory
->get('migrate_drupal.settings')
->get('enforce_source_module_tags') ?: [];
}
return $this->enforcedSourceModuleTags;
}
/**
* {@inheritdoc}
*/
public function processDefinition(&$definition, $plugin_id) {
parent::processDefinition($definition, $plugin_id);
$source_id = $definition['source']['plugin'];
$source_definition = $this->sourceManager->getDefinition($source_id);
// If the source plugin uses annotations, then the 'provider' key is the
// array of providers and the 'providers' key is not defined.
$providers = $source_definition['providers'] ?? $source_definition['provider'];
// Check if the migration has any of the tags that trigger source_module
// enforcement.
$has_enforced_tags = !empty(array_intersect(
$definition['migration_tags'] ?? [],
$this->getEnforcedSourceModuleTags(),
));
// If source_module is not defined in the migration, then check for it in
// the source plugin.
$has_source_module = !empty($definition['source']['source_module'])
|| !empty($source_definition['source_module']);
$requires_migrate_drupal = in_array('migrate_drupal', $providers, TRUE);
if ($requires_migrate_drupal && $has_enforced_tags && !$has_source_module) {
throw new BadPluginDefinitionException($source_id, 'source_module');
}
if (!$requires_migrate_drupal && !$has_enforced_tags && $has_source_module) {
@trigger_error("Setting the source_module property without the expected tags is deprecated in drupal:11.2.0 and will trigger an error in drupal:12.0.0. See https://www.drupal.org/node/3306373", E_USER_DEPRECATED);
}
}
}

View File

@ -0,0 +1,457 @@
<?php
namespace Drupal\migrate_drupal;
use Drupal\Core\Discovery\YamlDiscovery;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Messenger\MessengerTrait;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface;
/**
* Determines the migrate state for all modules enabled on the source.
*
* Retrieves migrate info from *.migrate_drupal.yml files.
*
* Knowing which modules will be upgraded and those that will not is needed by
* anyone upgrading a legacy Drupal version. This service provides that
* information by analyzing the existing migrations and data in
* migrate_drupal.yml files. Modules that are enabled or disabled in the source
* are included in the analysis modules that are uninstalled are ignored.
*
* Deciding the upgrade state of a source module is a complicated task. A
* destination module is not limited in any way to the source modules or the
* current major version destination modules it is providing migrations for. We
* see this in core where the Drupal 6 Menu module is upgraded by having
* migrations in three Drupal 8 modules; menu_link_content, menu_ui and system.
* If migrations for any of those three modules are not complete or if any of
* them are not installed on the destination site then the Drupal 6 Menu module
* cannot be listed as upgraded. If any one of the conditions are not met then
* it should be listed as will not be upgraded.
*
* Another challenge is to ensure that legacy source modules that do not need an
* upgrade path are handled correctly. These will not have migrations but should
* be listed as will be upgraded, which even though there are not migrations
* under the hood, it lets a site admin know that upgrading with this module
* enabled is safe.
*
* There is not enough information in the existing system to determine the
* correct state of the upgrade path for these, and other scenarios.
*
* The solution is for every destination module that is the successor to a
* module built for a legacy Drupal version to declare the state of the upgrade
* path(s) for the module. A module's upgrade path from a previous version may
* consist of one or more migrations sets. Each migration set definition
* consists of a source module supporting a legacy Drupal version, and one or
* more current destination modules. This allows a module to indicate that a
* provided migration set requires additional modules to be enabled in the
* destination.
*
* A migration set can be marked 'finished', which indicates that all
* migrations that are going to be provided by this destination module for this
* migration set have been written and are complete. A migration set may also
* be marked 'not_finished' which indicates that the module either has not
* provided any migrations for the set, or needs to provide additional
* migrations to complete the set. Note that other modules may still provide
* additional finished or not_finished migrations for the same migration set.
*
* Modules inform the upgrade process of the migration sets by adding them to
* their <module_name>.migrate_drupal.yml file.
*
* The <module_name>.migrate_drupal.yml file uses the following structure:
*
* # (optional) List of the source_module/destination_module(s) for the
* # migration sets that this module provides and are complete.
* finished:
* # One or more Drupal legacy version number mappings (i.e. 6 and/or 7).
* 6:
* # A mapping of legacy module machine names to either an array of modules
* # or a single destination module machine name to define this migration
* # set.
* <source_module_1>: <destination_module_1>
* <source_module_2>:
* - <destination_module_1>
* - <destination_module_2>
* 7:
* <source_module_1>: <destination_module_1>
* <source_module_2>:
* - <destination_module_1>
* - <destination_module_2>
* # (optional) List of the migration sets that this module provides, or will be
* # providing, that are incomplete or do not yet exist.
* not_finished:
* 6:
* <source_module_1>: <destination_module_1>
* <source_module_2>:
* - <destination_module_1>
* - <destination_module_2>
*
* Examples:
*
* @code
* finished:
* 6:
* node: node
* 7:
* node: node
* entity_translation: node
* not_finished:
* 7:
* commerce_product: commerce_product
* other_module:
* - other_module
* - further_module
* @endcode
*
* In this example the module has completed the upgrade path for data from the
* Drupal 6 and Drupal 7 Node modules to the Drupal 8 Node module and for data
* from the Drupal 7 Entity Translation module to the Drupal 8 Node module.
*
* @code
* finished:
* 6:
* pirate: pirate
* 7:
* pirate: pirate
* @endcode
*
* The Pirate module does not require an upgrade path. By declaring the upgrade
* finished the Pirate module will be included in the finished list. That is,
* as long as no other module has an entry "pirate: <any module name>' in its
* not_finished section.
*/
class MigrationState {
use MessengerTrait;
use StringTranslationTrait;
/**
* Source module upgrade state when all its migrations are complete.
*
* @var string
*/
const FINISHED = 'finished';
/**
* Source module upgrade state when all its migrations are not complete.
*
* @var string
*/
const NOT_FINISHED = 'not_finished';
/**
* The field plugin manager service.
*
* @var \Drupal\Core\Extension\ModuleHandler
*/
protected $moduleHandler;
/**
* The field plugin manager service.
*
* @var \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface
*/
protected $fieldPluginManager;
/**
* An array of migration states declared for each source migration.
*
* States are keyed by version. Each value is an array keyed by name of the
* source module and the value is an array of all the states declared for this
* source module.
*
* @var array
*/
protected $stateBySource;
/**
* An array of destinations declared for each source migration.
*
* Destinations are keyed by version. Each value is an array keyed by the name
* of the source module and the value is an array of the destination modules.
*
* @var array
*/
protected $declaredBySource;
/**
* An array of migration source and destinations derived from migrations.
*
* The key is the source version and the value is an array where the key is
* the source module and the value is an array of destinations derived from
* migration plugins.
*
* @var array
*/
protected $discoveredBySource;
/**
* An array of migration source and destinations.
*
* Values are derived from migration plugins and declared states. The key is
* the source version and the value is an array where the key is the source
* module and the value is an array of declared or derived destinations.
*
* @var array
*/
protected $destinations = [];
/**
* Array of enabled modules.
*
* @var array
*/
protected $enabledModules = [];
/**
* Construct a new MigrationState object.
*
* @param \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface $fieldPluginManager
* Field plugin manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
* Module handler.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* Messenger service.
* @param \Drupal\Core\StringTranslation\TranslationInterface $stringTranslation
* String translation service.
*/
public function __construct(MigrateFieldPluginManagerInterface $fieldPluginManager, ModuleHandlerInterface $moduleHandler, MessengerInterface $messenger, TranslationInterface $stringTranslation) {
$this->fieldPluginManager = $fieldPluginManager;
$this->moduleHandler = $moduleHandler;
$this->enabledModules = array_keys($this->moduleHandler->getModuleList());
$this->enabledModules[] = 'core';
$this->messenger = $messenger;
$this->stringTranslation = $stringTranslation;
}
/**
* Gets the upgrade states for all enabled source modules.
*
* @param string $version
* The legacy drupal version.
* @param array $source_system_data
* The data from the source site system table.
* @param array $migrations
* An array of migrations.
*
* @return array
* An associative array of data with keys of state, source modules and a
* value which is a comma separated list of destination modules.
*/
public function getUpgradeStates($version, array $source_system_data, array $migrations) {
return $this->buildUpgradeState($version, $source_system_data, $migrations);
}
/**
* Gets migration state information from *.migrate_drupal.yml.
*
* @return array
* An association array keyed by module of the finished and not_finished
* migrations for each module.
* */
protected function getMigrationStates() {
// Always instantiate a new YamlDiscovery object so that we always search on
// the up-to-date list of modules.
$discovery = new YamlDiscovery('migrate_drupal', array_map(function ($value) {
return $value . '/migrations/state';
}, $this->moduleHandler->getModuleDirectories()));
return $discovery->findAll();
}
/**
* Determines migration state for each source module enabled on the source.
*
* If there are no migrations for a module and no declared state the state is
* set to NOT_FINISHED. When a module does not need any migrations, such as
* Overlay, a state of finished is declared in system.migrate_drupal.yml.
*
* If there are migrations for a module the following happens. If the
* destination module is 'core' the state is set to FINISHED. If there are
* any occurrences of 'not_finished' in the *.migrate_drupal.yml information
* for this source module then the state is set to NOT_FINISHED. And finally,
* if there is an occurrence of 'finished' the state is set to FINISHED.
*
* @param string $version
* The legacy drupal version.
* @param array $source_system_data
* The data from the source site system table.
* @param array $migrations
* An array of migrations.
*
* @return array
* An associative array of data with keys of state, source modules and a
* value which is a comma separated list of destination modules.
* Example.
*
* @code
* [
* 'finished' => [
* 'menu' => [
* 'menu_link_content','menu_ui','system'
* ]
* ],
* ]
* @endcode
*/
protected function buildUpgradeState($version, array $source_system_data, array $migrations) {
// Remove core profiles from the system data.
unset($source_system_data['module']['standard'], $source_system_data['module']['minimal']);
$this->buildDiscoveredDestinationsBySource($version, $migrations, $source_system_data);
$this->buildDeclaredStateBySource($version);
$upgrade_state = [];
// Loop through every source module that is enabled on the source site.
foreach ($source_system_data['module'] as $module) {
// The source plugins check requirements requires that all
// source_modules are enabled so do the same here.
if ($module['status']) {
$source_module = $module['name'];
$upgrade_state[$this->getSourceState($version, $source_module)][$source_module] = implode(', ', $this->getDestinationsForSource($version, $source_module));
}
}
foreach ($upgrade_state as $key => $value) {
ksort($upgrade_state[$key]);
}
return $upgrade_state;
}
/**
* Builds migration source and destination module information.
*
* @param string $version
* The legacy Drupal version.
* @param array $migrations
* The discovered migrations.
* @param array $source_system_data
* The data from the source site system table.
*/
protected function buildDiscoveredDestinationsBySource($version, array $migrations, array $source_system_data) {
$discovered_upgrade_paths = [];
$table_data = [];
foreach ($migrations as $migration) {
$migration_id = $migration->getPluginId();
$source_module = $migration->getSourcePlugin()->getSourceModule();
if (!$source_module) {
$this->messenger()
->addError($this->t('Source module not found for @migration_id.', ['@migration_id' => $migration_id]));
}
$destination_module = $migration->getDestinationPlugin()
->getDestinationModule();
if (!$destination_module) {
$this->messenger()
->addError($this->t('Destination module not found for @migration_id.', ['@migration_id' => $migration_id]));
}
if ($source_module && $destination_module) {
$discovered_upgrade_paths[$source_module][] = $destination_module;
$table_data[$source_module][$destination_module][$migration_id] = $migration->label();
}
}
// Add entries for the field plugins to discovered_upgrade_paths.
$definitions = $this->fieldPluginManager->getDefinitions();
foreach ($definitions as $definition) {
// This is not strict so that we find field plugins with an annotation
// where the Drupal core version is an integer and when it is a string.
if (in_array($version, $definition['core'])) {
$source_module = $definition['source_module'];
$destination_module = $definition['destination_module'];
$discovered_upgrade_paths[$source_module][] = $destination_module;
$table_data[$source_module][$destination_module][$definition['id']] = $definition['id'];
}
}
ksort($table_data);
foreach ($table_data as $source_module => $destination_module_info) {
ksort($table_data[$source_module]);
}
$this->discoveredBySource[$version] = array_map('array_unique', $discovered_upgrade_paths);
}
/**
* Gets migration data from *.migrate_drupal.yml sorted by source module.
*
* @param string $version
* The legacy Drupal version.
*/
protected function buildDeclaredStateBySource($version) {
$migration_states = $this->getMigrationStates();
$state_by_source = [];
$dest_by_source = [];
$states = [static::FINISHED, static::NOT_FINISHED];
foreach ($migration_states as $info) {
foreach ($states as $state) {
if (isset($info[$state][$version])) {
foreach ($info[$state][$version] as $source => $destination) {
// Add the state.
$state_by_source[$source][] = $state;
// Add the destination modules.
$dest_by_source += [$source => []];
$dest_by_source[$source] = array_merge($dest_by_source[$source], (array) $destination);
}
}
}
}
$this->stateBySource[$version] = array_map('array_unique', $state_by_source);
$this->declaredBySource[$version] = array_map('array_unique', $dest_by_source);
}
/**
* Tests if a destination exists for the given source module.
*
* @param string $version
* Source version of Drupal.
* @param string $source_module
* Source module.
*
* @return string
* Migration state, either 'finished' or 'not_finished'.
*/
protected function getSourceState($version, $source_module) {
// The state is finished only when no declarations of 'not_finished'
// were found and each destination module is enabled.
if (!$destinations = $this->getDestinationsForSource($version, $source_module)) {
// No discovered or declared state.
return MigrationState::NOT_FINISHED;
}
if (!isset($this->stateBySource[$version][$source_module])) {
// No declared state.
return MigrationState::NOT_FINISHED;
}
if (in_array(MigrationState::NOT_FINISHED, $this->stateBySource[$version][$source_module], TRUE) || !in_array(MigrationState::FINISHED, $this->stateBySource[$version][$source_module], TRUE)) {
return MigrationState::NOT_FINISHED;
}
if (array_diff($destinations, $this->enabledModules)) {
return MigrationState::NOT_FINISHED;
}
return MigrationState::FINISHED;
}
/**
* Get net destinations for source module.
*
* @param string $version
* Source version.
* @param string $source_module
* Source module.
*
* @return array
* Destination modules either declared by {modulename}.migrate_drupal.yml
* files or discovered from migration plugins.
*/
protected function getDestinationsForSource($version, $source_module) {
if (!isset($this->destinations[$version][$source_module])) {
$this->discoveredBySource[$version] += [$source_module => []];
$this->declaredBySource[$version] += [$source_module => []];
$destination = array_unique(array_merge($this->discoveredBySource[$version][$source_module], $this->declaredBySource[$version][$source_module]));
sort($destination);
$this->destinations[$version][$source_module] = $destination;
}
return $this->destinations[$version][$source_module];
}
}

View File

@ -0,0 +1,88 @@
<?php
namespace Drupal\migrate_drupal;
use Drupal\Core\Database\Connection;
use Drupal\Core\Site\Settings;
/**
* Provides a class to determine the type of migration.
*/
final class NodeMigrateType {
use MigrationConfigurationTrait;
/**
* Only the complete node migration map tables are in use.
*/
const NODE_MIGRATE_TYPE_COMPLETE = 'COMPLETE';
/**
* Only the classic node migration map tables are in use.
*/
const NODE_MIGRATE_TYPE_CLASSIC = 'CLASSIC';
/**
* Determines the type of node migration to be used.
*
* The node complete migration is the default. It is not used when there
* are existing tables for dN_node.
*
* @param \Drupal\Core\Database\Connection $connection
* The connection to the target database.
* @param string|false $version
* The Drupal version of the source database, FALSE if it cannot be
* determined.
*
* @return string
* The migrate type.
*
* @internal
*/
public static function getNodeMigrateType(Connection $connection, $version) {
$migrate_node_migrate_type_classic = Settings::get('migrate_node_migrate_type_classic', FALSE);
if ($migrate_node_migrate_type_classic) {
return static::NODE_MIGRATE_TYPE_CLASSIC;
}
$migrate_type = static::NODE_MIGRATE_TYPE_COMPLETE;
if ($version) {
// Create the variable name, 'node_has_rows' or 'node_complete_exists' and
// set it the default value, FALSE.
$node_has_rows = FALSE;
$node_complete_has_rows = FALSE;
// Find out what migrate map tables have rows for the node migrations.
// It is either the classic, 'dN_node', or the complete,
// 'dN_node_complete', or both. This is used to determine which migrations
// are run and if migrations using the node migrations in a
// migration_lookup are altered.
$bases = ['node', 'node_complete'];
$tables = $connection->schema()
->findTables('migrate_map_d' . $version . '_node%');
foreach ($bases as $base) {
$has_rows = $base . '_has_rows';
$base_tables = preg_grep('/^migrate_map_d' . $version . '_' . $base . '_{2}.*$/', $tables);
// Set the has_rows True when a map table has rows with a positive
// count for the matched migration.
foreach ($base_tables as $base_table) {
if ($connection->schema()->tableExists($base_table)) {
$count = $connection->select($base_table)->countQuery()
->execute()->fetchField();
if ($count > 0) {
$$has_rows = TRUE;
break;
}
}
}
}
// Set the node migration type to use.
if ($node_has_rows && !$node_complete_has_rows) {
$migrate_type = static::NODE_MIGRATE_TYPE_CLASSIC;
}
}
return $migrate_type;
}
}

View File

@ -0,0 +1,109 @@
<?php
namespace Drupal\migrate_drupal\Plugin;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Row;
/**
* Provides an interface for all field type plugins.
*/
interface MigrateFieldInterface extends PluginInspectionInterface {
/**
* Apply any custom processing to the field migration.
*
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
* The migration entity.
*/
public function alterFieldMigration(MigrationInterface $migration);
/**
* Apply any custom processing to the field instance migration.
*
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
* The migration entity.
*/
public function alterFieldInstanceMigration(MigrationInterface $migration);
/**
* Apply any custom processing to the field widget migration.
*
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
* The migration entity.
*/
public function alterFieldWidgetMigration(MigrationInterface $migration);
/**
* Apply any custom processing to the field formatter migration.
*
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
* The migration entity.
*/
public function alterFieldFormatterMigration(MigrationInterface $migration);
/**
* Get the field formatter type from the source.
*
* @param \Drupal\migrate\Row $row
* The field being migrated.
*
* @return string
* The field formatter type.
*/
public function getFieldFormatterType(Row $row);
/**
* Get a map between D6 formatters and D8 formatters for this field type.
*
* This is used by static::alterFieldFormatterMigration() in the base class.
*
* @return array
* The keys are D6 formatters and the values are D8 formatters.
*/
public function getFieldFormatterMap();
/**
* Get the field widget type from the source.
*
* @param \Drupal\migrate\Row $row
* The field being migrated.
*
* @return string
* The field widget type.
*/
public function getFieldWidgetType(Row $row);
/**
* Get a map between D6 and D8 widgets for this field type.
*
* @return array
* The keys are D6 field widget types and the values D8 widgets.
*/
public function getFieldWidgetMap();
/**
* Apply any custom processing to the field bundle migrations.
*
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
* The migration entity.
* @param string $field_name
* The field name we're processing the value for.
* @param array $data
* The array of field data from FieldValues::fieldData().
*/
public function defineValueProcessPipeline(MigrationInterface $migration, $field_name, $data);
/**
* Computes the destination type of a migrated field.
*
* @param \Drupal\migrate\Row $row
* The field being migrated.
*
* @return string
* The destination field type.
*/
public function getFieldType(Row $row);
}

View File

@ -0,0 +1,113 @@
<?php
namespace Drupal\migrate_drupal\Plugin;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\migrate\Plugin\Exception\BadPluginDefinitionException;
use Drupal\migrate\Plugin\MigratePluginManager;
use Drupal\migrate\Plugin\MigrationInterface;
/**
* Plugin manager for migrate field plugins.
*
* @see \Drupal\migrate_drupal\Plugin\MigrateFieldInterface
* @see \Drupal\migrate\Attribute\MigrateField
* @see plugin_api
*
* @ingroup migration
*/
class MigrateFieldPluginManager extends MigratePluginManager implements MigrateFieldPluginManagerInterface {
/**
* The default version of core to use for field plugins.
*
* These plugins were initially only built and used for Drupal 6 fields.
* Having been extended for Drupal 7 with a "core" annotation, we fall back to
* Drupal 6 where none exists.
*/
const DEFAULT_CORE_VERSION = 6;
/**
* Get the plugin ID from the field type.
*
* This method determines which field plugin should be used for a given field
* type and Drupal core version, returning the lowest weighted plugin
* supporting the provided core version, and which matches the field type
* either by plugin ID, or in the type_map annotation keys.
*
* @param string $field_type
* The field type being migrated.
* @param array $configuration
* (optional) An array of configuration relevant to the plugin instance.
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
* (optional) The current migration instance.
*
* @return string
* The ID of the plugin for the field type if available.
*
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* If the plugin cannot be determined, such as if the field type is invalid.
*
* @see \Drupal\migrate_drupal\Attribute\MigrateField
*/
public function getPluginIdFromFieldType($field_type, array $configuration = [], ?MigrationInterface $migration = NULL) {
$core = static::DEFAULT_CORE_VERSION;
if (!empty($configuration['core'])) {
$core = $configuration['core'];
}
elseif (!empty($migration->getPluginDefinition()['migration_tags'])) {
foreach ($migration->getPluginDefinition()['migration_tags'] as $tag) {
if ($tag == 'Drupal 7') {
$core = 7;
}
}
}
$definitions = $this->getDefinitions();
foreach ($definitions as $plugin_id => $definition) {
if (in_array($core, $definition['core'])) {
if (array_key_exists($field_type, $definition['type_map']) || $field_type === $plugin_id) {
return $plugin_id;
}
}
}
throw new PluginNotFoundException($field_type);
}
/**
* {@inheritdoc}
*/
public function processDefinition(&$definition, $plugin_id) {
parent::processDefinition($definition, $plugin_id);
foreach (['core', 'source_module', 'destination_module'] as $required_property) {
if (empty($definition[$required_property])) {
throw new BadPluginDefinitionException($plugin_id, $required_property);
}
}
}
/**
* {@inheritdoc}
*/
protected function findDefinitions() {
$definitions = parent::findDefinitions();
$this->sortDefinitions($definitions);
return $definitions;
}
/**
* Sorts a definitions array.
*
* This sorts the definitions array first by the weight column, and then by
* the plugin ID, ensuring a stable, deterministic, and testable ordering of
* plugins.
*
* @param array $definitions
* The definitions array to sort.
*/
protected function sortDefinitions(array &$definitions) {
array_multisort(array_column($definitions, 'weight'), SORT_ASC, SORT_NUMERIC, array_keys($definitions), SORT_ASC, SORT_NATURAL, $definitions);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Drupal\migrate_drupal\Plugin;
use Drupal\migrate\Plugin\MigratePluginManagerInterface;
use Drupal\migrate\Plugin\MigrationInterface;
/**
* Interface implemented by plugin manager for migrate field plugins.
*/
interface MigrateFieldPluginManagerInterface extends MigratePluginManagerInterface {
/**
* Get the plugin ID from the field type.
*
* @param string $field_type
* The field type being migrated.
* @param array $configuration
* (optional) An array of configuration relevant to the plugin instance.
* @param \Drupal\migrate\Plugin\MigrationInterface|null $migration
* (optional) The current migration instance.
*
* @return string
* The ID of the plugin for the field_type if available.
*
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* If the plugin cannot be determined, such as if the field type is invalid.
*/
public function getPluginIdFromFieldType($field_type, array $configuration = [], ?MigrationInterface $migration = NULL);
}

View File

@ -0,0 +1,40 @@
<?php
namespace Drupal\migrate_drupal\Plugin;
/**
* Interface for migrations with follow-up migrations.
*
* Some migrations need to be derived and executed after other migrations have
* been successfully executed. For example, a migration might need to be derived
* based on previously migrated data. For such a case, the migration dependency
* system is not enough since all migrations would still be derived before any
* one of them has been executed.
*
* Those "follow-up" migrations need to be tagged with the "Follow-up migration"
* tag (or any tag in the "follow_up_migration_tags" configuration) and thus
* they won't be derived with the other migrations.
*
* To get those follow-up migrations derived at the right time, the migrations
* on which they depend must implement this interface and generate them in the
* generateFollowUpMigrations() method.
*
* When the migrations implementing this interface have been successfully
* executed, the follow-up migrations will then be derived having access to the
* now migrated data.
*/
interface MigrationWithFollowUpInterface {
/**
* Generates follow-up migrations.
*
* When the migration implementing this interface has been successfully
* executed, this method will be used to generate the follow-up migrations
* which depends on the now migrated data.
*
* @return \Drupal\migrate\Plugin\MigrationInterface[]
* The follow-up migrations.
*/
public function generateFollowUpMigrations();
}

View File

@ -0,0 +1,203 @@
<?php
namespace Drupal\migrate_drupal\Plugin\migrate;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Component\Plugin\PluginBase;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Deriver for entity reference field translations.
*
* A migration will be created for every bundle with at least one entity
* reference field that is configured to point to one of the supported target
* entity types. The migrations will update the entity reference fields with
* values found in the mapping tables of the migrations associated with the
* target types.
*
* Example:
*
* @code
* id: d7_entity_reference_translation
* label: Entity reference translations
* migration_tags:
* - Drupal 7
* - Follow-up migration
* deriver: Drupal\migrate_drupal\Plugin\migrate\EntityReferenceTranslationDeriver
* target_types:
* node:
* - d7_node_translation
* source:
* plugin: empty
* key: default
* target: default
* process: []
* destination:
* plugin: null
* @endcode
*
* In this example, the only supported target type is 'node' and the associated
* migration for the mapping table lookup is 'd7_node_translation'.
*/
class EntityReferenceTranslationDeriver extends DeriverBase implements ContainerDeriverInterface {
use StringTranslationTrait;
/**
* The entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* EntityReferenceTranslationDeriver constructor.
*
* @param string $base_plugin_id
* The base plugin ID.
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
* The entity field manager.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct($base_plugin_id, EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager) {
$this->entityFieldManager = $entity_field_manager;
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$base_plugin_id,
$container->get('entity_field.manager'),
$container->get('entity_type.manager')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
// Get all entity reference fields.
$field_map = $this->entityFieldManager->getFieldMapByFieldType('entity_reference');
foreach ($field_map as $entity_type => $fields) {
foreach ($fields as $field_name => $field) {
foreach ($field['bundles'] as $bundle) {
$field_definitions = $this->entityFieldManager->getFieldDefinitions($entity_type, $bundle);
$target_type = $field_definitions[$field_name]->getSetting('target_type');
// If the field's target type is not supported, skip it.
if (!array_key_exists($target_type, $base_plugin_definition['target_types'])) {
continue;
}
// Key derivatives by entity types and bundles.
$derivative_key = $entity_type . '__' . $bundle;
$derivative = $base_plugin_definition;
$entity_type_definition = $this->entityTypeManager->getDefinition($entity_type);
// Set the migration label.
$derivative['label'] = $this->t('@label (@derivative)', [
'@label' => $base_plugin_definition['label'],
'@derivative' => $derivative_key,
]);
// Set the source plugin.
$derivative['source']['plugin'] = 'content_entity' . PluginBase::DERIVATIVE_SEPARATOR . $entity_type;
if ($entity_type_definition->hasKey('bundle')) {
$derivative['source']['bundle'] = $bundle;
}
// Set the process pipeline.
$id_key = $entity_type_definition->getKey('id');
$derivative['process'][$id_key] = $id_key;
if ($entity_type_definition->isRevisionable()) {
$revision_key = $entity_type_definition->getKey('revision');
$derivative['process'][$revision_key] = $revision_key;
}
if ($entity_type_definition->isTranslatable()) {
$langcode_key = $entity_type_definition->getKey('langcode');
$derivative['process'][$langcode_key] = $langcode_key;
}
// Set the destination plugin.
$derivative['destination']['plugin'] = 'entity' . PluginBase::DERIVATIVE_SEPARATOR . $entity_type;
if ($entity_type_definition->hasKey('bundle')) {
$derivative['destination']['default_bundle'] = $bundle;
}
if ($entity_type_definition->isTranslatable()) {
$derivative['destination']['translations'] = TRUE;
}
// Allow overwriting the entity reference field so we can update its
// values with the ones found in the mapping table.
$derivative['destination']['overwrite_properties'][$field_name] = $field_name;
// Add the entity reference field to the process pipeline.
$derivative['process'][$field_name] = [
'plugin' => 'sub_process',
'source' => $field_name,
'process' => [
'translation_target_id' => [
[
'plugin' => 'migration_lookup',
'source' => 'target_id',
'migration' => $base_plugin_definition['target_types'][$target_type],
'no_stub' => TRUE,
],
[
'plugin' => 'skip_on_empty',
'method' => 'process',
],
[
'plugin' => 'extract',
'index' => [0],
],
],
'target_id' => [
[
'plugin' => 'null_coalesce',
'source' => [
'@translation_target_id',
'target_id',
],
],
],
],
];
if (!isset($this->derivatives[$derivative_key])) {
// If this is a new derivative, add it to the returned derivatives.
$this->derivatives[$derivative_key] = $derivative;
}
else {
// If this is an existing derivative, it means this bundle has more
// than one entity reference field. In that case, we only want to
// add the field to the process pipeline and add it to
// overwrite_properties so it can be overwritten.
$this->derivatives[$derivative_key]['process'] += $derivative['process'];
$this->derivatives[$derivative_key]['destination']['overwrite_properties'] += $derivative['destination']['overwrite_properties'];
}
}
}
}
return $this->derivatives;
}
}

View File

@ -0,0 +1,87 @@
<?php
namespace Drupal\migrate_drupal\Plugin\migrate;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\Plugin\MigrateDestinationPluginManager;
use Drupal\migrate\Plugin\MigratePluginManager;
use Drupal\migrate\Plugin\Migration;
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
use Drupal\migrate_drupal\FieldDiscoveryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Migration plugin class for migrations dealing with field config and values.
*/
class FieldMigration extends Migration implements ContainerFactoryPluginInterface {
/**
* Flag indicating whether the field data has been filled already.
*
* @var bool
*/
protected $init = FALSE;
/**
* The migration field discovery service.
*
* @var \Drupal\migrate_drupal\FieldDiscoveryInterface
*/
protected $fieldDiscovery;
/**
* Constructs a FieldMigration.
*
* @param array $configuration
* Plugin configuration.
* @param string $plugin_id
* The plugin ID.
* @param mixed $plugin_definition
* The plugin definition.
* @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager
* The migration plugin manager.
* @param \Drupal\migrate\Plugin\MigratePluginManager $source_plugin_manager
* The source migration plugin manager.
* @param \Drupal\migrate\Plugin\MigratePluginManager $process_plugin_manager
* The process migration plugin manager.
* @param \Drupal\migrate\Plugin\MigrateDestinationPluginManager $destination_plugin_manager
* The destination migration plugin manager.
* @param \Drupal\migrate\Plugin\MigratePluginManager $id_map_plugin_manager
* The ID map migration plugin manager.
* @param \Drupal\migrate_drupal\FieldDiscoveryInterface $field_discovery
* The migration field discovery service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationPluginManagerInterface $migration_plugin_manager, MigratePluginManager $source_plugin_manager, MigratePluginManager $process_plugin_manager, MigrateDestinationPluginManager $destination_plugin_manager, MigratePluginManager $id_map_plugin_manager, FieldDiscoveryInterface $field_discovery) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration_plugin_manager, $source_plugin_manager, $process_plugin_manager, $destination_plugin_manager, $id_map_plugin_manager);
$this->fieldDiscovery = $field_discovery;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('plugin.manager.migration'),
$container->get('plugin.manager.migrate.source'),
$container->get('plugin.manager.migrate.process'),
$container->get('plugin.manager.migrate.destination'),
$container->get('plugin.manager.migrate.id_map'),
$container->get('migrate_drupal.field_discovery')
);
}
/**
* {@inheritdoc}
*/
public function getProcess() {
if (!$this->init) {
$this->init = TRUE;
$this->fieldDiscovery->addAllFieldProcesses($this);
}
return parent::getProcess();
}
}

View File

@ -0,0 +1,119 @@
<?php
namespace Drupal\migrate_drupal\Plugin\migrate\field;
use Drupal\Core\Plugin\PluginBase;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\MigrateFieldInterface;
/**
* The base class for all field plugins.
*
* @see \Drupal\migrate\Plugin\MigratePluginManager
* @see \Drupal\migrate_drupal\Attribute\MigrateField
* @see \Drupal\migrate_drupal\Plugin\MigrateFieldInterface
* @see plugin_api
*
* @ingroup migration
*/
abstract class FieldPluginBase extends PluginBase implements MigrateFieldInterface {
/**
* {@inheritdoc}
*/
public function alterFieldMigration(MigrationInterface $migration) {
$process[0]['map'][$this->pluginId][$this->pluginId] = $this->pluginId;
$migration->mergeProcessOfProperty('type', $process);
}
/**
* {@inheritdoc}
*/
public function alterFieldInstanceMigration(MigrationInterface $migration) {
// Nothing to do by default with field instances.
}
/**
* {@inheritdoc}
*/
public function alterFieldWidgetMigration(MigrationInterface $migration) {
$process = [];
foreach ($this->getFieldWidgetMap() as $source_widget => $destination_widget) {
$process['type']['map'][$source_widget] = $destination_widget;
}
$migration->mergeProcessOfProperty('options/type', $process);
}
/**
* {@inheritdoc}
*/
public function getFieldFormatterType(Row $row) {
return $row->getSourceProperty('formatter/type');
}
/**
* {@inheritdoc}
*/
public function getFieldFormatterMap() {
return [];
}
/**
* {@inheritdoc}
*/
public function getFieldWidgetType(Row $row) {
return $row->getSourceProperty('widget/type');
}
/**
* {@inheritdoc}
*/
public function getFieldWidgetMap() {
// By default, use the plugin ID for the widget types.
return [
$this->pluginId => $this->pluginId . '_default',
];
}
/**
* {@inheritdoc}
*/
public function alterFieldFormatterMigration(MigrationInterface $migration) {
$process = [];
// Some migrate field plugin IDs are prefixed with 'd6_' or 'd7_'. Since the
// plugin ID is used in the static map as the module name, we have to remove
// this prefix from the plugin ID.
$plugin_id = preg_replace('/d[67]_/', '', $this->pluginId);
foreach ($this->getFieldFormatterMap() as $source_format => $destination_format) {
$process[0]['map'][$plugin_id][$source_format] = $destination_format;
}
$migration->mergeProcessOfProperty('options/type', $process);
}
/**
* {@inheritdoc}
*/
public function defineValueProcessPipeline(MigrationInterface $migration, $field_name, $data) {
$process = [
'plugin' => 'get',
'source' => $field_name,
];
$migration->mergeProcessOfProperty($field_name, $process);
}
/**
* {@inheritdoc}
*/
public function getFieldType(Row $row) {
$field_type = $row->getSourceProperty('type');
if (isset($this->pluginDefinition['type_map'][$field_type])) {
return $this->pluginDefinition['type_map'][$field_type];
}
else {
return $field_type;
}
}
}

View File

@ -0,0 +1,63 @@
<?php
namespace Drupal\migrate_drupal\Plugin\migrate\field;
use Drupal\migrate\Plugin\MigrationInterface;
/**
* Base class for Drupal reference fields.
*/
abstract class ReferenceBase extends FieldPluginBase {
/**
* Gets the plugin ID for the reference type migration.
*
* The reference type migration will be added as a required dependency.
*
* @return string
* The plugin id.
*/
abstract protected function getEntityTypeMigrationId();
/**
* Gets the name of the field property which holds the entity ID.
*
* @return string
* The entity id.
*/
abstract protected function entityId();
/**
* {@inheritdoc}
*/
public function alterFieldInstanceMigration(MigrationInterface $migration) {
parent::alterFieldInstanceMigration($migration);
// Add the reference migration as a required dependency to this migration.
$migration->addRequiredDependencies([$this->getEntityTypeMigrationId()]);
}
/**
* {@inheritdoc}
*/
public function defineValueProcessPipeline(MigrationInterface $migration, $field_name, $data) {
$process = [
'plugin' => 'sub_process',
'source' => $field_name,
'process' => ['target_id' => $this->entityId()],
];
$migration->setProcessOfProperty($field_name, $process);
}
/**
* {@inheritdoc}
*/
public function getFieldWidgetMap() {
return [
$this->pluginId . '_select' => 'options_select',
$this->pluginId . '_buttons' => 'options_buttons',
$this->pluginId . '_autocomplete' => 'entity_reference_autocomplete_tags',
];
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace Drupal\migrate_drupal\Plugin\migrate\field\d6;
// cspell:ignore nodereference
use Drupal\migrate_drupal\Attribute\MigrateField;
use Drupal\migrate_drupal\Plugin\migrate\field\ReferenceBase;
/**
* MigrateField Plugin for Drupal 6 node reference fields.
*
* @internal
*/
#[MigrateField(
id: 'nodereference',
core: [6],
type_map: [
'nodereference' => 'entity_reference',
],
source_module: 'nodereference',
destination_module: 'core',
)]
class NodeReference extends ReferenceBase {
/**
* The plugin ID for the reference type migration.
*
* @var string
*/
protected $nodeTypeMigration = 'd6_node_type';
/**
* {@inheritdoc}
*/
protected function getEntityTypeMigrationId() {
return $this->nodeTypeMigration;
}
/**
* {@inheritdoc}
*/
protected function entityId() {
return 'nid';
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace Drupal\migrate_drupal\Plugin\migrate\field\d6;
// cspell:ignore userreference
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate_drupal\Attribute\MigrateField;
use Drupal\migrate_drupal\Plugin\migrate\field\ReferenceBase;
/**
* MigrateField Plugin for Drupal 6 user reference fields.
*
* @internal
*/
#[MigrateField(
id: 'userreference',
core: [6],
type_map: [
'userreference' => 'entity_reference',
],
source_module: 'userreference',
destination_module: 'core',
)]
class UserReference extends ReferenceBase {
/**
* The plugin ID for the reference type migration.
*
* @var string
*/
protected $userTypeMigration = 'd6_user_role';
/**
* {@inheritdoc}
*/
protected function getEntityTypeMigrationId() {
return $this->userTypeMigration;
}
/**
* {@inheritdoc}
*/
protected function entityId() {
return 'uid';
}
/**
* {@inheritdoc}
*/
public function defineValueProcessPipeline(MigrationInterface $migration, $field_name, $data) {
$process = [
'plugin' => 'sub_process',
'source' => $field_name,
'process' => [
'target_id' => [
'plugin' => 'migration_lookup',
'migration' => 'd6_user',
'source' => 'uid',
],
],
];
$migration->setProcessOfProperty($field_name, $process);
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace Drupal\migrate_drupal\Plugin\migrate\field\d7;
use Drupal\migrate_drupal\Attribute\MigrateField;
use Drupal\migrate_drupal\Plugin\migrate\field\ReferenceBase;
/**
* MigrateField plugin for Drupal 7 node_reference fields.
*/
#[MigrateField(
id: 'node_reference',
core: [7],
type_map: [
'node_reference' => 'entity_reference',
],
source_module: 'node_reference',
destination_module: 'core',
)]
class NodeReference extends ReferenceBase {
/**
* The plugin ID for the reference type migration.
*
* @var string
*/
protected $nodeTypeMigration = 'd7_node_type';
/**
* {@inheritdoc}
*/
protected function getEntityTypeMigrationId() {
return $this->nodeTypeMigration;
}
/**
* {@inheritdoc}
*/
protected function entityId() {
return 'nid';
}
/**
* {@inheritdoc}
*/
public function getFieldFormatterMap() {
return [
'node_reference_default' => 'entity_reference_label',
'node_reference_plain' => 'entity_reference_label',
'node_reference_nid' => 'entity_reference_entity_id',
'node_reference_node' => 'entity_reference_entity_view',
'node_reference_path' => 'entity_reference_label',
];
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace Drupal\migrate_drupal\Plugin\migrate\field\d7;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate_drupal\Attribute\MigrateField;
use Drupal\migrate_drupal\Plugin\migrate\field\ReferenceBase;
/**
* MigrateField plugin for Drupal 7 user_reference fields.
*/
#[MigrateField(
id: 'user_reference',
core: [7],
type_map: [
'user_reference' => 'entity_reference',
],
source_module: 'user_reference',
destination_module: 'core',
)]
class UserReference extends ReferenceBase {
/**
* The plugin ID for the reference type migration.
*
* @var string
*/
protected $userTypeMigration = 'd7_user_role';
/**
* {@inheritdoc}
*/
protected function getEntityTypeMigrationId() {
return $this->userTypeMigration;
}
/**
* {@inheritdoc}
*/
protected function entityId() {
return 'uid';
}
/**
* {@inheritdoc}
*/
public function getFieldFormatterMap() {
return [
'user_reference_default' => 'entity_reference_label',
'user_reference_plain' => 'entity_reference_label',
'user_reference_uid' => 'entity_reference_entity_id',
'user_reference_user' => 'entity_reference_entity_view',
'user_reference_path' => 'entity_reference_label',
];
}
/**
* {@inheritdoc}
*/
public function defineValueProcessPipeline(MigrationInterface $migration, $field_name, $data) {
$process = [
'plugin' => 'sub_process',
'source' => $field_name,
'process' => [
'target_id' => [
'plugin' => 'migration_lookup',
'migration' => 'd7_user',
'source' => 'uid',
],
],
];
$migration->setProcessOfProperty($field_name, $process);
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace Drupal\migrate_drupal\Plugin\migrate\process;
use Drupal\migrate\Attribute\MigrateProcess;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Returns only the nid from migration_lookup on node_complete migration.
*
* It is possible that migration_lookups that use the classic node migrations
* in the migration key have been altered to include the complete node
* migration. The classic node migration and complete node migration have a
* different number of destination keys. This process plugin will ensure that
* when the complete node migration is used in the lookup the nid value is
* returned. This keeps the behavior the same as the classic node migration.
*
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
*/
#[MigrateProcess('node_complete_node_lookup')]
class NodeCompleteNodeLookup extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if (is_array($value) && count($value) === 3) {
return $value[0];
}
return $value;
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace Drupal\migrate_drupal\Plugin\migrate\process;
use Drupal\migrate\Attribute\MigrateProcess;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Returns only the vid from migration_lookup on node_complete migration.
*
* It is possible that migration_lookups that use the classic node migrations
* in the migration key have been altered to include the complete node
* migration. The classic node migration and complete node migration have a
* different number of destination keys. This process plugin will ensure that
* when the complete node migration is used in the lookup the vid value is
* returned. This keeps the behavior the same as the classic node migration.
*
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
*/
#[MigrateProcess('node_complete_node_revision_lookup')]
class NodeCompleteNodeRevisionLookup extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if (is_array($value) && count($value) === 3) {
return $value[1];
}
return $value;
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Drupal\migrate_drupal\Plugin\migrate\process;
use Drupal\migrate\Attribute\MigrateProcess;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Returns nid and langcode from migration_lookup on node_complete migration.
*
* It is possible that migration_lookups that use the classic node migrations
* in the migration key have been altered to include the complete node
* migration. The classic node migration and complete node migration have a
* different number of destination keys. This process plugin will ensure that
* when the complete node migration is used in the lookup the nid and langcode
* values are returned. This keeps the behavior the same as the classic node
* migration.
*
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
*/
#[MigrateProcess('node_complete_node_translation_lookup')]
class NodeCompleteNodeTranslationLookup extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if (is_array($value) && count($value) === 3) {
// If the language is 'und' then the node was not translated.
if ($value[2] === 'und') {
return NULL;
}
unset($value[1]);
return array_values($value);
}
return $value;
}
}

View File

@ -0,0 +1,299 @@
<?php
namespace Drupal\migrate_drupal\Plugin\migrate\source;
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\EntityFieldDefinitionTrait;
use Drupal\migrate\Plugin\migrate\source\SourcePluginBase;
use Drupal\migrate\Plugin\MigrateSourceInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Source plugin to get content entities from the current version of Drupal.
*
* @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use
* \Drupal\migrate\Plugin\migrate\source\ContentEntity instead.
*
* @see https://www.drupal.org/node/3498916
*
* This plugin uses the Entity API to export entity data. If the source entity
* type has custom field storage fields or computed fields, this class will need
* to be extended and the new class will need to load/calculate the values for
* those fields.
*
* Available configuration keys:
* - entity_type: The entity type ID of the entities being exported. This is
* calculated dynamically by the deriver so it is only needed if the deriver
* is not utilized, i.e., a custom source plugin.
* - bundle: (optional) If the entity type is bundleable, only return entities
* of this bundle.
* - include_translations: (optional) Indicates if the entity translations
* should be included, defaults to TRUE.
* - add_revision_id: (optional) Indicates if the revision key is added to the
* source IDs, defaults to TRUE.
*
* Examples:
*
* This will return the default revision for all nodes, from every bundle and
* every translation. The revision key is added to the source IDs.
* @code
* source:
* plugin: content_entity:node
* @endcode
*
* This will return the default revision for all nodes, from every bundle and
* every translation. The revision key is not added to the source IDs.
* @code
* source:
* plugin: content_entity:node
* add_revision_id: false
* @endcode
*
* This will only return nodes of type 'article' in their default language.
* @code
* source:
* plugin: content_entity:node
* bundle: article
* include_translations: false
* @endcode
*
* For additional configuration keys, refer to the parent class:
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*/
class ContentEntity extends SourcePluginBase implements ContainerFactoryPluginInterface {
use EntityFieldDefinitionTrait;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* The entity type bundle info service.
*
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
*/
protected $entityTypeBundleInfo;
/**
* The entity type definition.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $entityType;
/**
* The plugin's default configuration.
*
* @var array
*/
protected $defaultConfiguration = [
'bundle' => NULL,
'include_translations' => TRUE,
'add_revision_id' => TRUE,
];
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info) {
@trigger_error(__CLASS__ . ' is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use \Drupal\migrate\Plugin\migrate\source\ContentEntity instead. See https://www.drupal.org/node/3498916', E_USER_DEPRECATED);
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
if (empty($plugin_definition['entity_type'])) {
throw new InvalidPluginDefinitionException($plugin_id, 'Missing required "entity_type" definition.');
}
$this->entityTypeManager = $entity_type_manager;
$this->entityFieldManager = $entity_field_manager;
$this->entityTypeBundleInfo = $entity_type_bundle_info;
$this->entityType = $this->entityTypeManager->getDefinition($plugin_definition['entity_type']);
if (!$this->entityType instanceof ContentEntityTypeInterface) {
throw new InvalidPluginDefinitionException($plugin_id, sprintf('The entity type (%s) is not supported. The "content_entity" source plugin only supports content entities.', $plugin_definition['entity_type']));
}
if (!empty($configuration['bundle'])) {
if (!$this->entityType->hasKey('bundle')) {
throw new \InvalidArgumentException(sprintf('A bundle was provided but the entity type (%s) is not bundleable.', $plugin_definition['entity_type']));
}
$bundle_info = array_keys($this->entityTypeBundleInfo->getBundleInfo($this->entityType->id()));
if (!in_array($configuration['bundle'], $bundle_info, TRUE)) {
throw new \InvalidArgumentException(sprintf('The provided bundle (%s) is not valid for the (%s) entity type.', $configuration['bundle'], $plugin_definition['entity_type']));
}
}
parent::__construct($configuration + $this->defaultConfiguration, $plugin_id, $plugin_definition, $migration);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, ?MigrationInterface $migration = NULL) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$container->get('entity_type.manager'),
$container->get('entity_field.manager'),
$container->get('entity_type.bundle.info')
);
}
/**
* {@inheritdoc}
*/
public function __toString() {
return (string) $this->entityType->getPluralLabel();
}
/**
* Initializes the iterator with the source data.
*
* @return \Generator
* A data generator for this source.
*/
protected function initializeIterator() {
$ids = $this->query()->execute();
return $this->yieldEntities($ids);
}
/**
* Loads and yields entities, one at a time.
*
* @param array $ids
* The entity IDs.
*
* @return \Generator
* An iterable of the loaded entities.
*/
protected function yieldEntities(array $ids) {
$storage = $this->entityTypeManager
->getStorage($this->entityType->id());
foreach ($ids as $id) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $storage->load($id);
yield $this->toArray($entity);
if ($this->configuration['include_translations']) {
foreach ($entity->getTranslationLanguages(FALSE) as $language) {
yield $this->toArray($entity->getTranslation($language->getId()));
}
}
}
}
/**
* Converts an entity to an array.
*
* Makes all IDs into flat values. All other values are returned as per
* $entity->toArray(), which is a nested array.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity to convert.
*
* @return array
* The entity, represented as an array.
*/
protected function toArray(ContentEntityInterface $entity) {
$return = $entity->toArray();
// This is necessary because the IDs must be flat. They cannot be nested for
// the ID map.
foreach (array_keys($this->getIds()) as $id) {
/** @var \Drupal\Core\TypedData\Plugin\DataType\ItemList $value */
$value = $entity->get($id);
// Force the IDs on top of the previous values.
$return[$id] = $value->first()->getString();
}
return $return;
}
/**
* Query to retrieve the entities.
*
* @return \Drupal\Core\Entity\Query\QueryInterface
* The query.
*/
public function query() {
$query = $this->entityTypeManager
->getStorage($this->entityType->id())
->getQuery()
->accessCheck(FALSE);
if (!empty($this->configuration['bundle'])) {
$query->condition($this->entityType->getKey('bundle'), $this->configuration['bundle']);
}
// Exclude anonymous user account.
if ($this->entityType->id() === 'user' && !empty($this->entityType->getKey('id'))) {
$query->condition($this->entityType->getKey('id'), 0, '>');
}
return $query;
}
/**
* {@inheritdoc}
*/
public function count($refresh = FALSE): int {
// If no translations are included, then a simple query is possible.
if (!$this->configuration['include_translations']) {
return parent::count($refresh);
}
// @todo Determine a better way to retrieve a valid count for translations.
// https://www.drupal.org/project/drupal/issues/2937166
return MigrateSourceInterface::NOT_COUNTABLE;
}
/**
* {@inheritdoc}
*/
protected function doCount() {
return $this->query()->count()->execute();
}
/**
* {@inheritdoc}
*/
public function fields() {
// Retrieving fields from a non-fieldable content entity will throw a
// LogicException. Return an empty list of fields instead.
if (!$this->entityType->entityClassImplements('Drupal\Core\Entity\FieldableEntityInterface')) {
return [];
}
$field_definitions = $this->entityFieldManager->getBaseFieldDefinitions($this->entityType->id());
if (!empty($this->configuration['bundle'])) {
$field_definitions += $this->entityFieldManager->getFieldDefinitions($this->entityType->id(), $this->configuration['bundle']);
}
$fields = array_map(function ($definition) {
return (string) $definition->getLabel();
}, $field_definitions);
return $fields;
}
/**
* {@inheritdoc}
*/
public function getIds() {
$id_key = $this->entityType->getKey('id');
$ids[$id_key] = $this->getDefinitionFromEntity($id_key);
if ($this->configuration['add_revision_id'] && $this->entityType->isRevisionable()) {
$revision_key = $this->entityType->getKey('revision');
$ids[$revision_key] = $this->getDefinitionFromEntity($revision_key);
}
if ($this->entityType->isTranslatable()) {
$langcode_key = $this->entityType->getKey('langcode');
$ids[$langcode_key] = $this->getDefinitionFromEntity($langcode_key);
}
return $ids;
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace Drupal\migrate_drupal\Plugin\migrate\source;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Deriver for content entity source plugins.
*
* @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use
* \Drupal\migrate\Plugin\migrate\source\ContentEntityDeriver instead.
*
* @see https://www.drupal.org/node/3498916
*/
class ContentEntityDeriver extends DeriverBase implements ContainerDeriverInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a new ContentEntityDeriver.
*
* @param string $base_plugin_id
* The base plugin ID.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity type manager.
*/
public function __construct($base_plugin_id, EntityTypeManagerInterface $entityTypeManager) {
@trigger_error(__CLASS__ . ' is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use \Drupal\migrate\Plugin\migrate\source\ContentEntity instead. See https://www.drupal.org/node/3498916', E_USER_DEPRECATED);
$this->entityTypeManager = $entityTypeManager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$base_plugin_id,
$container->get('entity_type.manager')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$this->derivatives = [];
foreach ($this->entityTypeManager->getDefinitions() as $id => $definition) {
if ($definition instanceof ContentEntityTypeInterface) {
$this->derivatives[$id] = $base_plugin_definition;
// Provide entity_type so the source can be used apart from a deriver.
$this->derivatives[$id]['entity_type'] = $id;
}
}
return parent::getDerivativeDefinitions($base_plugin_definition);
}
}

View File

@ -0,0 +1,200 @@
<?php
namespace Drupal\migrate_drupal\Plugin\migrate\source;
use Drupal\Component\Plugin\DependentPluginInterface;
use Drupal\Core\Entity\DependencyTrait;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\State\StateInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\migrate\Plugin\migrate\source\SqlBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* A base class for source plugins using a Drupal database as a source.
*
* Provides general purpose helper methods that are commonly needed
* when writing source plugins that use a Drupal database as a source, for
* example:
* - Check if the given module exists in the source database.
* - Read Drupal configuration variables from the source database.
*
* For a full list, refer to the methods of this class.
*
* For available configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*/
abstract class DrupalSqlBase extends SqlBase implements DependentPluginInterface {
use DependencyTrait;
/**
* The contents of the system table.
*
* @var array
*/
protected $systemData;
/**
* If the source provider is missing.
*
* @var bool
*/
protected $requirements = TRUE;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityTypeManagerInterface $entity_type_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state);
$this->entityTypeManager = $entity_type_manager;
}
/**
* Retrieves all system data information from the source Drupal database.
*
* @return array
* List of system table information keyed by type and name.
*/
public function getSystemData() {
if (!isset($this->systemData)) {
$this->systemData = [];
try {
$results = $this->select('system', 's')
->fields('s')
->execute();
foreach ($results as $result) {
$this->systemData[$result['type']][$result['name']] = $result;
}
}
catch (\Exception) {
// The table might not exist for example in tests.
}
}
return $this->systemData;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, ?MigrationInterface $migration = NULL) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$container->get('state'),
$container->get('entity_type.manager')
);
}
/**
* {@inheritdoc}
*/
public function checkRequirements() {
parent::checkRequirements();
if ($this->pluginDefinition['requirements_met'] === TRUE) {
if ($source_module = $this->getSourceModule()) {
if ($this->moduleExists($source_module)) {
if (isset($this->pluginDefinition['minimum_version'])) {
$minimum_version = (int) $this->pluginDefinition['minimum_version'];
$installed_version = (int) $this->getModuleSchemaVersion($source_module);
if ($minimum_version > $installed_version) {
throw new RequirementsException('Required minimum version ' . $this->pluginDefinition['minimum_version'], ['minimum_version' => $this->pluginDefinition['minimum_version']]);
}
}
}
else {
throw new RequirementsException('The module ' . $source_module . ' is not enabled in the source site.', ['source_module' => $source_module]);
}
}
}
}
/**
* Retrieves a module schema_version from the source Drupal database.
*
* @param string $module
* Name of module.
*
* @return mixed
* The current module schema version on the origin system table or FALSE if
* not found.
*/
protected function getModuleSchemaVersion($module) {
$system_data = $this->getSystemData();
return $system_data['module'][$module]['schema_version'] ?? FALSE;
}
/**
* Checks if a given module is enabled in the source Drupal database.
*
* @param string $module
* Name of module to check.
*
* @return bool
* TRUE if module is enabled on the origin system, FALSE if not.
*/
protected function moduleExists($module) {
$system_data = $this->getSystemData();
return !empty($system_data['module'][$module]['status']);
}
/**
* Reads a variable from a source Drupal database.
*
* @param string $name
* Name of the variable.
* @param mixed $default
* The default value.
*
* @return mixed
* The variable value.
*/
protected function variableGet($name, $default) {
try {
$result = $this->select('variable', 'v')
->fields('v', ['value'])
->condition('name', $name)
->execute()
->fetchField();
}
// The table might not exist.
catch (\Exception) {
$result = FALSE;
}
return $result !== FALSE ? unserialize($result) : $default;
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
// Generic handling for Drupal source plugin constants.
if (isset($this->configuration['constants']['entity_type'])) {
$this->addDependency('module', $this->entityTypeManager->getDefinition($this->configuration['constants']['entity_type'])->getProvider());
}
if (isset($this->configuration['constants']['module'])) {
$this->addDependency('module', $this->configuration['constants']['module']);
}
return $this->dependencies;
}
/**
* {@inheritdoc}
*/
public function getSourceModule(): ?string {
return parent::getSourceModule() ?? $this->pluginDefinition['source_module'] ?? NULL;
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace Drupal\migrate_drupal\Plugin\migrate\source;
use Drupal\Component\Plugin\DependentPluginInterface;
use Drupal\Core\Entity\DependencyTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Plugin\migrate\source\EmptySource as BaseEmptySource;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
/**
* Source returning an empty row with Drupal specific config dependencies.
*
* For more information and available configuration keys, refer to the parent
* classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\EmptySource
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "md_empty",
* source_module = "system",
* )
*/
class EmptySource extends BaseEmptySource implements ContainerFactoryPluginInterface, DependentPluginInterface {
use DependencyTrait;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityTypeManagerInterface $entity_type_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, ?MigrationInterface $migration = NULL) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$container->get('entity_type.manager')
);
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
// The empty source plugin supports the entity_type constant.
if (isset($this->configuration['constants']['entity_type'])) {
$this->addDependency('module', $this->entityTypeManager->getDefinition($this->configuration['constants']['entity_type'])->getProvider());
}
return $this->dependencies;
}
}

View File

@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace Drupal\migrate_drupal\Plugin\migrate\source;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
use Drupal\migrate\MigrateException;
use Drupal\migrate\Row;
// cspell:ignore objectid
/**
* Gets an i18n translation from the source database.
*/
trait I18nQueryTrait {
/**
* The i18n string table name.
*
* @var string
*/
protected string $i18nStringTable;
/**
* Gets the translation for the property not already in the row.
*
* For some i18n migrations there are two translation values, such as a
* translated title and a translated description, that need to be retrieved.
* Since these values are stored in separate rows of the i18nStringTable
* table we get them individually, one in the source plugin query() and the
* other in prepareRow(). The names of the properties varies, for example,
* in BoxTranslation they are 'body' and 'title' whereas in
* MenuLinkTranslation they are 'title' and 'description'. This will save both
* translations to the row.
*
* @param \Drupal\migrate\Row $row
* The current migration row which must include both a 'language' property
* and an 'objectid' property. The 'objectid' is the value for the
* 'objectid' field in the i18n_string table.
* @param string $property_not_in_row
* The name of the property to get the translation for.
* @param string $object_id_name
* The value of the objectid in the i18n table.
* @param \Drupal\migrate\Plugin\MigrateIdMapInterface $id_map
* The ID map.
*
* @return bool
* FALSE if the property has already been migrated.
*
* @throws \Drupal\migrate\MigrateException
*/
protected function getPropertyNotInRowTranslation(Row $row, string $property_not_in_row, string $object_id_name, MigrateIdMapInterface $id_map): bool {
$language = $row->getSourceProperty('language');
if (!$language) {
throw new MigrateException('No language found.');
}
$object_id = $row->getSourceProperty($object_id_name);
if (!$object_id) {
throw new MigrateException('No objectid found.');
}
// If this row has been migrated it is a duplicate so skip it.
if ($id_map->lookupDestinationIds([$object_id_name => $object_id, 'language' => $language])) {
return FALSE;
}
// Save the translation for the property already in the row.
$property_in_row = $row->getSourceProperty('property');
$row->setSourceProperty($property_in_row . '_translated', $row->getSourceProperty('translation'));
// Get the translation, if one exists, for the property not already in the
// row.
$query = $this->select($this->i18nStringTable, 'i18n')
->fields('i18n', ['lid'])
->condition('i18n.property', $property_not_in_row)
->condition('i18n.objectid', $object_id);
$query->leftJoin('locales_target', 'lt', '[i18n].[lid] = [lt].[lid]');
$query->condition('lt.language', $language);
$query->addField('lt', 'translation');
$results = $query->execute()->fetchAssoc();
$row->setSourceProperty($property_not_in_row . '_translated', $results['translation'] ?? NULL);
return TRUE;
}
}

View File

@ -0,0 +1,175 @@
<?php
namespace Drupal\migrate_drupal\Plugin\migrate\source;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\State\StateInterface;
use Drupal\migrate\Plugin\MigrationInterface;
/**
* Drupal 6/7 variable source from database.
*
* This source class fetches variables from the source Drupal database.
* Depending on the configuration, this returns zero or a single row and as such
* is not a good example for any normal source class returning multiple rows.
*
* Available configuration keys (one of which must be defined):
* - variables: (optional) The list of variables to retrieve from the source
* database. Specified variables are retrieved in a single row.
* - variables_no_row_if_missing: (optional) The list of variables to retrieve
* from the source database. If any of the variables listed here are missing
* in the source, then the source will return zero rows.
*
* Examples:
*
* With this configuration, the source will return one row even when the
* "filter_fallback_format" variable isn't available:
* @code
* source:
* plugin: variable
* variables:
* - filter_fallback_format
* @endcode
*
* With this configuration, the source will return one row if the variable is
* available, and zero if it isn't:
* @code
* source:
* plugin: variable
* variables_no_row_if_missing:
* - filter_fallback_format
* @endcode
*
* The variables and the variables_no_row_if_missing lists are always merged
* together. All of the following configurations are valid:
* @code
* source:
* plugin: variable
* variables:
* - book_child_type
* - book_block_mode
* - book_allowed_types
* variables_no_row_if_missing:
* - book_child_type
* - book_block_mode
* - book_allowed_types
*
* source:
* plugin: variable
* variables:
* - book_child_type
* - book_block_mode
* variables_no_row_if_missing:
* - book_allowed_types
*
* source:
* plugin: variable
* variables_no_row_if_missing:
* - book_child_type
* - book_block_mode
* - book_allowed_types
* @endcode
*
* For additional configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "variable",
* source_module = "system",
* )
*/
class Variable extends DrupalSqlBase {
/**
* The variable names to fetch.
*
* @var array
*/
protected $variables;
/**
* The variables that result in no row if any are missing from the source.
*
* @var array
*/
protected $variablesNoRowIfMissing;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityTypeManagerInterface $entity_type_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_type_manager);
$this->variablesNoRowIfMissing = $this->configuration['variables_no_row_if_missing'] ?? [];
$variables = $this->configuration['variables'] ?? [];
$this->variables = array_unique(array_merge(array_values($variables), array_values($this->variablesNoRowIfMissing)));
}
/**
* {@inheritdoc}
*/
protected function initializeIterator() {
if ($this->count()) {
return new \ArrayIterator([$this->values()]);
}
return new \ArrayIterator();
}
/**
* Return the values of the variables specified in the plugin configuration.
*
* @return array
* An associative array where the keys are the variables specified in the
* plugin configuration and the values are the values found in the source.
* Only those values are returned that are actually in the database.
*/
protected function values() {
// Create an ID field so we can record migration in the map table.
// Arbitrarily, use the first variable name.
$values['id'] = reset($this->variables);
return $values + array_map('unserialize', $this->prepareQuery()->execute()->fetchAllKeyed());
}
/**
* {@inheritdoc}
*/
protected function doCount() {
if (empty($this->variablesNoRowIfMissing)) {
return 1;
}
$variable_names = array_keys($this->query()->execute()->fetchAllAssoc('name'));
if (!empty(array_diff($this->variablesNoRowIfMissing, $variable_names))) {
return 0;
}
return 1;
}
/**
* {@inheritdoc}
*/
public function fields() {
return array_combine($this->variables, $this->variables);
}
/**
* {@inheritdoc}
*/
public function query() {
return $this->getDatabase()
->select('variable', 'v')
->fields('v', ['name', 'value'])
->condition('name', $this->variables, 'IN');
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['id']['type'] = 'string';
return $ids;
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace Drupal\migrate_drupal\Plugin\migrate\source;
use Drupal\migrate\Row;
// cspell:ignore multirow
/**
* Drupal 6/7 multiple variables source from database.
*
* Unlike the variable source plugin, this one returns one row per
* variable.
*
* Available configuration keys:
* - variables: (required) The list of variables to retrieve from the source
* database. Each variable is retrieved in a separate row.
*
* Example:
*
* @code
* plugin: variable_multirow
* variables:
* - date_format_long
* - date_format_medium
* - date_format_short
* @endcode
*
* In this example the specified variables are retrieved from the source
* database one row per variable.
*
* For additional configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "variable_multirow",
* source_module = "system",
* )
*/
class VariableMultiRow extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function query() {
return $this->select('variable', 'v')
->fields('v', ['name', 'value'])
// Cast scalars to array so we can consistently use an IN condition.
->condition('name', (array) $this->configuration['variables'], 'IN');
}
/**
* {@inheritdoc}
*/
public function fields() {
return [
'name' => $this->t('Name'),
'value' => $this->t('Value'),
];
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
if ($value = $row->getSourceProperty('value')) {
$row->setSourceProperty('value', unserialize($value));
}
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['name']['type'] = 'string';
return $ids;
}
}

View File

@ -0,0 +1,131 @@
<?php
namespace Drupal\migrate_drupal\Plugin\migrate\source\d6;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\State\StateInterface;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Drupal 6 i18n_variable source from database.
*
* Available configuration keys:
* - variables: (required) The list of variable translations to retrieve from
* the source database. All translations are retrieved in a single row.
*
* Examples:
*
* @code
* plugin: d6_variable_translation
* variables:
* - site_offline_message
* @endcode
* In this example the translations for site_offline_message variable are
* retrieved from the source database.
*
* For additional configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "d6_variable_translation",
* source_module = "i18n",
* )
*/
class VariableTranslation extends DrupalSqlBase {
/**
* The variable names to fetch.
*
* @var array
*/
protected $variables;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityTypeManagerInterface $entity_type_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_type_manager);
$this->variables = $this->configuration['variables'];
}
/**
* {@inheritdoc}
*/
protected function initializeIterator() {
return new \ArrayIterator($this->values());
}
/**
* Return the values of the variables specified in the plugin configuration.
*
* @return array
* An associative array where the keys are the variables specified in the
* plugin configuration and the values are the values found in the source.
* A key/value pair is added for the language code. Only those values are
* returned that are actually in the database.
*/
protected function values() {
$values = [];
$result = $this->prepareQuery()->execute()->FetchAllAssoc('language');
foreach ($result as $i18n_variable) {
$values[]['language'] = $i18n_variable->language;
}
$result = $this->prepareQuery()->execute()->FetchAll();
foreach ($result as $i18n_variable) {
foreach ($values as $key => $value) {
if ($values[$key]['language'] === $i18n_variable->language) {
$values[$key][$i18n_variable->name] = unserialize($i18n_variable->value);
break;
}
}
}
return $values;
}
/**
* {@inheritdoc}
*/
protected function doCount() {
return $this->initializeIterator()->count();
}
/**
* {@inheritdoc}
*/
public function fields() {
return array_combine($this->variables, $this->variables);
}
/**
* {@inheritdoc}
*/
public function query() {
return $this->getDatabase()
->select('i18n_variable', 'v')
->fields('v')
->condition('name', (array) $this->configuration['variables'], 'IN');
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['language']['type'] = 'string';
return $ids;
}
/**
* {@inheritdoc}
*/
public function checkRequirements() {
if (!$this->getDatabase()->schema()->tableExists('i18n_variable')) {
throw new RequirementsException("Source database table 'i18n_variable' does not exist");
}
parent::checkRequirements();
}
}

View File

@ -0,0 +1,157 @@
<?php
namespace Drupal\migrate_drupal\Plugin\migrate\source\d7;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Base class for D7 source plugins which need to collect field values.
*
* Field values are collected from the Field API.
*
* Refer to the existing implementations for examples:
*
* @see \Drupal\node\Plugin\migrate\source\d7\Node
* @see \Drupal\user\Plugin\migrate\source\d7\User
*
* For available configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*/
abstract class FieldableEntity extends DrupalSqlBase {
/**
* Cached field and field instance definitions.
*
* @var array
*/
protected $fieldInfo;
/**
* Returns all non-deleted field instances attached to a specific entity type.
*
* Typically, getFields() is used in the prepareRow method of a source plugin
* to get a list of all the field instances of the entity. A source plugin can
* then loop through the list of fields to do any other preparation before
* processing the row. Typically, a source plugin will use getFieldValues()
* to get the values of each field.
*
* @param string $entity_type
* The entity type ID.
* @param string|null $bundle
* (optional) The bundle.
*
* @return array[]
* The field instances, keyed by field name.
*/
protected function getFields($entity_type, $bundle = NULL) {
$cid = $entity_type . ':' . ($bundle ?? '');
if (!isset($this->fieldInfo[$cid])) {
$query = $this->select('field_config_instance', 'fci')
->fields('fci')
->condition('fci.entity_type', $entity_type)
->condition('fci.bundle', $bundle ?? $entity_type)
->condition('fci.deleted', 0);
// Join the 'field_config' table and add the 'translatable' setting to the
// query.
$query->leftJoin('field_config', 'fc', '[fci].[field_id] = [fc].[id]');
$query->addField('fc', 'translatable');
$this->fieldInfo[$cid] = $query->execute()->fetchAllAssoc('field_name');
}
return $this->fieldInfo[$cid];
}
/**
* Retrieves field values for a single field of a single entity.
*
* Typically, getFieldValues() is used in the prepareRow method of a source
* plugin where the return values are placed on the row source.
*
* @param string $entity_type
* The entity type.
* @param string $field
* The field name.
* @param int $entity_id
* The entity ID.
* @param int|null $revision_id
* (optional) The entity revision ID.
* @param string $language
* (optional) The field language.
*
* @return array
* The raw field values, keyed and sorted by delta.
*/
protected function getFieldValues($entity_type, $field, $entity_id, $revision_id = NULL, $language = NULL) {
$table = (isset($revision_id) ? 'field_revision_' : 'field_data_') . $field;
$query = $this->select($table, 't')
->fields('t')
->condition('entity_type', $entity_type)
->condition('entity_id', $entity_id)
->condition('deleted', 0)
->orderBy('delta');
if (isset($revision_id)) {
$query->condition('revision_id', $revision_id);
}
// Add 'language' as a query condition if it has been defined by Entity
// Translation.
if ($language) {
$query->condition('language', $language);
}
$values = [];
foreach ($query->execute() as $row) {
foreach ($row as $key => $value) {
$delta = $row['delta'];
if (str_starts_with($key, $field)) {
$column = substr($key, strlen($field) + 1);
$values[$delta][$column] = $value;
}
}
}
return $values;
}
/**
* Checks if an entity type uses Entity Translation.
*
* @param string $entity_type
* The entity type.
*
* @return bool
* Whether the entity type uses entity translation.
*/
protected function isEntityTranslatable($entity_type) {
return in_array($entity_type, $this->variableGet('entity_translation_entity_types', []), TRUE);
}
/**
* Gets an entity source language from the 'entity_translation' table.
*
* @param string $entity_type
* The entity type.
* @param int $entity_id
* The entity ID.
*
* @return string|bool
* The entity source language or FALSE if no source language was found.
*/
protected function getEntityTranslationSourceLanguage($entity_type, $entity_id) {
try {
return $this->select('entity_translation', 'et')
->fields('et', ['language'])
->condition('entity_type', $entity_type)
->condition('entity_id', $entity_id)
->condition('source', '')
->execute()
->fetchField();
}
// The table might not exist.
catch (\Exception) {
return FALSE;
}
}
}

View File

@ -0,0 +1,126 @@
<?php
namespace Drupal\migrate_drupal\Plugin\migrate\source\d7;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\State\StateInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Drupal 7 variable_store source from database.
*
* Available configuration keys:
* - variables: (required) The list of variable translations to retrieve from
* the source database. All translations are retrieved in a single row.
*
* Example:
*
* @code
* plugin: d7_variable_translation
* variables:
* - site_name
* - site_slogan
* @endcode
* In this example the translations for site_name and site_slogan variables are
* retrieved from the source database.
*
* For additional configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "d7_variable_translation",
* source_module = "i18n_variable",
* )
*/
class VariableTranslation extends DrupalSqlBase {
/**
* The variable names to fetch.
*
* @var array
*/
protected $variables;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityTypeManagerInterface $entity_type_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_type_manager);
$this->variables = $this->configuration['variables'];
}
/**
* {@inheritdoc}
*/
protected function initializeIterator() {
return new \ArrayIterator($this->values());
}
/**
* Return the values of the variables specified in the plugin configuration.
*
* @return array
* An associative array where the keys are the variables specified in the
* plugin configuration and the values are the values found in the source.
* A key/value pair is added for the language code. Only those values are
* returned that are actually in the database.
*/
protected function values() {
$values = [];
$result = $this->prepareQuery()->execute()->FetchAllAssoc('realm_key');
foreach ($result as $variable_store) {
$values[]['language'] = $variable_store['realm_key'];
}
$result = $this->prepareQuery()->execute()->FetchAll();
foreach ($result as $variable_store) {
foreach ($values as $key => $value) {
if ($values[$key]['language'] === $variable_store['realm_key']) {
if ($variable_store['serialized']) {
$values[$key][$variable_store['name']] = unserialize($variable_store['value']);
break;
}
else {
$values[$key][$variable_store['name']] = $variable_store['value'];
break;
}
}
}
}
return $values;
}
/**
* {@inheritdoc}
*/
protected function doCount() {
return $this->initializeIterator()->count();
}
/**
* {@inheritdoc}
*/
public function fields() {
return array_combine($this->variables, $this->variables);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['language']['type'] = 'string';
return $ids;
}
/**
* {@inheritdoc}
*/
public function query() {
return $this->select('variable_store', 'vs')
->fields('vs')
->condition('realm', 'language')
->condition('name', (array) $this->configuration['variables'], 'IN');
}
}

View File

@ -0,0 +1,115 @@
<?php
namespace Drupal\migrate_drupal\Plugin\migrate\source\d8;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\State\StateInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Drupal 8+ configuration source from database.
*
* @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use
* \Drupal\migrate\Plugin\migrate\source\ConfigEntity instead.
* @see https://www.drupal.org/node/3508578
*
* Available configuration keys:
* - collections: (optional) The collection of configuration storage to retrieve
* from the source - can be a string or an array. If omitted, configuration
* objects of all available collections are retrieved.
* - names: (optional) Names of configuration objects to retrieve from the
* source - can be a string or an array. If omitted, all available
* configuration objects are retrieved.
*
* Examples:
*
* @code
* source:
* plugin: d8_config
* names:
* - node.type.article
* - node.type.page
* @endcode
*
* In this example configuration objects of article and page content types are
* retrieved from the source database.
*
* @code
* source:
* plugin: d8_config
* collections: language.fr
* names:
* - node.type.article
* - node.type.page
* @endcode
*
* In this example configuration objects are filtered by language.fr collection.
* As a result, French versions of specified configuration objects are retrieved
* from the source database.
*
* For additional configuration keys, refer to the parent classes.
*
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
*
* @MigrateSource(
* id = "d8_config",
* source_module = "system",
* )
*/
class Config extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityTypeManagerInterface $entity_type_manager) {
@trigger_error(__CLASS__ . ' is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use \Drupal\migrate\Plugin\migrate\source\ContentEntity instead. See https://www.drupal.org/node/3508578', E_USER_DEPRECATED);
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_type_manager);
}
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('config', 'c')
->fields('c', ['collection', 'name', 'data']);
if (!empty($this->configuration['collections'])) {
$query->condition('collection', (array) $this->configuration['collections'], 'IN');
}
if (!empty($this->configuration['names'])) {
$query->condition('name', (array) $this->configuration['names'], 'IN');
}
return $query;
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
$row->setSourceProperty('data', unserialize($row->getSourceProperty('data')));
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function fields() {
return [
'collection' => $this->t('The config object collection.'),
'name' => $this->t('The config object name.'),
'data' => $this->t('Serialized configuration object data.'),
];
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['collection']['type'] = 'string';
$ids['name']['type'] = 'string';
return $ids;
}
}

View File

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Drupal\migrate_drupal\Tests;
use Drupal\migrate\Row;
/**
* Provides common functionality for testing stubbing.
*/
trait StubTestTrait {
/**
* Tests that creating a stub of an entity type results in a valid entity.
*
* @param string $entity_type_id
* The entity type we are stubbing.
*/
protected function performStubTest($entity_type_id) {
$entity_id = $this->createEntityStub($entity_type_id);
$this->assertNotEmpty($entity_id, 'Stub successfully created');
// When validateStub fails, it will return an array with the violations.
$this->assertEmpty($this->validateStub($entity_type_id, $entity_id));
}
/**
* Create a stub of the given entity type.
*
* @param string $entity_type_id
* The entity type we are stubbing.
*
* @return int
* ID of the created entity.
*/
protected function createEntityStub($entity_type_id) {
// Create a dummy migration to pass to the destination plugin.
$definition = [
'migration_tags' => ['Stub test'],
'source' => ['plugin' => 'empty'],
'process' => [],
'destination' => ['plugin' => 'entity:' . $entity_type_id],
];
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$destination_plugin = $migration->getDestinationPlugin(TRUE);
$stub_row = new Row([], [], TRUE);
$destination_ids = $destination_plugin->import($stub_row);
return reset($destination_ids);
}
/**
* Perform validation on a stub entity.
*
* @param string $entity_type_id
* The entity type we are stubbing.
* @param string $entity_id
* ID of the stubbed entity to validate.
*
* @return \Drupal\Core\Entity\EntityConstraintViolationListInterface
* List of constraint violations identified.
*/
protected function validateStub($entity_type_id, $entity_id) {
$controller = \Drupal::entityTypeManager()->getStorage($entity_type_id);
/** @var \Drupal\Core\Entity\ContentEntityInterface $stub_entity */
$stub_entity = $controller->load($entity_id);
return $stub_entity->validate();
}
}