Initial Drupal 11 with DDEV setup
This commit is contained in:
@ -0,0 +1,22 @@
|
||||
---
|
||||
label: 'Migrating, updating, and upgrading'
|
||||
top_level: true
|
||||
---
|
||||
<h2>{% trans %}What are updating, upgrading, and migrating?{% endtrans %}</h2>
|
||||
<p>{% trans %}<em>Updating</em> is the process of changing from one minor version of the software to a newer version, such as from version 8.3.4 to 8.3.5, or 8.3.5 to 8.4.0. Starting with version 8.x, you can also update to major versions 9, 10, and beyond if your add-on modules, themes, and install profiles are compatible. <em>Upgrading</em> is the process of changing from an older major version of the software to a newer version, such as from version 7 to 8. <em>Migrating</em> is the process of importing data into a site.{% endtrans %}</p>
|
||||
<p>{% trans %}To upgrade a site from Drupal 6 or 7 to Drupal 8 or later, keeping the content and configuration the same, you will install the new version of the software and add-on modules and themes in a new site, and then migrate the content and other data from your old site into the new site.{% endtrans %}</p>
|
||||
<h2>{% trans %}Overview of Migrating{% endtrans %}</h2>
|
||||
<p>{% trans %}You can use the <em>Migration</em> group of modules to perform the migration step of upgrading from Drupal 6 or 7 to Drupal 8 or later, as well as other migrations. These modules also provide APIs that can be used by programmers to write custom software for migrations. Here are the functions of the core migration modules:{% endtrans %}</p>
|
||||
<dl>
|
||||
<dt>{% trans %}Migrate{% endtrans %}</dt>
|
||||
<dd>{% trans %}Provides the underlying API for migrating data.{% endtrans %}</dd>
|
||||
<dt>{% trans %}Migrate Drupal{% endtrans %}</dt>
|
||||
<dd>{% trans %}Provides data migration from older versions of the core software into a new site.{% endtrans %}</dd>
|
||||
<dt>{% trans %}Migrate Drupal UI{% endtrans %}</dt>
|
||||
<dd>{% trans %}Provides a user interface for performing data migration from older versions of the core software into a new site.{% endtrans %}</dd>
|
||||
</dl>
|
||||
<p>{% trans %}If the source of the data you want to migrate is a different content management system, or if the data source is a site that was built using contributed modules that the core migration modules do not support, then you will also need one or more contributed or custom modules in order to migrate your data.{% endtrans %}</p>
|
||||
<h2>{% trans %}Additional Resources{% endtrans %}</h2>
|
||||
<ul>
|
||||
<li>{% trans %}<a href="https://www.drupal.org/docs/upgrading-drupal">Upgrading Drupal</a>{% endtrans %}</li>
|
||||
</ul>
|
||||
211
web/core/modules/migrate/migrate.api.php
Normal file
211
web/core/modules/migrate/migrate.api.php
Normal file
@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Hooks provided by the Migrate module.
|
||||
*/
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Plugin\MigrateSourceInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* @defgroup migration Migrate API
|
||||
* @{
|
||||
* Overview of the Migrate API, which migrates data into Drupal.
|
||||
*
|
||||
* @section overview Overview of a migration
|
||||
* Migration is an
|
||||
* @link http://wikipedia.org/wiki/Extract,_transform,_load Extract, Transform, Load @endlink
|
||||
* (ETL) process. In the Drupal Migrate API, the extract phase is called
|
||||
* 'source', the transform phase is called 'process', and the load phase is
|
||||
* called 'destination'. It is important to understand that the term 'load' in
|
||||
* ETL refers to loading data into the storage while in a typical Drupal context
|
||||
* the term 'load' refers to loading data from storage.
|
||||
*
|
||||
* In the source phase, a set of data, called the row, is retrieved from the
|
||||
* data source. The data can be migrated from a database, loaded from a file
|
||||
* (for example CSV, JSON or XML) or fetched from a web service (for example RSS
|
||||
* or REST). The row is sent to the process phase where it is transformed as
|
||||
* needed or marked to be skipped. After processing, the transformed row is
|
||||
* passed to the destination phase where it is loaded (saved) into the target
|
||||
* Drupal site.
|
||||
*
|
||||
* Migrate API uses the Drupal plugin system for many different purposes. Most
|
||||
* importantly, the overall ETL process is defined as a migration plugin and the
|
||||
* three phases (source, process and destination) have their own plugin types.
|
||||
*
|
||||
* @section sec_migrations Migrate API migration plugins
|
||||
* Migration plugin definitions are stored in a module's 'migrations' directory.
|
||||
* The plugin class is \Drupal\migrate\Plugin\Migration, with interface
|
||||
* \Drupal\migrate\Plugin\MigrationInterface. Migration plugins are managed by
|
||||
* the \Drupal\migrate\Plugin\MigrationPluginManager class. Migration plugins
|
||||
* are only available if the providers of their source plugins are installed.
|
||||
*
|
||||
* @link https://www.drupal.org/docs/8/api/migrate-api/migrate-destination-plugins-examples Example migrations in Migrate API handbook. @endlink
|
||||
*
|
||||
* @section sec_source Migrate API source plugins
|
||||
* Migrate API source plugins implement
|
||||
* \Drupal\migrate\Plugin\MigrateSourceInterface and usually extend
|
||||
* \Drupal\migrate\Plugin\migrate\source\SourcePluginBase. They have the
|
||||
* \Drupal\migrate\Attribute\MigrateSource attribute and must be in
|
||||
* namespace subdirectory 'Plugin\migrate\source' under the namespace of the
|
||||
* module that defines them. Migrate API source plugins are managed by the
|
||||
* \Drupal\migrate\Plugin\MigrateSourcePluginManager class.
|
||||
*
|
||||
* @link https://api.drupal.org/api/drupal/namespace/Drupal!migrate!Plugin!migrate!source List of source plugins provided by the core Migrate module. @endlink
|
||||
* @link https://www.drupal.org/docs/8/api/migrate-api/migrate-source-plugins Core and contributed source plugin usage examples in Migrate API handbook. @endlink
|
||||
*
|
||||
* @section sec_process Migrate API process plugins
|
||||
* Migrate API process plugins implement
|
||||
* \Drupal\migrate\Plugin\MigrateProcessInterface and usually extend
|
||||
* \Drupal\migrate\ProcessPluginBase. They have the
|
||||
* \Drupal\migrate\Attribute\MigrateProcess attribute and must be in
|
||||
* namespace subdirectory 'Plugin\migrate\process' under the namespace of the
|
||||
* module that defines them. Migrate API process plugins are managed by the
|
||||
* \Drupal\migrate\Plugin\MigratePluginManager class.
|
||||
*
|
||||
* @link https://api.drupal.org/api/drupal/namespace/Drupal!migrate!Plugin!migrate!process List of process plugins for common operations provided by the core Migrate module. @endlink
|
||||
*
|
||||
* @section sec_destination Migrate API destination plugins
|
||||
* Migrate API destination plugins implement
|
||||
* \Drupal\migrate\Plugin\MigrateDestinationInterface and usually extend
|
||||
* \Drupal\migrate\Plugin\migrate\destination\DestinationBase. They have the
|
||||
* \Drupal\migrate\Attribute\MigrateDestination attribute and must be in
|
||||
* namespace subdirectory 'Plugin\migrate\destination' under the namespace of
|
||||
* the module that defines them. Migrate API destination plugins are managed by
|
||||
* the \Drupal\migrate\Plugin\MigrateDestinationPluginManager class.
|
||||
*
|
||||
* @link https://api.drupal.org/api/drupal/namespace/Drupal!migrate!Plugin!migrate!destination List of destination plugins for Drupal configuration and content entities provided by the core Migrate module. @endlink
|
||||
*
|
||||
* @section sec_key_concepts Migrate API key concepts
|
||||
* @subsection sec_stubs Stubs
|
||||
* Taxonomy terms are an example of a data structure where an entity can have a
|
||||
* reference to a parent. When a term is being migrated, it is possible that its
|
||||
* parent term has not yet been migrated. Migrate API addresses this 'chicken
|
||||
* and egg' dilemma by creating a stub term for the parent so that the child
|
||||
* term can establish a reference to it. When the parent term is eventually
|
||||
* migrated, Migrate API updates the previously created stub with the actual
|
||||
* content.
|
||||
*
|
||||
* @subsection sec_map_tables Map tables
|
||||
* Once a migrated row is saved and the destination IDs are known, Migrate API
|
||||
* saves the source IDs, destination IDs, and the row hash into a map table. The
|
||||
* source IDs and the hash facilitate tracking changes for continuous
|
||||
* migrations. Other migrations can use the map tables for lookup purposes when
|
||||
* establishing relationships between records.
|
||||
*
|
||||
* @subsection sec_high_water_mark High-water mark
|
||||
* A High-water mark allows the Migrate API to track changes so that only data
|
||||
* that has been created or updated in the source since the migration was
|
||||
* previously executed is migrated. The only requirement to use the high-water
|
||||
* feature is to declare the row property to use for the high-water mark. This
|
||||
* can be any property that indicates the highest value migrated so far. For
|
||||
* example, a timestamp property that indicates when a row of data was created
|
||||
* or last updated would make an excellent high-water property. If the migration
|
||||
* is executed again, only those rows that have a higher timestamp than in the
|
||||
* previous migration would be included.
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* plugin: d7_node
|
||||
* high_water_property:
|
||||
* name: changed
|
||||
* @endcode
|
||||
*
|
||||
* In this example, the row property 'changed' is the high_water_property. If
|
||||
* the value of 'changed' is greater than the current high-water mark the row
|
||||
* is processed and the value of the high-water mark is updated to the value of
|
||||
* 'changed'.
|
||||
*
|
||||
* @subsection sec_rollbacks Rollbacks
|
||||
* When developing a migration, it is quite typical that the first version does
|
||||
* not provide correct results for all migrated data. Rollbacks allow you to
|
||||
* undo a migration and then execute it again after adjusting it.
|
||||
*
|
||||
* @section sec_more_info Documentation handbooks
|
||||
* @link https://www.drupal.org/docs/8/api/migrate-api Migrate API handbook. @endlink
|
||||
* @link https://www.drupal.org/docs/8/upgrade Upgrading to Drupal 8 handbook. @endlink
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup hooks
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Allows adding data to a row before processing it.
|
||||
*
|
||||
* For example, filter module used to store filter format settings in the
|
||||
* variables table which now needs to be inside the filter format config
|
||||
* file. So, it needs to be added here.
|
||||
*
|
||||
* hook_migrate_MIGRATION_ID_prepare_row() is also available.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row being imported.
|
||||
* @param \Drupal\migrate\Plugin\MigrateSourceInterface $source
|
||||
* The source migration.
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The current migration.
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
function hook_migrate_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration): void {
|
||||
if ($migration->id() == 'd6_filter_formats') {
|
||||
$value = $source->getDatabase()->query('SELECT [value] FROM {variable} WHERE [name] = :name', [':name' => 'my_module_filter_foo_' . $row->getSourceProperty('format')])->fetchField();
|
||||
if ($value) {
|
||||
$row->setSourceProperty('settings:my_module:foo', unserialize($value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows adding data to a row for a migration with the specified ID.
|
||||
*
|
||||
* This provides the same functionality as hook_migrate_prepare_row() but
|
||||
* removes the need to check the value of $migration->id().
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row being imported.
|
||||
* @param \Drupal\migrate\Plugin\MigrateSourceInterface $source
|
||||
* The source migration.
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The current migration.
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
function hook_migrate_MIGRATION_ID_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) {
|
||||
$value = $source->getDatabase()->query('SELECT [value] FROM {variable} WHERE [name] = :name', [':name' => 'my_module_filter_foo_' . $row->getSourceProperty('format')])->fetchField();
|
||||
if ($value) {
|
||||
$row->setSourceProperty('settings:my_module:foo', unserialize($value));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows altering the list of discovered migration plugins.
|
||||
*
|
||||
* Modules are able to alter specific migrations structures or even remove or
|
||||
* append additional migrations to the discovery. For example, this
|
||||
* implementation filters out Drupal 6 migrations from the discovered migration
|
||||
* list. This is done by checking the migration tags.
|
||||
*
|
||||
* @param array[] $migrations
|
||||
* An associative array of migrations keyed by migration ID. Each value is the
|
||||
* migration array, obtained by decoding the migration YAML file and enriched
|
||||
* with some meta information added during discovery phase, like migration
|
||||
* 'class', 'provider' or '_discovered_file_path'.
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
function hook_migration_plugins_alter(array &$migrations) {
|
||||
$migrations = array_filter($migrations, function (array $migration) {
|
||||
$tags = isset($migration['migration_tags']) ? (array) $migration['migration_tags'] : [];
|
||||
return !in_array('Drupal 6', $tags);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup hooks".
|
||||
*/
|
||||
5
web/core/modules/migrate/migrate.info.yml
Normal file
5
web/core/modules/migrate/migrate.info.yml
Normal file
@ -0,0 +1,5 @@
|
||||
name: Migrate
|
||||
type: module
|
||||
description: 'Provides a framework for migrating data to Drupal.'
|
||||
package: Migration
|
||||
version: VERSION
|
||||
13
web/core/modules/migrate/migrate.install
Normal file
13
web/core/modules/migrate/migrate.install
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains install and update functions for Migrate.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_update_last_removed().
|
||||
*/
|
||||
function migrate_update_last_removed(): int {
|
||||
return 10100;
|
||||
}
|
||||
6
web/core/modules/migrate/migrate.links.menu.yml
Normal file
6
web/core/modules/migrate/migrate.links.menu.yml
Normal file
@ -0,0 +1,6 @@
|
||||
migrate.messages:
|
||||
title: Migration messages
|
||||
parent: system.admin_reports
|
||||
description: View the migration messages.
|
||||
route_name: migrate.messages
|
||||
weight: 0
|
||||
2
web/core/modules/migrate/migrate.permissions.yml
Normal file
2
web/core/modules/migrate/migrate.permissions.yml
Normal file
@ -0,0 +1,2 @@
|
||||
view migration messages:
|
||||
title: 'View migration messages'
|
||||
15
web/core/modules/migrate/migrate.post_update.php
Normal file
15
web/core/modules/migrate/migrate.post_update.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Post update functions for migrate.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_removed_post_updates().
|
||||
*/
|
||||
function migrate_removed_post_updates(): array {
|
||||
return [
|
||||
'migrate_post_update_clear_migrate_source_count_cache' => '10.0.0',
|
||||
];
|
||||
}
|
||||
15
web/core/modules/migrate/migrate.routing.yml
Normal file
15
web/core/modules/migrate/migrate.routing.yml
Normal file
@ -0,0 +1,15 @@
|
||||
migrate.messages:
|
||||
path: '/admin/reports/migration-messages'
|
||||
defaults:
|
||||
_controller: '\Drupal\migrate\Controller\MigrateMessageController::overview'
|
||||
_title: 'Migration messages'
|
||||
requirements:
|
||||
_permission: 'view migration messages'
|
||||
|
||||
migrate.messages.detail:
|
||||
path: '/admin/reports/migration-messages/{migration_id}'
|
||||
defaults:
|
||||
_controller: '\Drupal\migrate\Controller\MigrateMessageController::details'
|
||||
_title_callback: '\Drupal\migrate\Controller\MigrateMessageController::title'
|
||||
requirements:
|
||||
_permission: 'view migration messages'
|
||||
51
web/core/modules/migrate/migrate.services.yml
Normal file
51
web/core/modules/migrate/migrate.services.yml
Normal file
@ -0,0 +1,51 @@
|
||||
parameters:
|
||||
migrate.skip_procedural_hook_scan: true
|
||||
|
||||
services:
|
||||
_defaults:
|
||||
autoconfigure: true
|
||||
migrate.plugin_event_subscriber:
|
||||
class: Drupal\migrate\Plugin\PluginEventSubscriber
|
||||
cache.migrate:
|
||||
class: Drupal\Core\Cache\CacheBackendInterface
|
||||
tags:
|
||||
- { name: cache.bin }
|
||||
factory: ['@cache_factory', 'get']
|
||||
arguments: [migrate]
|
||||
plugin.manager.migrate.source:
|
||||
class: Drupal\migrate\Plugin\MigrateSourcePluginManager
|
||||
arguments: [source, '@container.namespaces', '@cache.discovery', '@module_handler']
|
||||
plugin.manager.migrate.process:
|
||||
class: Drupal\migrate\Plugin\MigratePluginManager
|
||||
arguments:
|
||||
- process
|
||||
- '@container.namespaces'
|
||||
- '@cache.discovery'
|
||||
- '@module_handler'
|
||||
- 'Drupal\migrate\Attribute\MigrateProcess'
|
||||
- 'Drupal\migrate\Annotation\MigrateProcessPlugin'
|
||||
plugin.manager.migrate.destination:
|
||||
class: Drupal\migrate\Plugin\MigrateDestinationPluginManager
|
||||
arguments: [destination, '@container.namespaces', '@cache.discovery', '@module_handler', '@entity_type.manager']
|
||||
plugin.manager.migrate.id_map:
|
||||
class: Drupal\migrate\Plugin\MigratePluginManager
|
||||
arguments: [id_map, '@container.namespaces', '@cache.discovery', '@module_handler']
|
||||
cache.discovery_migration:
|
||||
class: Drupal\Core\Cache\CacheBackendInterface
|
||||
tags:
|
||||
- { name: cache.bin }
|
||||
factory: ['@cache_factory', 'get']
|
||||
arguments: [discovery_migration]
|
||||
plugin.manager.migration:
|
||||
class: Drupal\migrate\Plugin\MigrationPluginManager
|
||||
arguments: ['@module_handler', '@cache.discovery_migration', '@language_manager']
|
||||
Drupal\migrate\Plugin\MigrationPluginManagerInterface: '@plugin.manager.migration'
|
||||
Drupal\migrate\MigrateBuildDependencyInterface: '@plugin.manager.migration'
|
||||
migrate.lookup:
|
||||
class: Drupal\migrate\MigrateLookup
|
||||
arguments: ['@plugin.manager.migration']
|
||||
Drupal\migrate\MigrateLookupInterface: '@migrate.lookup'
|
||||
migrate.stub:
|
||||
class: Drupal\migrate\MigrateStub
|
||||
arguments: ['@plugin.manager.migration']
|
||||
Drupal\migrate\MigrateStubInterface: '@migrate.stub'
|
||||
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a migration destination plugin annotation object.
|
||||
*
|
||||
* Plugin Namespace: Plugin\migrate\destination
|
||||
*
|
||||
* For a working example, see
|
||||
* \Drupal\migrate\Plugin\migrate\destination\UrlAlias
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateDestinationInterface
|
||||
* @see \Drupal\migrate\Plugin\migrate\destination\DestinationBase
|
||||
* @see \Drupal\migrate\Plugin\MigrateDestinationPluginManager
|
||||
* @see \Drupal\migrate\Annotation\MigrateSource
|
||||
* @see \Drupal\migrate\Annotation\MigrateProcessPlugin
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class MigrateDestination extends Plugin {
|
||||
|
||||
/**
|
||||
* A unique identifier for the process plugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* Whether requirements are met.
|
||||
*
|
||||
* If TRUE and a 'provider' key is present in the annotation then the
|
||||
* default destination plugin manager will set this to FALSE if the
|
||||
* provider (module/theme) doesn't exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $requirements_met = TRUE;
|
||||
|
||||
/**
|
||||
* Identifies the system handling the data the destination plugin will write.
|
||||
*
|
||||
* The destination plugin itself determines how the value is used. For
|
||||
* example, Migrate Drupal's destination plugins expect destination_module to
|
||||
* be the name of a module that must be installed on the destination.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $destination_module;
|
||||
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a migration process plugin annotation object.
|
||||
*
|
||||
* Plugin Namespace: Plugin\migrate\process
|
||||
*
|
||||
* For a working example, see
|
||||
* \Drupal\migrate\Plugin\migrate\process\DefaultValue
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigratePluginManager
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
* @see \Drupal\migrate\ProcessPluginBase
|
||||
* @see \Drupal\migrate\Annotation\MigrateSource
|
||||
* @see \Drupal\migrate\Annotation\MigrateDestination
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class MigrateProcessPlugin extends Plugin {
|
||||
|
||||
/**
|
||||
* A unique identifier for the process plugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* Whether the plugin handles multiples itself.
|
||||
*
|
||||
* This property is optional and it does not need to be declared.
|
||||
*
|
||||
* Typically these plugins will expect an array as input and iterate over it
|
||||
* themselves, changing the whole array. For example the 'sub_process' and the
|
||||
* 'flatten' plugins. If the plugin only need to change a single value it
|
||||
* can skip setting this attribute and let
|
||||
* \Drupal\migrate\MigrateExecutable::processRow() handle the iteration.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $handle_multiples = FALSE;
|
||||
|
||||
}
|
||||
98
web/core/modules/migrate/src/Annotation/MigrateSource.php
Normal file
98
web/core/modules/migrate/src/Annotation/MigrateSource.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a migration source plugin annotation object.
|
||||
*
|
||||
* Plugin Namespace: Plugin\migrate\source
|
||||
*
|
||||
* For a working example, check
|
||||
* \Drupal\migrate\Plugin\migrate\source\EmptySource
|
||||
* \Drupal\migrate_drupal\Plugin\migrate\source\UrlAlias
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigratePluginManager
|
||||
* @see \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
* @see \Drupal\migrate\Annotation\MigrateProcessPlugin
|
||||
* @see \Drupal\migrate\Annotation\MigrateDestination
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class MigrateSource extends Plugin implements MultipleProviderAnnotationInterface {
|
||||
|
||||
/**
|
||||
* A unique identifier for the process plugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* Whether requirements are met.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $requirements_met = TRUE;
|
||||
|
||||
/**
|
||||
* Identifies the system providing the data the source plugin will read.
|
||||
*
|
||||
* The source plugin itself determines how the value is used. For example,
|
||||
* Migrate Drupal's source plugins expect source_module to be the name of a
|
||||
* module that must be installed and enabled in the source database.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
* @see \Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase::checkRequirements
|
||||
*/
|
||||
public $source_module;
|
||||
|
||||
/**
|
||||
* Specifies the minimum version of the source provider.
|
||||
*
|
||||
* This can be any type, and the source plugin itself determines how it is
|
||||
* used. For example, Migrate Drupal's source plugins expect this to be an
|
||||
* integer representing the minimum installed database schema version of the
|
||||
* module specified by source_module.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $minimum_version;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProvider() {
|
||||
if (isset($this->definition['provider'])) {
|
||||
return is_array($this->definition['provider']) ? reset($this->definition['provider']) : $this->definition['provider'];
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProviders() {
|
||||
if (isset($this->definition['provider'])) {
|
||||
// Ensure that we return an array even if
|
||||
// \Drupal\Component\Annotation\AnnotationInterface::setProvider() has
|
||||
// been called.
|
||||
return (array) $this->definition['provider'];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setProviders(array $providers) {
|
||||
$this->definition['provider'] = $providers;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\AnnotationInterface;
|
||||
|
||||
/**
|
||||
* Defines a common interface for classed annotations with multiple providers.
|
||||
*
|
||||
* @todo This is a temporary solution to the fact that migration source plugins
|
||||
* have more than one provider. This functionality will be moved to core in
|
||||
* https://www.drupal.org/node/2786355.
|
||||
*/
|
||||
interface MultipleProviderAnnotationInterface extends AnnotationInterface {
|
||||
|
||||
/**
|
||||
* Gets the name of the provider of the annotated class.
|
||||
*
|
||||
* @return string
|
||||
* The provider of the annotation. If there are multiple providers the first
|
||||
* is returned.
|
||||
*/
|
||||
public function getProvider();
|
||||
|
||||
/**
|
||||
* Gets the provider names of the annotated class.
|
||||
*
|
||||
* @return string[]
|
||||
* The providers of the annotation.
|
||||
*/
|
||||
public function getProviders();
|
||||
|
||||
/**
|
||||
* Sets the provider names of the annotated class.
|
||||
*
|
||||
* @param string[] $providers
|
||||
* The providers of the annotation.
|
||||
*/
|
||||
public function setProviders(array $providers);
|
||||
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\migrate\Attribute;
|
||||
|
||||
use Drupal\Component\Plugin\Attribute\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a MigrateDestination attribute.
|
||||
*
|
||||
* Plugin Namespace: Plugin\migrate\destination
|
||||
*
|
||||
* For a working example, see
|
||||
* \Drupal\migrate\Plugin\migrate\destination\UrlAlias
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateDestinationPluginManager
|
||||
* @see \Drupal\migrate\Plugin\MigrateDestinationInterface
|
||||
* @see \Drupal\migrate\Plugin\migrate\destination\DestinationBase
|
||||
* @see \Drupal\migrate\Attribute\MigrateProcess
|
||||
* @see \Drupal\migrate\Attribute\MigrateSource
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
class MigrateDestination extends Plugin {
|
||||
|
||||
/**
|
||||
* Constructs a migrate destination plugin attribute object.
|
||||
*
|
||||
* @param string $id
|
||||
* A unique identifier for the destination plugin.
|
||||
* @param bool $requirements_met
|
||||
* (optional) Whether requirements are met.
|
||||
* @param string|null $destination_module
|
||||
* (optional) Identifies the system handling the data the destination plugin
|
||||
* will write. The destination plugin itself determines how the value is
|
||||
* used. For example, Migrate's destination plugins expect
|
||||
* destination_module to be the name of a module that must be installed on
|
||||
* the destination.
|
||||
* @param class-string|null $deriver
|
||||
* (optional) The deriver class.
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $id,
|
||||
public bool $requirements_met = TRUE,
|
||||
public readonly ?string $destination_module = NULL,
|
||||
public readonly ?string $deriver = NULL,
|
||||
) {
|
||||
}
|
||||
|
||||
}
|
||||
50
web/core/modules/migrate/src/Attribute/MigrateProcess.php
Normal file
50
web/core/modules/migrate/src/Attribute/MigrateProcess.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\migrate\Attribute;
|
||||
|
||||
use Drupal\Component\Plugin\Attribute\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a MigrateProcess attribute.
|
||||
*
|
||||
* Plugin Namespace: Plugin\migrate\process
|
||||
*
|
||||
* For a working example, see
|
||||
* \Drupal\migrate\Plugin\migrate\process\DefaultValue
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigratePluginManager
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
* @see \Drupal\migrate\ProcessPluginBase
|
||||
* @see \Drupal\migrate\Attribute\MigrateDestination
|
||||
* @see \Drupal\migrate\Attribute\MigrateSource
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
class MigrateProcess extends Plugin {
|
||||
|
||||
/**
|
||||
* Constructs a migrate process plugin attribute object.
|
||||
*
|
||||
* @param string $id
|
||||
* A unique identifier for the process plugin.
|
||||
* @param bool $handle_multiples
|
||||
* (optional) Whether the plugin handles multiples itself. Typically these
|
||||
* plugins will expect an array as input and iterate over it themselves,
|
||||
* changing the whole array. For example the 'sub_process' and the 'flatten'
|
||||
* plugins. If the plugin only needs to change a single value, then it can
|
||||
* skip setting this attribute and let
|
||||
* \Drupal\migrate\MigrateExecutable::processRow() handle the iteration.
|
||||
* @param class-string|null $deriver
|
||||
* (optional) The deriver class.
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $id,
|
||||
public readonly bool $handle_multiples = FALSE,
|
||||
public readonly ?string $deriver = NULL,
|
||||
) {}
|
||||
|
||||
}
|
||||
89
web/core/modules/migrate/src/Attribute/MigrateSource.php
Normal file
89
web/core/modules/migrate/src/Attribute/MigrateSource.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\migrate\Attribute;
|
||||
|
||||
use Drupal\Component\Plugin\Attribute\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a MigrateSource attribute.
|
||||
*
|
||||
* Plugin Namespace: Plugin\migrate\source
|
||||
*
|
||||
* For a working example, see
|
||||
* \Drupal\migrate\Plugin\migrate\source\EmptySource
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigratePluginManager
|
||||
* @see \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
* @see \Drupal\migrate\Attribute\MigrateDestination
|
||||
* @see \Drupal\migrate\Attribute\MigrateProcess
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)]
|
||||
class MigrateSource extends Plugin implements MultipleProviderAttributeInterface {
|
||||
|
||||
/**
|
||||
* The providers of the source plugin.
|
||||
*/
|
||||
protected array $providers = [];
|
||||
|
||||
/**
|
||||
* Constructs a migrate source plugin attribute object.
|
||||
*
|
||||
* @param string $id
|
||||
* A unique identifier for the source plugin.
|
||||
* @param bool $requirements_met
|
||||
* (optional) Whether requirements are met. Defaults to true. The source
|
||||
* plugin itself determines how the value is used. For example, Migrate
|
||||
* Drupal's source plugins expect source_module to be the name of a module
|
||||
* that must be installed and enabled in the source database.
|
||||
* @param mixed $minimum_version
|
||||
* (optional) Specifies the minimum version of the source provider. This can
|
||||
* be any type, and the source plugin itself determines how it is used. For
|
||||
* example, Migrate Drupal's source plugins expect this to be an integer
|
||||
* representing the minimum installed database schema version of the module
|
||||
* specified by source_module.
|
||||
* @param class-string|null $deriver
|
||||
* (optional) The deriver class.
|
||||
*
|
||||
* @see \Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase::checkRequirements
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $id,
|
||||
public bool $requirements_met = TRUE,
|
||||
public readonly mixed $minimum_version = NULL,
|
||||
public readonly ?string $deriver = NULL,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setProvider(string $provider): void {
|
||||
$this->setProviders([$provider]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProviders(): array {
|
||||
return $this->providers;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setProviders(array $providers): void {
|
||||
if ($providers) {
|
||||
parent::setProvider(reset($providers));
|
||||
}
|
||||
else {
|
||||
$this->provider = NULL;
|
||||
}
|
||||
$this->providers = $providers;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\migrate\Attribute;
|
||||
|
||||
use Drupal\Component\Plugin\Attribute\AttributeInterface;
|
||||
|
||||
/**
|
||||
* Defines a common interface for attributes with multiple providers.
|
||||
*
|
||||
* @internal
|
||||
* This is a temporary solution to the fact that migration source plugins have
|
||||
* more than one provider. This functionality will be moved to core in
|
||||
* https://www.drupal.org/node/2786355.
|
||||
*/
|
||||
interface MultipleProviderAttributeInterface extends AttributeInterface {
|
||||
|
||||
/**
|
||||
* Gets the name of the provider of the attribute class.
|
||||
*
|
||||
* @return string|null
|
||||
* The provider of the attribute. If there are multiple providers the first
|
||||
* is returned.
|
||||
*/
|
||||
public function getProvider(): ?string;
|
||||
|
||||
/**
|
||||
* Gets the provider names of the attribute class.
|
||||
*
|
||||
* @return string[]
|
||||
* The providers of the attribute.
|
||||
*/
|
||||
public function getProviders(): array;
|
||||
|
||||
/**
|
||||
* Sets the provider names of the attribute class.
|
||||
*
|
||||
* @param string[] $providers
|
||||
* The providers of the attribute.
|
||||
*/
|
||||
public function setProviders(array $providers): void;
|
||||
|
||||
}
|
||||
27
web/core/modules/migrate/src/Audit/AuditException.php
Normal file
27
web/core/modules/migrate/src/Audit/AuditException.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Audit;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
|
||||
/**
|
||||
* Defines an exception to throw if an error occurs during a migration audit.
|
||||
*/
|
||||
class AuditException extends \RuntimeException {
|
||||
|
||||
/**
|
||||
* AuditException constructor.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration that caused the exception.
|
||||
* @param string $message
|
||||
* The reason the audit failed.
|
||||
* @param \Throwable $previous
|
||||
* (optional) The previous exception.
|
||||
*/
|
||||
public function __construct(MigrationInterface $migration, $message, ?\Throwable $previous = NULL) {
|
||||
$message = sprintf('Cannot audit migration %s: %s', $migration->id(), $message);
|
||||
parent::__construct($message, 0, $previous);
|
||||
}
|
||||
|
||||
}
|
||||
149
web/core/modules/migrate/src/Audit/AuditResult.php
Normal file
149
web/core/modules/migrate/src/Audit/AuditResult.php
Normal file
@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Audit;
|
||||
|
||||
use Drupal\Component\Render\MarkupInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
|
||||
/**
|
||||
* Encapsulates the result of a migration audit.
|
||||
*/
|
||||
class AuditResult implements MarkupInterface, \Countable {
|
||||
|
||||
/**
|
||||
* The audited migration.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationInterface
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* The result of the audit (TRUE if passed, FALSE otherwise).
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $status;
|
||||
|
||||
/**
|
||||
* The reasons why the migration passed or failed the audit.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $reasons = [];
|
||||
|
||||
/**
|
||||
* AuditResult constructor.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The audited migration.
|
||||
* @param bool $status
|
||||
* The result of the audit (TRUE if passed, FALSE otherwise).
|
||||
* @param string[] $reasons
|
||||
* (optional) The reasons why the migration passed or failed the audit.
|
||||
*/
|
||||
public function __construct(MigrationInterface $migration, $status, array $reasons = []) {
|
||||
if (!is_bool($status)) {
|
||||
throw new \InvalidArgumentException('Audit results must have a boolean status.');
|
||||
}
|
||||
$this->migration = $migration;
|
||||
$this->status = $status;
|
||||
array_walk($reasons, [$this, 'addReason']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the audited migration.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrationInterface
|
||||
* The audited migration.
|
||||
*/
|
||||
public function getMigration() {
|
||||
return $this->migration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the boolean result of the audit.
|
||||
*
|
||||
* @return bool
|
||||
* The result of the audit. TRUE if the migration passed the audit, FALSE
|
||||
* otherwise.
|
||||
*/
|
||||
public function passed() {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a reason why the migration passed or failed the audit.
|
||||
*
|
||||
* @param string|object $reason
|
||||
* The reason to add. Can be a string or a string-castable object.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addReason($reason) {
|
||||
array_push($this->reasons, (string) $reason);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a passing audit result for a migration.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The audited migration.
|
||||
* @param string[] $reasons
|
||||
* (optional) The reasons why the migration passed the audit.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function pass(MigrationInterface $migration, array $reasons = []) {
|
||||
return new static($migration, TRUE, $reasons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a failing audit result for a migration.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The audited migration.
|
||||
* @param array $reasons
|
||||
* (optional) The reasons why the migration failed the audit.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function fail(MigrationInterface $migration, array $reasons = []) {
|
||||
return new static($migration, FALSE, $reasons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Countable::count() for Twig template compatibility.
|
||||
*
|
||||
* @return int
|
||||
* The number of reasons why the migration passed or failed the audit.
|
||||
*
|
||||
* @see \Drupal\Component\Render\MarkupInterface
|
||||
*/
|
||||
public function count(): int {
|
||||
return count($this->reasons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reasons the migration passed or failed, as a string.
|
||||
*
|
||||
* @return string
|
||||
* The reasons why the migration passed or failed the audit.
|
||||
*
|
||||
* @see \Drupal\Component\Render\MarkupInterface
|
||||
*/
|
||||
public function __toString() {
|
||||
return implode("\n", $this->reasons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reasons the migration passed or failed, for JSON serialization.
|
||||
*
|
||||
* @return string[]
|
||||
* The reasons why the migration passed or failed the audit.
|
||||
*/
|
||||
public function jsonSerialize(): string {
|
||||
return $this->reasons;
|
||||
}
|
||||
|
||||
}
|
||||
42
web/core/modules/migrate/src/Audit/AuditorInterface.php
Normal file
42
web/core/modules/migrate/src/Audit/AuditorInterface.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Audit;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for migration auditors.
|
||||
*
|
||||
* A migration auditor is a class which can examine a migration to determine if
|
||||
* it will cause conflicts with data already existing in the destination system.
|
||||
* What kind of auditing it does, and how it does it, is up to the implementing
|
||||
* class.
|
||||
*/
|
||||
interface AuditorInterface {
|
||||
|
||||
/**
|
||||
* Audits a migration.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration to audit.
|
||||
*
|
||||
* @throws \Drupal\migrate\Audit\AuditException
|
||||
* If the audit fails.
|
||||
*
|
||||
* @return \Drupal\migrate\Audit\AuditResult
|
||||
* The result of the audit.
|
||||
*/
|
||||
public function audit(MigrationInterface $migration);
|
||||
|
||||
/**
|
||||
* Audits a set of migrations.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface[] $migrations
|
||||
* The migrations to audit.
|
||||
*
|
||||
* @return \Drupal\migrate\Audit\AuditResult[]
|
||||
* The audit results, keyed by migration ID.
|
||||
*/
|
||||
public function auditMultiple(array $migrations);
|
||||
|
||||
}
|
||||
26
web/core/modules/migrate/src/Audit/HighestIdInterface.php
Normal file
26
web/core/modules/migrate/src/Audit/HighestIdInterface.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Audit;
|
||||
|
||||
/**
|
||||
* Defines an interface for destination and ID maps which track a highest ID.
|
||||
*
|
||||
* When implemented by destination plugins, getHighestId() should return the
|
||||
* highest ID of the destination entity type that exists in the system. So, for
|
||||
* example, the entity:node plugin should return the highest node ID that
|
||||
* exists, regardless of whether it was created by a migration.
|
||||
*
|
||||
* When implemented by an ID map, getHighestId() should return the highest
|
||||
* migrated ID of the destination entity type.
|
||||
*/
|
||||
interface HighestIdInterface {
|
||||
|
||||
/**
|
||||
* Returns the highest ID tracked by the implementing plugin.
|
||||
*
|
||||
* @return int
|
||||
* The highest ID.
|
||||
*/
|
||||
public function getHighestId();
|
||||
|
||||
}
|
||||
102
web/core/modules/migrate/src/Audit/IdAuditor.php
Normal file
102
web/core/modules/migrate/src/Audit/IdAuditor.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Audit;
|
||||
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\migrate\Plugin\migrate\destination\EntityContentComplete;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
|
||||
// cspell:ignore destid
|
||||
|
||||
/**
|
||||
* Audits migrations that create content entities in the destination system.
|
||||
*/
|
||||
class IdAuditor implements AuditorInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function audit(MigrationInterface $migration) {
|
||||
// If the migration does not opt into auditing, it passes.
|
||||
if (!$migration->isAuditable()) {
|
||||
return AuditResult::pass($migration);
|
||||
}
|
||||
|
||||
$interface = HighestIdInterface::class;
|
||||
|
||||
$destination = $migration->getDestinationPlugin();
|
||||
if (!$destination instanceof HighestIdInterface) {
|
||||
throw new AuditException($migration, "Destination does not implement $interface");
|
||||
}
|
||||
|
||||
$id_map = $migration->getIdMap();
|
||||
if (!$id_map instanceof HighestIdInterface) {
|
||||
throw new AuditException($migration, "ID map does not implement $interface");
|
||||
}
|
||||
|
||||
if ($destination->getHighestId() > $id_map->getHighestId() || ($destination instanceof EntityContentComplete && !$this->auditEntityComplete($migration))) {
|
||||
return AuditResult::fail($migration, [
|
||||
$this->t('The destination system contains data which was not created by a migration.'),
|
||||
]);
|
||||
}
|
||||
|
||||
return AuditResult::pass($migration);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function auditMultiple(array $migrations) {
|
||||
$conflicts = [];
|
||||
|
||||
foreach ($migrations as $migration) {
|
||||
$migration_id = $migration->getPluginId();
|
||||
$conflicts[$migration_id] = $this->audit($migration);
|
||||
}
|
||||
ksort($conflicts);
|
||||
return $conflicts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Audits an EntityComplete migration.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration to audit.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the audit passes and FALSE if not.
|
||||
*
|
||||
* @todo Refactor in https://www.drupal.org/project/drupal/issues/3061676 or
|
||||
* https://www.drupal.org/project/drupal/issues/3091004
|
||||
*/
|
||||
private function auditEntityComplete(MigrationInterface $migration) {
|
||||
$map_table = $migration->getIdMap()->mapTableName();
|
||||
|
||||
$database = \Drupal::database();
|
||||
if (!$database->schema()->tableExists($map_table)) {
|
||||
throw new \InvalidArgumentException();
|
||||
}
|
||||
|
||||
$query = $database->select($map_table, 'map')
|
||||
->fields('map', ['destid2'])
|
||||
->range(0, 1)
|
||||
->orderBy('destid2', 'DESC');
|
||||
$max = (int) $query->execute()->fetchField();
|
||||
|
||||
// Make a migration based on node_complete but with an entity_revision
|
||||
// destination.
|
||||
$revision_migration = $migration->getPluginDefinition();
|
||||
$revision_migration['id'] = $migration->getPluginId() . '-revision';
|
||||
$revision_migration['destination']['plugin'] = 'entity_revision:node';
|
||||
$revision_migration = \Drupal::service('plugin.manager.migration')->createStubMigration($revision_migration);
|
||||
|
||||
// Get the highest node revision ID.
|
||||
$destination = $revision_migration->getDestinationPlugin();
|
||||
$highest = $destination->getHighestId();
|
||||
|
||||
return $max <= $highest;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,296 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Controller;
|
||||
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Database\DatabaseConnectionRefusedException;
|
||||
use Drupal\Core\Database\DatabaseNotFoundException;
|
||||
use Drupal\Core\Database\Query\SelectInterface;
|
||||
use Drupal\Core\Form\FormBuilderInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\migrate\Exception\RequirementsException;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
// cspell:ignore sourceid
|
||||
|
||||
/**
|
||||
* Provides controller methods for the Message form.
|
||||
*/
|
||||
class MigrateMessageController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* Constructs a MigrateController.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $database
|
||||
* A database connection.
|
||||
* @param \Drupal\Core\Form\FormBuilderInterface $formBuilder
|
||||
* The form builder service.
|
||||
* @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migrationPluginManager
|
||||
* The migration plugin manager.
|
||||
*/
|
||||
public function __construct(
|
||||
protected Connection $database,
|
||||
FormBuilderInterface $formBuilder,
|
||||
protected MigrationPluginManagerInterface $migrationPluginManager,
|
||||
) {
|
||||
$this->formBuilder = $formBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an overview of migrate messages.
|
||||
*
|
||||
* @return array
|
||||
* A render array as expected by
|
||||
* \Drupal\Core\Render\RendererInterface::render().
|
||||
*/
|
||||
public function overview(): array {
|
||||
// Check if there are migrate_message tables.
|
||||
$tables = $this->database->schema()->findTables('migrate_message_%');
|
||||
if (empty($tables)) {
|
||||
$build['no_tables'] = [
|
||||
'#type' => 'item',
|
||||
'#markup' => $this->t('There are no migration message tables.'),
|
||||
];
|
||||
return $build;
|
||||
}
|
||||
|
||||
// There are migrate_message tables so build the overview form.
|
||||
$migrations = $this->migrationPluginManager->createInstances([]);
|
||||
|
||||
$header = [
|
||||
$this->t('Migration'),
|
||||
$this->t('Machine Name'),
|
||||
$this->t('Messages'),
|
||||
];
|
||||
|
||||
// Display the number of messages for each migration.
|
||||
$rows = [];
|
||||
foreach ($migrations as $id => $migration) {
|
||||
$message_count = $migration->getIdMap()->messageCount();
|
||||
// The message count is zero when there are no messages or when the
|
||||
// message table does not exist.
|
||||
if ($message_count == 0) {
|
||||
continue;
|
||||
}
|
||||
$row = [];
|
||||
$row['label'] = $migration->label();
|
||||
$row['machine_name'] = $id;
|
||||
$route_parameters = [
|
||||
'migration_id' => $migration->id(),
|
||||
];
|
||||
$row['messages'] = [
|
||||
'data' => [
|
||||
'#type' => 'link',
|
||||
'#title' => $message_count,
|
||||
'#url' => Url::fromRoute('migrate.messages.detail', $route_parameters),
|
||||
],
|
||||
];
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
$build['migrations_table'] = [
|
||||
'#type' => 'table',
|
||||
'#header' => $header,
|
||||
'#rows' => $rows,
|
||||
'#empty' => $this->t('No migration messages available.'),
|
||||
];
|
||||
$build['message_pager'] = ['#type' => 'pager'];
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a listing of migration messages for the given migration ID.
|
||||
*
|
||||
* @param string $migration_id
|
||||
* A migration ID.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request.
|
||||
*
|
||||
* @return array
|
||||
* A render array.
|
||||
*/
|
||||
public function details(string $migration_id, Request $request): array {
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
|
||||
$migration = $this->migrationPluginManager->createInstance($migration_id);
|
||||
|
||||
if (!$migration) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
// Get the map and message table names.
|
||||
$map_table = $migration->getIdMap()->mapTableName();
|
||||
$message_table = $migration->getIdMap()->messageTableName();
|
||||
|
||||
// If the map table does not exist then do not continue.
|
||||
if (!$this->database->schema()->tableExists($map_table)) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
// If there is a map table but no message table display an error.
|
||||
if (!$this->database->schema()->tableExists($message_table)) {
|
||||
$this->messenger()->addError($this->t('The message table is missing for this migration.'));
|
||||
return [];
|
||||
}
|
||||
|
||||
// Create the column header names.
|
||||
$header = [];
|
||||
$source_plugin = $migration->getSourcePlugin();
|
||||
// Create the column header names from the source plugin fields() method.
|
||||
// Fallback to the source_id name when the source ID is missing from
|
||||
// fields() method.
|
||||
try {
|
||||
$fields = $source_plugin->fields();
|
||||
}
|
||||
catch (DatabaseConnectionRefusedException | DatabaseNotFoundException | RequirementsException | \PDOException) {
|
||||
}
|
||||
|
||||
$source_id_field_names = array_keys($source_plugin->getIds());
|
||||
$count = 1;
|
||||
foreach ($source_id_field_names as $source_id_field_name) {
|
||||
$display_name = preg_replace(
|
||||
[
|
||||
'/^[Tt]he /',
|
||||
'/\.$/',
|
||||
], '', $fields[$source_id_field_name] ?? $source_id_field_name);
|
||||
$header[] = [
|
||||
'data' => ucfirst($display_name),
|
||||
'field' => 'sourceid' . $count++,
|
||||
'class' => [RESPONSIVE_PRIORITY_MEDIUM],
|
||||
];
|
||||
}
|
||||
|
||||
$header[] = [
|
||||
'data' => $this->t('Severity level'),
|
||||
'field' => 'level',
|
||||
'class' => [RESPONSIVE_PRIORITY_LOW],
|
||||
];
|
||||
$header[] = [
|
||||
'data' => $this->t('Message'),
|
||||
'field' => 'message',
|
||||
];
|
||||
|
||||
$levels = [
|
||||
MigrationInterface::MESSAGE_ERROR => $this->t('Error'),
|
||||
MigrationInterface::MESSAGE_WARNING => $this->t('Warning'),
|
||||
MigrationInterface::MESSAGE_NOTICE => $this->t('Notice'),
|
||||
MigrationInterface::MESSAGE_INFORMATIONAL => $this->t('Info'),
|
||||
];
|
||||
|
||||
// Gets each message row and the source ID(s) for that message.
|
||||
$query = $this->database->select($message_table, 'msg')
|
||||
->extend('\Drupal\Core\Database\Query\PagerSelectExtender')
|
||||
->extend('\Drupal\Core\Database\Query\TableSortExtender');
|
||||
// Not all messages have a matching row in the map table.
|
||||
$query->leftJoin($map_table, 'map', 'msg.source_ids_hash = map.source_ids_hash');
|
||||
$query->fields('msg');
|
||||
$query->fields('map');
|
||||
$this->addFilterToQuery($request, $query);
|
||||
$result = $query
|
||||
->limit(50)
|
||||
->orderByHeader($header)
|
||||
->execute();
|
||||
|
||||
// Build the rows to display.
|
||||
$rows = [];
|
||||
$add_explanation = FALSE;
|
||||
$num_ids = count($source_id_field_names);
|
||||
foreach ($result as $message_row) {
|
||||
$new_row = [];
|
||||
for ($count = 1; $count <= $num_ids; $count++) {
|
||||
$map_key = 'sourceid' . $count;
|
||||
$new_row[$map_key] = $message_row->$map_key ?? NULL;
|
||||
if (empty($new_row[$map_key])) {
|
||||
$new_row[$map_key] = $this->t('Not available');
|
||||
$add_explanation = TRUE;
|
||||
}
|
||||
}
|
||||
$new_row['level'] = $levels[$message_row->level];
|
||||
$new_row['message'] = $message_row->message;
|
||||
$rows[] = $new_row;
|
||||
}
|
||||
|
||||
// Build the complete form.
|
||||
$build['message_filter_form'] = $this->formBuilder->getForm('Drupal\migrate\Form\MessageForm');
|
||||
$build['message_table'] = [
|
||||
'#type' => 'table',
|
||||
'#header' => $header,
|
||||
'#rows' => $rows,
|
||||
'#empty' => $this->t('No messages for this migration.'),
|
||||
'#attributes' => ['id' => 'admin-migrate-msg', 'class' => ['admin-migrate-msg']],
|
||||
];
|
||||
$build['message_pager'] = ['#type' => 'pager'];
|
||||
|
||||
if ($add_explanation) {
|
||||
$build['explanation'] = [
|
||||
'#type' => 'item',
|
||||
'#markup' => $this->t("When there is an error processing a row, the migration system saves the error message but not the source ID(s) of the row. That is why some messages in this table have 'Not available' in the source ID column(s)."),
|
||||
];
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a filter to the query for migrate message administration.
|
||||
*
|
||||
* This method retrieves the session-based filters from the request and
|
||||
* applies them to the provided query object. If no filters are present, the
|
||||
* query is left unchanged.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request.
|
||||
* @param \Drupal\Core\Database\Query\SelectInterface $query
|
||||
* The database query.
|
||||
*/
|
||||
protected function addFilterToQuery(Request $request, SelectInterface $query): void {
|
||||
$session_filters = $request->getSession()->get('migration_messages_overview_filter', []);
|
||||
if (empty($session_filters)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build the condition.
|
||||
foreach ($session_filters as $filter) {
|
||||
if (empty($filter['value'])) {
|
||||
continue;
|
||||
}
|
||||
switch ($filter['type']) {
|
||||
case 'array':
|
||||
$values = array_values($filter['value']);
|
||||
if ($filter['field'] === 'msg.level') {
|
||||
$values = array_map(fn($x) => (int) $x, $values);
|
||||
}
|
||||
$query->condition($filter['field'], $values, 'IN');
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
$query->condition($filter['field'], "%{$filter['value']}%", 'LIKE');
|
||||
break;
|
||||
|
||||
default:
|
||||
$query->condition($filter['field'], $filter['value']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the title for the details page.
|
||||
*
|
||||
* @param string $migration_id
|
||||
* A migration ID.
|
||||
*
|
||||
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
|
||||
* The translated title.
|
||||
*/
|
||||
public function title(string $migration_id): TranslatableMarkup {
|
||||
return $this->t(
|
||||
'Messages of %migration',
|
||||
['%migration' => $migration_id]
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
56
web/core/modules/migrate/src/EntityFieldDefinitionTrait.php
Normal file
56
web/core/modules/migrate/src/EntityFieldDefinitionTrait.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
/**
|
||||
* The entity field definition trait.
|
||||
*/
|
||||
trait EntityFieldDefinitionTrait {
|
||||
|
||||
/**
|
||||
* Gets the field definition from a specific entity base field.
|
||||
*
|
||||
* The method takes the field ID as an argument and returns the field storage
|
||||
* definition to be used in getIds() by querying the destination entity base
|
||||
* field definition.
|
||||
*
|
||||
* @param string $key
|
||||
* The field ID key.
|
||||
*
|
||||
* @return array
|
||||
* An associative array with a structure that contains the field type, keyed
|
||||
* as 'type', together with field storage settings as they are returned by
|
||||
* FieldStorageDefinitionInterface::getSettings().
|
||||
*
|
||||
* @see \Drupal\Core\Field\FieldStorageDefinitionInterface::getSettings()
|
||||
*/
|
||||
protected function getDefinitionFromEntity($key) {
|
||||
$plugin_id = $this->getPluginId();
|
||||
$entity_type_id = $this->getEntityTypeId($plugin_id);
|
||||
/** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $definitions */
|
||||
$definitions = $this->entityFieldManager->getBaseFieldDefinitions($entity_type_id);
|
||||
$field_definition = $definitions[$key];
|
||||
|
||||
return [
|
||||
'type' => $field_definition->getType(),
|
||||
] + $field_definition->getSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the entity type from configuration or plugin ID.
|
||||
*
|
||||
* @param string $plugin_id
|
||||
* The plugin ID.
|
||||
*
|
||||
* @return string
|
||||
* The entity type.
|
||||
*/
|
||||
protected static function getEntityTypeId($plugin_id) {
|
||||
$entity_type_id = NULL;
|
||||
if (strpos($plugin_id, static::DERIVATIVE_SEPARATOR)) {
|
||||
[, $entity_type_id] = explode(static::DERIVATIVE_SEPARATOR, $plugin_id, 2);
|
||||
}
|
||||
return $entity_type_id;
|
||||
}
|
||||
|
||||
}
|
||||
63
web/core/modules/migrate/src/Event/EventBase.php
Normal file
63
web/core/modules/migrate/src/Event/EventBase.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateMessageInterface;
|
||||
use Drupal\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Base class for migration events.
|
||||
*/
|
||||
class EventBase extends Event {
|
||||
|
||||
/**
|
||||
* The migration.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationInterface
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* The current message service.
|
||||
*
|
||||
* @var \Drupal\migrate\MigrateMessageInterface
|
||||
*/
|
||||
protected $message;
|
||||
|
||||
/**
|
||||
* Constructs a Migrate event object.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration being run.
|
||||
* @param \Drupal\migrate\MigrateMessageInterface $message
|
||||
* The Migrate message service.
|
||||
*/
|
||||
public function __construct(MigrationInterface $migration, MigrateMessageInterface $message) {
|
||||
$this->migration = $migration;
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the migration.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrationInterface
|
||||
* The migration being run.
|
||||
*/
|
||||
public function getMigration() {
|
||||
return $this->migration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message using the Migrate message service.
|
||||
*
|
||||
* @param string $message
|
||||
* The message to log.
|
||||
* @param string $type
|
||||
* The type of message, for example: status or warning.
|
||||
*/
|
||||
public function logMessage($message, $type = 'status') {
|
||||
$this->message->display($message, $type);
|
||||
}
|
||||
|
||||
}
|
||||
26
web/core/modules/migrate/src/Event/ImportAwareInterface.php
Normal file
26
web/core/modules/migrate/src/Event/ImportAwareInterface.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
/**
|
||||
* Interface for plugins that react to pre- or post-import events.
|
||||
*/
|
||||
interface ImportAwareInterface {
|
||||
|
||||
/**
|
||||
* Performs pre-import tasks.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateImportEvent $event
|
||||
* The pre-import event object.
|
||||
*/
|
||||
public function preImport(MigrateImportEvent $event);
|
||||
|
||||
/**
|
||||
* Performs post-import tasks.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateImportEvent $event
|
||||
* The post-import event object.
|
||||
*/
|
||||
public function postImport(MigrateImportEvent $event);
|
||||
|
||||
}
|
||||
187
web/core/modules/migrate/src/Event/MigrateEvents.php
Normal file
187
web/core/modules/migrate/src/Event/MigrateEvents.php
Normal file
@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
// cspell:ignore idmap
|
||||
|
||||
/**
|
||||
* Defines events for the migration system.
|
||||
*
|
||||
* @see \Drupal\migrate\Event\MigrateMapSaveEvent
|
||||
* @see \Drupal\migrate\Event\MigrateMapDeleteEvent
|
||||
* @see \Drupal\migrate\Event\MigrateImportEvent
|
||||
* @see \Drupal\migrate\Event\MigratePreRowSaveEvent
|
||||
* @see \Drupal\migrate\Event\MigratePostRowSaveEvent
|
||||
* @see \Drupal\migrate\Event\MigrateRollbackEvent
|
||||
* @see \Drupal\migrate\Event\MigrateRowDeleteEvent
|
||||
* @see \Drupal\migrate\Event\MigrateIdMapMessageEvent
|
||||
*/
|
||||
final class MigrateEvents {
|
||||
|
||||
/**
|
||||
* Name of the event fired when saving to a migration's map.
|
||||
*
|
||||
* This event allows modules to perform an action whenever the disposition of
|
||||
* an item being migrated is saved to the map table. The event listener method
|
||||
* receives a \Drupal\migrate\Event\MigrateMapSaveEvent instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see \Drupal\migrate\Event\MigrateMapSaveEvent
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const MAP_SAVE = 'migrate.map_save';
|
||||
|
||||
/**
|
||||
* Name of the event fired when removing an entry from a migration's map.
|
||||
*
|
||||
* This event allows modules to perform an action whenever a row is deleted
|
||||
* from a migration's map table (implying it has been rolled back). The event
|
||||
* listener method receives a \Drupal\migrate\Event\MigrateMapDeleteEvent
|
||||
* instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see \Drupal\migrate\Event\MigrateMapDeleteEvent
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const MAP_DELETE = 'migrate.map_delete';
|
||||
|
||||
/**
|
||||
* Name of the event fired when beginning a migration import operation.
|
||||
*
|
||||
* This event allows modules to perform an action whenever a migration import
|
||||
* operation is about to begin. The event listener method receives a
|
||||
* \Drupal\migrate\Event\MigrateImportEvent instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see \Drupal\migrate\Event\MigrateImportEvent
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PRE_IMPORT = 'migrate.pre_import';
|
||||
|
||||
/**
|
||||
* Name of the event fired when finishing a migration import operation.
|
||||
*
|
||||
* This event allows modules to perform an action whenever a migration import
|
||||
* operation is completing. The event listener method receives a
|
||||
* \Drupal\migrate\Event\MigrateImportEvent instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see \Drupal\migrate\Event\MigrateImportEvent
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const POST_IMPORT = 'migrate.post_import';
|
||||
|
||||
/**
|
||||
* Name of the event fired when about to import a single item.
|
||||
*
|
||||
* This event allows modules to perform an action whenever a specific item
|
||||
* is about to be saved by the destination plugin. The event listener method
|
||||
* receives a \Drupal\migrate\Event\MigratePreRowSaveEvent instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see \Drupal\migrate\Event\MigratePreRowSaveEvent
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PRE_ROW_SAVE = 'migrate.pre_row_save';
|
||||
|
||||
/**
|
||||
* Name of the event fired just after a single item has been imported.
|
||||
*
|
||||
* This event allows modules to perform an action whenever a specific item
|
||||
* has been saved by the destination plugin. The event listener method
|
||||
* receives a \Drupal\migrate\Event\MigratePostRowSaveEvent instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see \Drupal\migrate\Event\MigratePostRowSaveEvent
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const POST_ROW_SAVE = 'migrate.post_row_save';
|
||||
|
||||
/**
|
||||
* Name of the event fired when beginning a migration rollback operation.
|
||||
*
|
||||
* This event allows modules to perform an action whenever a migration
|
||||
* rollback operation is about to begin. The event listener method receives a
|
||||
* \Drupal\migrate\Event\MigrateRollbackEvent instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see \Drupal\migrate\Event\MigrateRollbackEvent
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PRE_ROLLBACK = 'migrate.pre_rollback';
|
||||
|
||||
/**
|
||||
* Name of the event fired when finishing a migration rollback operation.
|
||||
*
|
||||
* This event allows modules to perform an action whenever a migration
|
||||
* rollback operation is completing. The event listener method receives a
|
||||
* \Drupal\migrate\Event\MigrateRollbackEvent instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see \Drupal\migrate\Event\MigrateRollbackEvent
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const POST_ROLLBACK = 'migrate.post_rollback';
|
||||
|
||||
/**
|
||||
* Name of the event fired when about to delete a single item.
|
||||
*
|
||||
* This event allows modules to perform an action whenever a specific item
|
||||
* is about to be deleted by the destination plugin. The event listener method
|
||||
* receives a \Drupal\migrate\Event\MigrateRowDeleteEvent instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see \Drupal\migrate\Event\MigrateRowDeleteEvent
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PRE_ROW_DELETE = 'migrate.pre_row_delete';
|
||||
|
||||
/**
|
||||
* Name of the event fired just after a single item has been deleted.
|
||||
*
|
||||
* This event allows modules to perform an action whenever a specific item
|
||||
* has been deleted by the destination plugin. The event listener method
|
||||
* receives a \Drupal\migrate\Event\MigrateRowDeleteEvent instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see \Drupal\migrate\Event\MigrateRowDeleteEvent
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const POST_ROW_DELETE = 'migrate.post_row_delete';
|
||||
|
||||
/**
|
||||
* Name of the event fired when saving a message to the ID map.
|
||||
*
|
||||
* This event allows modules to perform an action whenever a message is being
|
||||
* logged by the ID map. The event listener method receives a
|
||||
* \Drupal\migrate\Event\MigrateIdMapMessageEvent instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see \Drupal\migrate\Event\MigrateIdMapMessageEvent
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const IDMAP_MESSAGE = 'migrate.idmap_message';
|
||||
|
||||
}
|
||||
104
web/core/modules/migrate/src/Event/MigrateIdMapMessageEvent.php
Normal file
104
web/core/modules/migrate/src/Event/MigrateIdMapMessageEvent.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Wraps an ID map message event for event listeners.
|
||||
*/
|
||||
class MigrateIdMapMessageEvent extends Event {
|
||||
|
||||
/**
|
||||
* Migration entity.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationInterface
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* Array of values uniquely identifying the source row.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sourceIdValues;
|
||||
|
||||
/**
|
||||
* Message to be logged.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $message;
|
||||
|
||||
/**
|
||||
* Message severity.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $level;
|
||||
|
||||
/**
|
||||
* Constructs a post-save event object.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* Migration entity.
|
||||
* @param array $source_id_values
|
||||
* Values represent the source ID.
|
||||
* @param string $message
|
||||
* The message.
|
||||
* @param int $level
|
||||
* Severity level (one of the MigrationInterface::MESSAGE_* constants).
|
||||
*/
|
||||
public function __construct(MigrationInterface $migration, array $source_id_values, $message, $level) {
|
||||
$this->migration = $migration;
|
||||
$this->sourceIdValues = $source_id_values;
|
||||
$this->message = $message;
|
||||
$this->level = $level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the migration entity.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrationInterface
|
||||
* The migration entity involved.
|
||||
*/
|
||||
public function getMigration() {
|
||||
return $this->migration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the source ID values.
|
||||
*
|
||||
* @return array
|
||||
* The source ID as an array.
|
||||
*/
|
||||
public function getSourceIdValues() {
|
||||
return $this->sourceIdValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message to be logged.
|
||||
*
|
||||
* @return string
|
||||
* The message text.
|
||||
*/
|
||||
public function getMessage() {
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the severity level of the message.
|
||||
*
|
||||
* Message levels are declared in MigrationInterface and start with MESSAGE_.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrationInterface
|
||||
*
|
||||
* @return int
|
||||
* The message level.
|
||||
*/
|
||||
public function getLevel() {
|
||||
return $this->level;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
/**
|
||||
* Wraps a pre- or post-import event for event listeners.
|
||||
*/
|
||||
class MigrateImportEvent extends EventBase {}
|
||||
61
web/core/modules/migrate/src/Event/MigrateMapDeleteEvent.php
Normal file
61
web/core/modules/migrate/src/Event/MigrateMapDeleteEvent.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Wraps a migrate map delete event for event listeners.
|
||||
*/
|
||||
class MigrateMapDeleteEvent extends Event {
|
||||
|
||||
/**
|
||||
* Map plugin.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrateIdMapInterface
|
||||
*/
|
||||
protected $map;
|
||||
|
||||
/**
|
||||
* Array of source ID fields.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sourceId;
|
||||
|
||||
/**
|
||||
* Constructs a migration map delete event object.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrateIdMapInterface $map
|
||||
* Map plugin.
|
||||
* @param array $source_id
|
||||
* Array of source ID fields representing the object being deleted from the
|
||||
* map.
|
||||
*/
|
||||
public function __construct(MigrateIdMapInterface $map, array $source_id) {
|
||||
$this->map = $map;
|
||||
$this->sourceId = $source_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the map plugin.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrateIdMapInterface
|
||||
* The map plugin that caused the event to fire.
|
||||
*/
|
||||
public function getMap() {
|
||||
return $this->map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the source ID of the item being removed from the map.
|
||||
*
|
||||
* @return array
|
||||
* Array of source ID fields.
|
||||
*/
|
||||
public function getSourceId() {
|
||||
return $this->sourceId;
|
||||
}
|
||||
|
||||
}
|
||||
60
web/core/modules/migrate/src/Event/MigrateMapSaveEvent.php
Normal file
60
web/core/modules/migrate/src/Event/MigrateMapSaveEvent.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Wraps a migrate map save event for event listeners.
|
||||
*/
|
||||
class MigrateMapSaveEvent extends Event {
|
||||
|
||||
/**
|
||||
* Map plugin.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrateIdMapInterface
|
||||
*/
|
||||
protected $map;
|
||||
|
||||
/**
|
||||
* Array of fields being saved to the map, keyed by field name.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fields;
|
||||
|
||||
/**
|
||||
* Constructs a migration map event object.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrateIdMapInterface $map
|
||||
* Map plugin.
|
||||
* @param array $fields
|
||||
* Array of fields being saved to the map.
|
||||
*/
|
||||
public function __construct(MigrateIdMapInterface $map, array $fields) {
|
||||
$this->map = $map;
|
||||
$this->fields = $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the map plugin.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrateIdMapInterface
|
||||
* The map plugin that caused the event to fire.
|
||||
*/
|
||||
public function getMap() {
|
||||
return $this->map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fields about to be saved to the map.
|
||||
*
|
||||
* @return array
|
||||
* Array of map fields, keyed by field name.
|
||||
*/
|
||||
public function getFields() {
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateMessageInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Wraps a post-save event for event listeners.
|
||||
*/
|
||||
class MigratePostRowSaveEvent extends MigratePreRowSaveEvent {
|
||||
|
||||
/**
|
||||
* The row's destination ID.
|
||||
*
|
||||
* @var array|bool
|
||||
*/
|
||||
protected $destinationIdValues = [];
|
||||
|
||||
/**
|
||||
* Constructs a post-save event object.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* Migration entity.
|
||||
* @param \Drupal\migrate\MigrateMessageInterface $message
|
||||
* The message interface.
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* Row object.
|
||||
* @param array|bool $destination_id_values
|
||||
* Values represent the destination ID.
|
||||
*/
|
||||
public function __construct(MigrationInterface $migration, MigrateMessageInterface $message, Row $row, $destination_id_values) {
|
||||
parent::__construct($migration, $message, $row);
|
||||
$this->destinationIdValues = $destination_id_values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the destination ID values.
|
||||
*
|
||||
* @return array
|
||||
* The destination ID as an array.
|
||||
*/
|
||||
public function getDestinationIdValues() {
|
||||
return $this->destinationIdValues;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateMessageInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Wraps a pre-save event for event listeners.
|
||||
*/
|
||||
class MigratePreRowSaveEvent extends EventBase {
|
||||
|
||||
/**
|
||||
* Row object.
|
||||
*
|
||||
* @var \Drupal\migrate\Row
|
||||
*/
|
||||
protected $row;
|
||||
|
||||
/**
|
||||
* Constructs a pre-save event object.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* Migration entity.
|
||||
* @param \Drupal\migrate\MigrateMessageInterface $message
|
||||
* The current migrate message service.
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The current row.
|
||||
*/
|
||||
public function __construct(MigrationInterface $migration, MigrateMessageInterface $message, Row $row) {
|
||||
parent::__construct($migration, $message);
|
||||
$this->row = $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the row object.
|
||||
*
|
||||
* @return \Drupal\migrate\Row
|
||||
* The row object about to be imported.
|
||||
*/
|
||||
public function getRow() {
|
||||
return $this->row;
|
||||
}
|
||||
|
||||
}
|
||||
40
web/core/modules/migrate/src/Event/MigrateRollbackEvent.php
Normal file
40
web/core/modules/migrate/src/Event/MigrateRollbackEvent.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Wraps a pre- or post-rollback event for event listeners.
|
||||
*/
|
||||
class MigrateRollbackEvent extends Event {
|
||||
|
||||
/**
|
||||
* Migration entity.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationInterface
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* Constructs a rollback event object.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* Migration entity.
|
||||
*/
|
||||
public function __construct(MigrationInterface $migration) {
|
||||
$this->migration = $migration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the migration entity.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrationInterface
|
||||
* The migration entity involved.
|
||||
*/
|
||||
public function getMigration() {
|
||||
return $this->migration;
|
||||
}
|
||||
|
||||
}
|
||||
60
web/core/modules/migrate/src/Event/MigrateRowDeleteEvent.php
Normal file
60
web/core/modules/migrate/src/Event/MigrateRowDeleteEvent.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Wraps a row deletion event for event listeners.
|
||||
*/
|
||||
class MigrateRowDeleteEvent extends Event {
|
||||
|
||||
/**
|
||||
* Migration entity.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationInterface
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* Values representing the destination ID.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $destinationIdValues;
|
||||
|
||||
/**
|
||||
* Constructs a row deletion event object.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* Migration entity.
|
||||
* @param array $destination_id_values
|
||||
* Values represent the destination ID.
|
||||
*/
|
||||
public function __construct(MigrationInterface $migration, $destination_id_values) {
|
||||
$this->migration = $migration;
|
||||
$this->destinationIdValues = $destination_id_values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the migration entity.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrationInterface
|
||||
* The migration being rolled back.
|
||||
*/
|
||||
public function getMigration() {
|
||||
return $this->migration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the destination ID values.
|
||||
*
|
||||
* @return array
|
||||
* The destination ID as an array.
|
||||
*/
|
||||
public function getDestinationIdValues() {
|
||||
return $this->destinationIdValues;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
/**
|
||||
* Interface for plugins that react to pre- or post-rollback events.
|
||||
*/
|
||||
interface RollbackAwareInterface {
|
||||
|
||||
/**
|
||||
* Performs pre-rollback tasks.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateRollbackEvent $event
|
||||
* The pre-rollback event object.
|
||||
*/
|
||||
public function preRollback(MigrateRollbackEvent $event);
|
||||
|
||||
/**
|
||||
* Performs post-rollback tasks.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateRollbackEvent $event
|
||||
* The post-rollback event object.
|
||||
*/
|
||||
public function postRollback(MigrateRollbackEvent $event);
|
||||
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Exception;
|
||||
|
||||
use Drupal\Core\Entity\EntityConstraintViolationListInterface;
|
||||
use Drupal\Core\Entity\RevisionableInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Symfony\Component\Validator\ConstraintViolationInterface;
|
||||
|
||||
/**
|
||||
* To throw when an entity generated during the import is not valid.
|
||||
*/
|
||||
class EntityValidationException extends MigrateException {
|
||||
|
||||
/**
|
||||
* The separator for combining multiple messages into a single string.
|
||||
*
|
||||
* Afterwards, the separator could be used to split a concatenated string
|
||||
* onto multiple lines.
|
||||
*
|
||||
* @code
|
||||
* explode(EntityValidationException::MESSAGES_SEPARATOR, $messages);
|
||||
* @endcode
|
||||
*/
|
||||
const MESSAGES_SEPARATOR = '||';
|
||||
|
||||
/**
|
||||
* The list of violations generated during the entity validation.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityConstraintViolationListInterface
|
||||
*/
|
||||
protected $violations;
|
||||
|
||||
/**
|
||||
* EntityValidationException constructor.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityConstraintViolationListInterface $violations
|
||||
* The list of violations generated during the entity validation.
|
||||
*/
|
||||
public function __construct(EntityConstraintViolationListInterface $violations) {
|
||||
$this->violations = $violations;
|
||||
|
||||
$entity = $this->violations->getEntity();
|
||||
$locator = $entity->getEntityTypeId();
|
||||
|
||||
if ($entity_id = $entity->id()) {
|
||||
$locator = sprintf('%s: %s', $locator, $entity_id);
|
||||
|
||||
if ($entity instanceof RevisionableInterface && $revision_id = $entity->getRevisionId()) {
|
||||
$locator .= sprintf(', revision: %s', $revision_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Example: "[user]: field_a=Violation 1., field_b=Violation 2.".
|
||||
// Example: "[user: 1]: field_a=Violation 1., field_b=Violation 2.".
|
||||
// Example: "[node: 19, revision: 12129]: field_a=Violation 1.".
|
||||
parent::__construct(sprintf('[%s]: %s', $locator, implode(static::MESSAGES_SEPARATOR, $this->getViolationMessages())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of violation messages.
|
||||
*
|
||||
* @return string[]
|
||||
* The list of violation messages.
|
||||
*/
|
||||
public function getViolationMessages() {
|
||||
$messages = [];
|
||||
|
||||
foreach ($this->violations as $violation) {
|
||||
assert($violation instanceof ConstraintViolationInterface);
|
||||
$messages[] = sprintf('%s=%s', $violation->getPropertyPath(), $violation->getMessage());
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of violations generated during the entity validation.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityConstraintViolationListInterface
|
||||
* The list of violations generated during the entity validation.
|
||||
*/
|
||||
public function getViolations() {
|
||||
return $this->violations;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Exception;
|
||||
|
||||
/**
|
||||
* Defines an exception thrown when a migration does not meet the requirements.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\RequirementsInterface
|
||||
*/
|
||||
class RequirementsException extends \RuntimeException {
|
||||
|
||||
/**
|
||||
* The missing requirements.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $requirements;
|
||||
|
||||
/**
|
||||
* Constructs a new RequirementsException instance.
|
||||
*
|
||||
* @param string $message
|
||||
* (optional) The Exception message to throw.
|
||||
* @param array $requirements
|
||||
* (optional) The missing requirements.
|
||||
* @param int $code
|
||||
* (optional) The Exception code.
|
||||
* @param \Throwable $previous
|
||||
* (optional) The previous exception used for the exception chaining.
|
||||
*/
|
||||
public function __construct($message = "", array $requirements = [], $code = 0, ?\Throwable $previous = NULL) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
|
||||
$this->requirements = $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of requirements.
|
||||
*
|
||||
* @return array
|
||||
* The requirements.
|
||||
*/
|
||||
public function getRequirements() {
|
||||
return $this->requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the requirements as a string.
|
||||
*
|
||||
* @return string
|
||||
* A formatted requirements string.
|
||||
*/
|
||||
public function getRequirementsString() {
|
||||
$output = '';
|
||||
foreach ($this->requirements as $requirement_type => $requirements) {
|
||||
if (!is_array($requirements)) {
|
||||
$requirements = [$requirements];
|
||||
}
|
||||
|
||||
foreach ($requirements as $value) {
|
||||
$output .= "$requirement_type: $value. ";
|
||||
}
|
||||
}
|
||||
return trim($output);
|
||||
}
|
||||
|
||||
}
|
||||
118
web/core/modules/migrate/src/Form/MessageForm.php
Normal file
118
web/core/modules/migrate/src/Form/MessageForm.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Migrate messages form.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class MessageForm extends FormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
$form = new static();
|
||||
$form->setStringTranslation($container->get('string_translation'));
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'migrate_messages_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$session_filters = $this->getRequest()->getSession()->get('migration_messages_overview_filter', []);
|
||||
$form['filters'] = [
|
||||
'#type' => 'details',
|
||||
'#open' => TRUE,
|
||||
'#title' => $this->t('Filter messages'),
|
||||
'#weight' => 0,
|
||||
];
|
||||
$form['filters']['message'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Message'),
|
||||
'#default_value' => $session_filters['message']['value'] ?? '',
|
||||
];
|
||||
$form['filters']['severity'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Severity level'),
|
||||
'#default_value' => $session_filters['severity']['value'] ?? [],
|
||||
'#options' => [
|
||||
MigrationInterface::MESSAGE_ERROR => $this->t('Error'),
|
||||
MigrationInterface::MESSAGE_WARNING => $this->t('Warning'),
|
||||
MigrationInterface::MESSAGE_NOTICE => $this->t('Notice'),
|
||||
MigrationInterface::MESSAGE_INFORMATIONAL => $this->t('Info'),
|
||||
],
|
||||
'#multiple' => TRUE,
|
||||
'#size' => 4,
|
||||
];
|
||||
$form['filters']['actions'] = [
|
||||
'#type' => 'actions',
|
||||
'#attributes' => ['class' => ['container-inline']],
|
||||
];
|
||||
$form['filters']['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Filter'),
|
||||
];
|
||||
$form['filters']['actions']['reset'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Reset'),
|
||||
'#submit' => ['::resetForm'],
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$filters['message'] = [
|
||||
'title' => $this->t('message'),
|
||||
'field' => 'msg.message',
|
||||
'type' => 'string',
|
||||
];
|
||||
$filters['severity'] = [
|
||||
'title' => $this->t('Severity'),
|
||||
'field' => 'msg.level',
|
||||
'type' => 'array',
|
||||
];
|
||||
$session_filters = $this->getRequest()->getSession()->get('migration_messages_overview_filter', []);
|
||||
foreach ($filters as $name => $filter) {
|
||||
if ($form_state->hasValue($name)) {
|
||||
$session_filters[$name] = [
|
||||
'field' => $filter['field'],
|
||||
'value' => $form_state->getValue($name),
|
||||
'type' => $filter['type'],
|
||||
];
|
||||
}
|
||||
}
|
||||
$this->getRequest()->getSession()->set('migration_messages_overview_filter', $session_filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the filter form.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*/
|
||||
public function resetForm(array $form, FormStateInterface $form_state): void {
|
||||
$this->getRequest()->getSession()->remove('migration_messages_overview_filter');
|
||||
}
|
||||
|
||||
}
|
||||
32
web/core/modules/migrate/src/Hook/MigrateHooks.php
Normal file
32
web/core/modules/migrate/src/Hook/MigrateHooks.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Hook;
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Hook implementations for migrate.
|
||||
*/
|
||||
class MigrateHooks {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
#[Hook('help')]
|
||||
public function help($route_name, RouteMatchInterface $route_match): ?string {
|
||||
switch ($route_name) {
|
||||
case 'help.page.migrate':
|
||||
$output = '<h2>' . $this->t('About') . '</h2>';
|
||||
$output .= '<p>';
|
||||
$output .= $this->t('The Migrate module provides a framework for migrating data, usually from an external source into your site. It does not provide a user interface. For more information, see the <a href=":migrate">online documentation for the Migrate module</a>.', [':migrate' => 'https://www.drupal.org/documentation/modules/migrate']);
|
||||
$output .= '</p>';
|
||||
return $output;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
/**
|
||||
* Interface for migration plugin manager for building dependencies.
|
||||
*/
|
||||
interface MigrateBuildDependencyInterface {
|
||||
|
||||
/**
|
||||
* Builds a dependency tree for the migrations and set their order.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface[] $migrations
|
||||
* Array of loaded migrations with their declared dependencies.
|
||||
* @param array $dynamic_ids
|
||||
* Keys are dynamic ids (for example node:*) values are a list of loaded
|
||||
* migration ids (for example node:page, node:article).
|
||||
*
|
||||
* @return array
|
||||
* An array of migrations.
|
||||
*/
|
||||
public function buildDependencyMigration(array $migrations, array $dynamic_ids);
|
||||
|
||||
}
|
||||
76
web/core/modules/migrate/src/MigrateException.php
Normal file
76
web/core/modules/migrate/src/MigrateException.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
|
||||
/**
|
||||
* Defines the migrate exception class.
|
||||
*/
|
||||
class MigrateException extends \Exception {
|
||||
|
||||
/**
|
||||
* The level of the error being reported.
|
||||
*
|
||||
* The value is a MigrationInterface::MESSAGE_* constant.
|
||||
*
|
||||
* @var int
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrationInterface
|
||||
*/
|
||||
protected $level;
|
||||
|
||||
/**
|
||||
* The status to record in the map table for the current item.
|
||||
*
|
||||
* The value is a MigrateIdMapInterface::STATUS_* constant.
|
||||
*
|
||||
* @var int
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateIdMapInterface
|
||||
*/
|
||||
protected $status;
|
||||
|
||||
/**
|
||||
* Constructs a MigrateException object.
|
||||
*
|
||||
* @param string $message
|
||||
* The message for the exception.
|
||||
* @param int $code
|
||||
* The Exception code.
|
||||
* @param \Throwable $previous
|
||||
* The previous exception used for the exception chaining.
|
||||
* @param int $level
|
||||
* The level of the error, a Migration::MESSAGE_* constant.
|
||||
* @param int $status
|
||||
* The status of the item for the map table, a MigrateMap::STATUS_*
|
||||
* constant.
|
||||
*/
|
||||
public function __construct($message = '', $code = 0, ?\Throwable $previous = NULL, $level = MigrationInterface::MESSAGE_ERROR, $status = MigrateIdMapInterface::STATUS_FAILED) {
|
||||
$this->level = $level;
|
||||
$this->status = $status;
|
||||
parent::__construct($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the level.
|
||||
*
|
||||
* @return int
|
||||
* An integer status code. @see Migration::MESSAGE_*
|
||||
*/
|
||||
public function getLevel() {
|
||||
return $this->level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status of the current item.
|
||||
*
|
||||
* @return int
|
||||
* An integer status code. @see MigrateMap::STATUS_*
|
||||
*/
|
||||
public function getStatus() {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
}
|
||||
623
web/core/modules/migrate/src/MigrateExecutable.php
Normal file
623
web/core/modules/migrate/src/MigrateExecutable.php
Normal file
@ -0,0 +1,623 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
use Drupal\Component\Utility\Bytes;
|
||||
use Drupal\Core\StringTranslation\ByteSizeMarkup;
|
||||
use Drupal\Core\Utility\Error;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\migrate\Event\MigrateEvents;
|
||||
use Drupal\migrate\Event\MigrateImportEvent;
|
||||
use Drupal\migrate\Event\MigratePostRowSaveEvent;
|
||||
use Drupal\migrate\Event\MigratePreRowSaveEvent;
|
||||
use Drupal\migrate\Event\MigrateRollbackEvent;
|
||||
use Drupal\migrate\Event\MigrateRowDeleteEvent;
|
||||
use Drupal\migrate\Exception\RequirementsException;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* Defines a migrate executable class.
|
||||
*/
|
||||
class MigrateExecutable implements MigrateExecutableInterface {
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* The configuration of the migration to do.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationInterface
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* Status of one row.
|
||||
*
|
||||
* The value is a MigrateIdMapInterface::STATUS_* constant, for example:
|
||||
* STATUS_IMPORTED.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $sourceRowStatus;
|
||||
|
||||
/**
|
||||
* The ratio of the memory limit at which an operation will be interrupted.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
protected $memoryThreshold = 0.85;
|
||||
|
||||
/**
|
||||
* The PHP memory_limit expressed in bytes.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $memoryLimit;
|
||||
|
||||
/**
|
||||
* The configuration values of the source.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sourceIdValues;
|
||||
|
||||
/**
|
||||
* An array of counts. Initially used for cache hit/miss tracking.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $counts = [];
|
||||
|
||||
/**
|
||||
* The source.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
*/
|
||||
protected $source;
|
||||
|
||||
/**
|
||||
* The event dispatcher.
|
||||
*
|
||||
* @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
|
||||
/**
|
||||
* Migration message service.
|
||||
*
|
||||
* @var \Drupal\migrate\MigrateMessageInterface
|
||||
*
|
||||
* @todo https://www.drupal.org/node/2822663 Make this protected.
|
||||
*/
|
||||
public $message;
|
||||
|
||||
/**
|
||||
* Constructs a MigrateExecutable and verifies and sets the memory limit.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration to run.
|
||||
* @param \Drupal\migrate\MigrateMessageInterface $message
|
||||
* (optional) The migrate message service.
|
||||
* @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $event_dispatcher
|
||||
* (optional) The event dispatcher.
|
||||
*/
|
||||
public function __construct(MigrationInterface $migration, ?MigrateMessageInterface $message = NULL, ?EventDispatcherInterface $event_dispatcher = NULL) {
|
||||
$this->migration = $migration;
|
||||
$this->message = $message ?: new MigrateMessage();
|
||||
$this->getIdMap()->setMessage($this->message);
|
||||
$this->eventDispatcher = $event_dispatcher;
|
||||
// Record the memory limit in bytes
|
||||
$limit = trim(ini_get('memory_limit'));
|
||||
if ($limit == '-1') {
|
||||
$this->memoryLimit = PHP_INT_MAX;
|
||||
}
|
||||
else {
|
||||
$this->memoryLimit = Bytes::toNumber($limit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the source.
|
||||
*
|
||||
* Makes sure source is initialized based on migration settings.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
* The source.
|
||||
*/
|
||||
protected function getSource() {
|
||||
if (!isset($this->source)) {
|
||||
$this->source = $this->migration->getSourcePlugin();
|
||||
}
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the event dispatcher.
|
||||
*
|
||||
* @return \Symfony\Contracts\EventDispatcher\EventDispatcherInterface
|
||||
* The event dispatcher service.
|
||||
*/
|
||||
protected function getEventDispatcher() {
|
||||
if (!$this->eventDispatcher) {
|
||||
$this->eventDispatcher = \Drupal::service('event_dispatcher');
|
||||
}
|
||||
return $this->eventDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import() {
|
||||
// Only begin the import operation if the migration is currently idle.
|
||||
if ($this->migration->getStatus() !== MigrationInterface::STATUS_IDLE) {
|
||||
$this->message->display($this->t('Migration @id is busy with another operation: @status',
|
||||
[
|
||||
'@id' => $this->migration->id(),
|
||||
// phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
|
||||
'@status' => $this->t($this->migration->getStatusLabel()),
|
||||
]), 'error');
|
||||
return MigrationInterface::RESULT_FAILED;
|
||||
}
|
||||
$this->getEventDispatcher()->dispatch(new MigrateImportEvent($this->migration, $this->message), MigrateEvents::PRE_IMPORT);
|
||||
|
||||
// Knock off migration if the requirements haven't been met.
|
||||
try {
|
||||
$this->migration->checkRequirements();
|
||||
}
|
||||
catch (RequirementsException $e) {
|
||||
$this->message->display(
|
||||
$this->t(
|
||||
'Migration @id did not meet the requirements. @message',
|
||||
[
|
||||
'@id' => $this->migration->id(),
|
||||
'@message' => $e->getMessage(),
|
||||
]
|
||||
),
|
||||
'error'
|
||||
);
|
||||
|
||||
return MigrationInterface::RESULT_FAILED;
|
||||
}
|
||||
|
||||
$this->migration->setStatus(MigrationInterface::STATUS_IMPORTING);
|
||||
$source = $this->getSource();
|
||||
|
||||
try {
|
||||
$source->rewind();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->message->display(
|
||||
$this->t('Migration failed with source plugin exception: @e in @file line @line', [
|
||||
'@e' => $e->getMessage(),
|
||||
'@file' => $e->getFile(),
|
||||
'@line' => $e->getLine(),
|
||||
]), 'error');
|
||||
$this->migration->setStatus(MigrationInterface::STATUS_IDLE);
|
||||
return MigrationInterface::RESULT_FAILED;
|
||||
}
|
||||
|
||||
// Get the process pipeline.
|
||||
$pipeline = FALSE;
|
||||
if ($source->valid()) {
|
||||
try {
|
||||
$pipeline = $this->migration->getProcessPlugins();
|
||||
}
|
||||
catch (MigrateException $e) {
|
||||
$row = $source->current();
|
||||
$this->sourceIdValues = $row->getSourceIdValues();
|
||||
$this->getIdMap()->saveIdMapping($row, [], $e->getStatus());
|
||||
$this->saveMessage($e->getMessage(), $e->getLevel());
|
||||
}
|
||||
}
|
||||
|
||||
$return = MigrationInterface::RESULT_COMPLETED;
|
||||
if ($pipeline) {
|
||||
$id_map = $this->getIdMap();
|
||||
$destination = $this->migration->getDestinationPlugin();
|
||||
while ($source->valid()) {
|
||||
$row = $source->current();
|
||||
$this->sourceIdValues = $row->getSourceIdValues();
|
||||
|
||||
try {
|
||||
foreach ($pipeline as $destination_property_name => $plugins) {
|
||||
$this->processPipeline($row, $destination_property_name, $plugins, NULL);
|
||||
}
|
||||
$save = TRUE;
|
||||
}
|
||||
catch (MigrateException $e) {
|
||||
$this->getIdMap()->saveIdMapping($row, [], $e->getStatus());
|
||||
$msg = sprintf("%s:%s:%s", $this->migration->getPluginId(), $destination_property_name, $e->getMessage());
|
||||
$this->saveMessage($msg, $e->getLevel());
|
||||
$save = FALSE;
|
||||
}
|
||||
catch (MigrateSkipRowException $e) {
|
||||
if ($e->getSaveToMap()) {
|
||||
$id_map->saveIdMapping($row, [], MigrateIdMapInterface::STATUS_IGNORED);
|
||||
}
|
||||
if ($message = trim($e->getMessage())) {
|
||||
$msg = sprintf("%s:%s: %s", $this->migration->getPluginId(), $destination_property_name, $message);
|
||||
$this->saveMessage($msg, MigrationInterface::MESSAGE_INFORMATIONAL);
|
||||
}
|
||||
$save = FALSE;
|
||||
}
|
||||
|
||||
if ($save) {
|
||||
try {
|
||||
$this->getEventDispatcher()
|
||||
->dispatch(new MigratePreRowSaveEvent($this->migration, $this->message, $row), MigrateEvents::PRE_ROW_SAVE);
|
||||
$destination_ids = $id_map->lookupDestinationIds($this->sourceIdValues);
|
||||
$destination_id_values = $destination_ids ? reset($destination_ids) : [];
|
||||
$destination_id_values = $destination->import($row, $destination_id_values);
|
||||
$this->getEventDispatcher()
|
||||
->dispatch(new MigratePostRowSaveEvent($this->migration, $this->message, $row, $destination_id_values), MigrateEvents::POST_ROW_SAVE);
|
||||
if ($destination_id_values) {
|
||||
// We do not save an idMap entry for config.
|
||||
if ($destination_id_values !== TRUE) {
|
||||
$id_map->saveIdMapping($row, $destination_id_values, $this->sourceRowStatus, $destination->rollbackAction());
|
||||
}
|
||||
}
|
||||
else {
|
||||
$id_map->saveIdMapping($row, [], MigrateIdMapInterface::STATUS_FAILED);
|
||||
if (!$id_map->messageCount()) {
|
||||
$message = $this->t('New object was not saved, no error provided');
|
||||
$this->saveMessage($message);
|
||||
$this->message->display($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (MigrateException $e) {
|
||||
$this->getIdMap()->saveIdMapping($row, [], $e->getStatus());
|
||||
$this->saveMessage($e->getMessage(), $e->getLevel());
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->getIdMap()
|
||||
->saveIdMapping($row, [], MigrateIdMapInterface::STATUS_FAILED);
|
||||
$this->handleException($e);
|
||||
}
|
||||
}
|
||||
|
||||
$this->sourceRowStatus = MigrateIdMapInterface::STATUS_IMPORTED;
|
||||
|
||||
// Check for memory exhaustion.
|
||||
if (($return = $this->checkStatus()) != MigrationInterface::RESULT_COMPLETED) {
|
||||
break;
|
||||
}
|
||||
|
||||
// If anyone has requested we stop, return the requested result.
|
||||
if ($this->migration->getStatus() == MigrationInterface::STATUS_STOPPING) {
|
||||
$return = $this->migration->getInterruptionResult();
|
||||
$this->migration->clearInterruptionResult();
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
$source->next();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->message->display(
|
||||
$this->t('Migration failed with source plugin exception: @e in @file line @line', [
|
||||
'@e' => $e->getMessage(),
|
||||
'@file' => $e->getFile(),
|
||||
'@line' => $e->getLine(),
|
||||
]), 'error');
|
||||
$this->migration->setStatus(MigrationInterface::STATUS_IDLE);
|
||||
return MigrationInterface::RESULT_FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->getEventDispatcher()->dispatch(new MigrateImportEvent($this->migration, $this->message), MigrateEvents::POST_IMPORT);
|
||||
$this->migration->setStatus(MigrationInterface::STATUS_IDLE);
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollback() {
|
||||
// Only begin the rollback operation if the migration is currently idle.
|
||||
if ($this->migration->getStatus() !== MigrationInterface::STATUS_IDLE) {
|
||||
// phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
|
||||
$this->message->display($this->t('Migration @id is busy with another operation: @status', ['@id' => $this->migration->id(), '@status' => $this->t($this->migration->getStatusLabel())]), 'error');
|
||||
return MigrationInterface::RESULT_FAILED;
|
||||
}
|
||||
|
||||
// Announce that rollback is about to happen.
|
||||
$this->getEventDispatcher()->dispatch(new MigrateRollbackEvent($this->migration), MigrateEvents::PRE_ROLLBACK);
|
||||
|
||||
// Optimistically assume things are going to work out; if not, $return will
|
||||
// be updated to some other status.
|
||||
$return = MigrationInterface::RESULT_COMPLETED;
|
||||
|
||||
$this->migration->setStatus(MigrationInterface::STATUS_ROLLING_BACK);
|
||||
$id_map = $this->getIdMap();
|
||||
$destination = $this->migration->getDestinationPlugin();
|
||||
|
||||
// Loop through each row in the map, and try to roll it back.
|
||||
$id_map->rewind();
|
||||
while ($id_map->valid()) {
|
||||
$destination_key = $id_map->currentDestination();
|
||||
if ($destination_key) {
|
||||
$map_row = $id_map->getRowByDestination($destination_key);
|
||||
if (!isset($map_row['rollback_action']) || $map_row['rollback_action'] == MigrateIdMapInterface::ROLLBACK_DELETE) {
|
||||
$this->getEventDispatcher()
|
||||
->dispatch(new MigrateRowDeleteEvent($this->migration, $destination_key), MigrateEvents::PRE_ROW_DELETE);
|
||||
$destination->rollback($destination_key);
|
||||
$this->getEventDispatcher()
|
||||
->dispatch(new MigrateRowDeleteEvent($this->migration, $destination_key), MigrateEvents::POST_ROW_DELETE);
|
||||
}
|
||||
// We're now done with this row, so remove it from the map.
|
||||
$id_map->deleteDestination($destination_key);
|
||||
}
|
||||
else {
|
||||
// If there is no destination key the import probably failed and we can
|
||||
// remove the row without further action.
|
||||
$source_key = $id_map->currentSource();
|
||||
$id_map->delete($source_key);
|
||||
}
|
||||
$id_map->next();
|
||||
|
||||
// Check for memory exhaustion.
|
||||
if (($return = $this->checkStatus()) != MigrationInterface::RESULT_COMPLETED) {
|
||||
break;
|
||||
}
|
||||
|
||||
// If anyone has requested we stop, return the requested result.
|
||||
if ($this->migration->getStatus() == MigrationInterface::STATUS_STOPPING) {
|
||||
$return = $this->migration->getInterruptionResult();
|
||||
$this->migration->clearInterruptionResult();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Notify modules that rollback attempt was complete.
|
||||
$this->getEventDispatcher()->dispatch(new MigrateRollbackEvent($this->migration), MigrateEvents::POST_ROLLBACK);
|
||||
$this->migration->setStatus(MigrationInterface::STATUS_IDLE);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID map from the current migration.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrateIdMapInterface
|
||||
* The ID map.
|
||||
*/
|
||||
protected function getIdMap() {
|
||||
return $this->migration->getIdMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processRow(Row $row, ?array $process = NULL, $value = NULL) {
|
||||
foreach ($this->migration->getProcessPlugins($process) as $destination => $plugins) {
|
||||
$this->processPipeline($row, $destination, $plugins, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a process pipeline.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The $row to be processed.
|
||||
* @param string $destination
|
||||
* The destination property name.
|
||||
* @param array $plugins
|
||||
* The process pipeline plugins.
|
||||
* @param mixed $value
|
||||
* (optional) Initial value of the pipeline for the destination.
|
||||
*
|
||||
* @see \Drupal\migrate\MigrateExecutableInterface::processRow
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
*/
|
||||
protected function processPipeline(Row $row, string $destination, array $plugins, $value) {
|
||||
$multiple = FALSE;
|
||||
/** @var \Drupal\migrate\Plugin\MigrateProcessInterface $plugin */
|
||||
foreach ($plugins as $plugin) {
|
||||
$definition = $plugin->getPluginDefinition();
|
||||
// Many plugins expect a scalar value but the current value of the
|
||||
// pipeline might be multiple scalars (this is set by the previous plugin)
|
||||
// and in this case the current value needs to be iterated and each scalar
|
||||
// separately transformed.
|
||||
if ($multiple && !$definition['handle_multiples']) {
|
||||
$new_value = [];
|
||||
if (!is_array($value)) {
|
||||
throw new MigrateException(sprintf('Pipeline failed at %s plugin for destination %s: %s received instead of an array,', $plugin->getPluginId(), $destination, $value));
|
||||
}
|
||||
$break = FALSE;
|
||||
foreach ($value as $scalar_value) {
|
||||
$plugin->reset();
|
||||
try {
|
||||
$new_value[] = $plugin->transform($scalar_value, $this, $row, $destination);
|
||||
}
|
||||
catch (MigrateSkipProcessException $e) {
|
||||
$new_value[] = NULL;
|
||||
$break = TRUE;
|
||||
}
|
||||
catch (MigrateException $e) {
|
||||
// Prepend the process plugin id to the message.
|
||||
$message = sprintf("%s: %s", $plugin->getPluginId(), $e->getMessage());
|
||||
throw new MigrateException($message);
|
||||
}
|
||||
if ($plugin->isPipelineStopped()) {
|
||||
$break = TRUE;
|
||||
}
|
||||
}
|
||||
$value = $new_value;
|
||||
if ($break) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$plugin->reset();
|
||||
try {
|
||||
$value = $plugin->transform($value, $this, $row, $destination);
|
||||
}
|
||||
catch (MigrateSkipProcessException) {
|
||||
$value = NULL;
|
||||
break;
|
||||
}
|
||||
catch (MigrateException $e) {
|
||||
// Prepend the process plugin id to the message.
|
||||
$message = sprintf("%s: %s", $plugin->getPluginId(), $e->getMessage());
|
||||
throw new MigrateException($message);
|
||||
}
|
||||
if ($plugin->isPipelineStopped()) {
|
||||
break;
|
||||
}
|
||||
$multiple = $plugin->multiple();
|
||||
}
|
||||
}
|
||||
// Ensure all values, including nulls, are migrated.
|
||||
if ($plugins) {
|
||||
if (isset($value)) {
|
||||
$row->setDestinationProperty($destination, $value);
|
||||
}
|
||||
else {
|
||||
$row->setEmptyDestinationProperty($destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the key array for the current source record.
|
||||
*
|
||||
* @return array
|
||||
* The current source IDs.
|
||||
*/
|
||||
protected function currentSourceIds() {
|
||||
return $this->getSource()->getCurrentIds();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function saveMessage($message, $level = MigrationInterface::MESSAGE_ERROR) {
|
||||
$this->getIdMap()->saveMessage($this->sourceIdValues, $message, $level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an Exception object and both saves and displays it.
|
||||
*
|
||||
* Pulls in additional information on the location triggering the exception.
|
||||
*
|
||||
* @param \Exception $exception
|
||||
* Object representing the exception.
|
||||
* @param bool $save
|
||||
* (optional) Whether to save the message in the migration's mapping table.
|
||||
* Set to FALSE in contexts where this doesn't make sense.
|
||||
*/
|
||||
protected function handleException(\Exception $exception, $save = TRUE) {
|
||||
$result = Error::decodeException($exception);
|
||||
$message = $result['@message'] . ' (' . $result['%file'] . ':' . $result['%line'] . ')';
|
||||
if ($save) {
|
||||
$this->saveMessage($message);
|
||||
}
|
||||
$this->message->display($message, 'error');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for exceptional conditions, and display feedback.
|
||||
*/
|
||||
protected function checkStatus() {
|
||||
if ($this->memoryExceeded()) {
|
||||
return MigrationInterface::RESULT_INCOMPLETE;
|
||||
}
|
||||
return MigrationInterface::RESULT_COMPLETED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether we've exceeded the desired memory threshold.
|
||||
*
|
||||
* If so, output a message.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the threshold is exceeded, otherwise FALSE.
|
||||
*/
|
||||
protected function memoryExceeded() {
|
||||
$usage = $this->getMemoryUsage();
|
||||
$pct_memory = $usage / $this->memoryLimit;
|
||||
if (!$threshold = $this->memoryThreshold) {
|
||||
return FALSE;
|
||||
}
|
||||
if ($pct_memory > $threshold) {
|
||||
$this->message->display(
|
||||
$this->t(
|
||||
'Memory usage is @usage (@pct% of limit @limit), reclaiming memory.',
|
||||
[
|
||||
'@pct' => round($pct_memory * 100),
|
||||
'@usage' => ByteSizeMarkup::create($usage, NULL, $this->stringTranslation),
|
||||
'@limit' => ByteSizeMarkup::create($this->memoryLimit, NULL, $this->stringTranslation),
|
||||
]
|
||||
),
|
||||
'warning'
|
||||
);
|
||||
$usage = $this->attemptMemoryReclaim();
|
||||
$pct_memory = $usage / $this->memoryLimit;
|
||||
// Use a lower threshold - we don't want to be in a situation where we
|
||||
// keep coming back here and trimming a tiny amount
|
||||
if ($pct_memory > (0.90 * $threshold)) {
|
||||
$this->message->display(
|
||||
$this->t(
|
||||
'Memory usage is now @usage (@pct% of limit @limit), not enough reclaimed, starting new batch',
|
||||
[
|
||||
'@pct' => round($pct_memory * 100),
|
||||
'@usage' => ByteSizeMarkup::create($usage, NULL, $this->stringTranslation),
|
||||
'@limit' => ByteSizeMarkup::create($this->memoryLimit, NULL, $this->stringTranslation),
|
||||
]
|
||||
),
|
||||
'warning'
|
||||
);
|
||||
return TRUE;
|
||||
}
|
||||
else {
|
||||
$this->message->display(
|
||||
$this->t(
|
||||
'Memory usage is now @usage (@pct% of limit @limit), reclaimed enough, continuing',
|
||||
[
|
||||
'@pct' => round($pct_memory * 100),
|
||||
'@usage' => ByteSizeMarkup::create($usage, NULL, $this->stringTranslation),
|
||||
'@limit' => ByteSizeMarkup::create($this->memoryLimit, NULL, $this->stringTranslation),
|
||||
]
|
||||
),
|
||||
'warning');
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the memory usage so far.
|
||||
*
|
||||
* @return int
|
||||
* The memory usage.
|
||||
*/
|
||||
protected function getMemoryUsage() {
|
||||
return memory_get_usage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to reclaim memory.
|
||||
*
|
||||
* @return int
|
||||
* The memory usage after reclaim.
|
||||
*/
|
||||
protected function attemptMemoryReclaim() {
|
||||
// First, try resetting Drupal's static storage - this frequently releases
|
||||
// plenty of memory to continue.
|
||||
drupal_static_reset();
|
||||
|
||||
// @todo Explore resetting the container.
|
||||
|
||||
// Run garbage collector to further reduce memory.
|
||||
gc_collect_cycles();
|
||||
|
||||
return memory_get_usage();
|
||||
}
|
||||
|
||||
}
|
||||
59
web/core/modules/migrate/src/MigrateExecutableInterface.php
Normal file
59
web/core/modules/migrate/src/MigrateExecutableInterface.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
|
||||
/**
|
||||
* Interface for the migration executable.
|
||||
*/
|
||||
interface MigrateExecutableInterface {
|
||||
|
||||
/**
|
||||
* Performs an import operation - migrate items from source to destination.
|
||||
*
|
||||
* @return int
|
||||
* Returns a value indicating the status of the import operation.
|
||||
* The possible values are the 'RESULT_' constants defined
|
||||
* in MigrationInterface.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrationInterface
|
||||
*/
|
||||
public function import();
|
||||
|
||||
/**
|
||||
* Performs a rollback operation - remove previously-imported items.
|
||||
*/
|
||||
public function rollback();
|
||||
|
||||
/**
|
||||
* Processes a row.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The $row to be processed.
|
||||
* @param array $process
|
||||
* (optional) A process pipeline configuration. If not set, the top level
|
||||
* process configuration in the migration entity is used.
|
||||
* @param mixed $value
|
||||
* (optional) Initial value of the pipeline for the first destination.
|
||||
* Usually setting this is not necessary as $process typically starts with
|
||||
* a 'get'. This is useful only when the $process contains a single
|
||||
* destination and needs to access a value outside of the source. See
|
||||
* \Drupal\migrate\Plugin\migrate\process\SubProcess::transformKey for an
|
||||
* example.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
*/
|
||||
public function processRow(Row $row, ?array $process = NULL, $value = NULL);
|
||||
|
||||
/**
|
||||
* Passes messages through to the map class.
|
||||
*
|
||||
* @param string $message
|
||||
* The message to record.
|
||||
* @param int $level
|
||||
* (optional) Message severity (defaults to MESSAGE_ERROR).
|
||||
*/
|
||||
public function saveMessage($message, $level = MigrationInterface::MESSAGE_ERROR);
|
||||
|
||||
}
|
||||
81
web/core/modules/migrate/src/MigrateLookup.php
Normal file
81
web/core/modules/migrate/src/MigrateLookup.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
use Drupal\Component\Plugin\Exception\PluginException;
|
||||
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
|
||||
|
||||
/**
|
||||
* Provides a migration lookup service.
|
||||
*/
|
||||
class MigrateLookup implements MigrateLookupInterface {
|
||||
|
||||
/**
|
||||
* The migration plugin manager.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
|
||||
*/
|
||||
protected $migrationPluginManager;
|
||||
|
||||
/**
|
||||
* Constructs a MigrateLookup object.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager
|
||||
* The migration plugin manager.
|
||||
*/
|
||||
public function __construct(MigrationPluginManagerInterface $migration_plugin_manager) {
|
||||
$this->migrationPluginManager = $migration_plugin_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lookup($migration_id, array $source_id_values) {
|
||||
$results = [];
|
||||
$migrations = $this->migrationPluginManager->createInstances($migration_id);
|
||||
if (!$migrations) {
|
||||
if (is_array($migration_id)) {
|
||||
if (count($migration_id) != 1) {
|
||||
throw new PluginException("Plugin IDs '" . implode("', '", $migration_id) . "' were not found.");
|
||||
}
|
||||
$migration_id = reset($migration_id);
|
||||
}
|
||||
throw new PluginNotFoundException($migration_id);
|
||||
}
|
||||
foreach ($migrations as $migration) {
|
||||
if ($result = $this->doLookup($migration, $source_id_values)) {
|
||||
$results = array_merge($results, $result);
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a lookup.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration upon which to perform the lookup.
|
||||
* @param array $source_id_values
|
||||
* The source ID values to look up.
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays of destination identifier values.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
* Thrown when $source_id_values contains unknown keys, or the wrong number
|
||||
* of keys.
|
||||
*/
|
||||
protected function doLookup(MigrationInterface $migration, array $source_id_values) {
|
||||
$destination_keys = array_keys($migration->getDestinationPlugin()->getIds());
|
||||
$indexed_ids = $migration->getIdMap()
|
||||
->lookupDestinationIds($source_id_values);
|
||||
$keyed_ids = [];
|
||||
foreach ($indexed_ids as $id) {
|
||||
$keyed_ids[] = array_combine($destination_keys, $id);
|
||||
}
|
||||
return $keyed_ids;
|
||||
}
|
||||
|
||||
}
|
||||
33
web/core/modules/migrate/src/MigrateLookupInterface.php
Normal file
33
web/core/modules/migrate/src/MigrateLookupInterface.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
/**
|
||||
* Provides an interface for the migration lookup service.
|
||||
*
|
||||
* @package Drupal\migrate
|
||||
*/
|
||||
interface MigrateLookupInterface {
|
||||
|
||||
/**
|
||||
* Retrieves destination ids from a migration lookup.
|
||||
*
|
||||
* @param string|string[] $migration_ids
|
||||
* An array of migration plugin IDs to look up, or a single ID as a string.
|
||||
* @param array $source_id_values
|
||||
* An array of source id values.
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays of destination ids, or an empty array if none were
|
||||
* found.
|
||||
*
|
||||
* @throws \Drupal\Component\Plugin\Exception\PluginException
|
||||
* Thrown by the migration plugin manager on error, or if the migration(s)
|
||||
* cannot be found.
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
* Thrown when $source_id_values contains unknown keys, or is the wrong
|
||||
* length.
|
||||
*/
|
||||
public function lookup($migration_ids, array $source_id_values);
|
||||
|
||||
}
|
||||
30
web/core/modules/migrate/src/MigrateMessage.php
Normal file
30
web/core/modules/migrate/src/MigrateMessage.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
use Drupal\Core\Logger\RfcLogLevel;
|
||||
|
||||
/**
|
||||
* Defines a migrate message class.
|
||||
*/
|
||||
class MigrateMessage implements MigrateMessageInterface {
|
||||
|
||||
/**
|
||||
* The map between migrate status and watchdog severity.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $map = [
|
||||
'status' => RfcLogLevel::INFO,
|
||||
'error' => RfcLogLevel::ERROR,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function display($message, $type = 'status') {
|
||||
$type = $this->map[$type] ?? RfcLogLevel::NOTICE;
|
||||
\Drupal::logger('migrate')->log($type, $message);
|
||||
}
|
||||
|
||||
}
|
||||
20
web/core/modules/migrate/src/MigrateMessageInterface.php
Normal file
20
web/core/modules/migrate/src/MigrateMessageInterface.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
/**
|
||||
* Interface for migration messages.
|
||||
*/
|
||||
interface MigrateMessageInterface {
|
||||
|
||||
/**
|
||||
* Displays a migrate message.
|
||||
*
|
||||
* @param string $message
|
||||
* The message to display.
|
||||
* @param string $type
|
||||
* The type of message, for example: status or warning.
|
||||
*/
|
||||
public function display($message, $type = 'status');
|
||||
|
||||
}
|
||||
20
web/core/modules/migrate/src/MigrateSkipProcessException.php
Normal file
20
web/core/modules/migrate/src/MigrateSkipProcessException.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
/**
|
||||
* This exception is thrown when the rest of the process should be skipped.
|
||||
*
|
||||
* @deprecated in drupal:10.3.0 and is removed from drupal:12.0.0. Return FALSE from a process
|
||||
* plugin's isPipelineStopped() method to stop further processing on a
|
||||
* pipeline.
|
||||
* @see https://www.drupal.org/node/3414511
|
||||
*/
|
||||
class MigrateSkipProcessException extends \Exception {
|
||||
|
||||
public function __construct(string $message = "", int $code = 0, ?\Throwable $previous = NULL) {
|
||||
trigger_error(__CLASS__ . " is deprecated in drupal:10.3.0 and is removed from drupal:12.0.0. Return TRUE from a process plugin's isPipelineStopped() method to halt further processing on a pipeline. See https://www.drupal.org/node/3414511", E_USER_DEPRECATED);
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
}
|
||||
45
web/core/modules/migrate/src/MigrateSkipRowException.php
Normal file
45
web/core/modules/migrate/src/MigrateSkipRowException.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
/**
|
||||
* This exception is thrown when a row should be skipped.
|
||||
*
|
||||
* This exception should be used in Migrate process plugins. Throwing it in a
|
||||
* source plugin may cause unexpected results in the count of rows processed.
|
||||
* And throwing it in a destination plugin causes an error.
|
||||
*/
|
||||
class MigrateSkipRowException extends \Exception {
|
||||
|
||||
/**
|
||||
* Whether to record the skip in the map table, or skip silently.
|
||||
*
|
||||
* @var bool
|
||||
* TRUE to record as STATUS_IGNORED in the map, FALSE to skip silently.
|
||||
*/
|
||||
protected $saveToMap;
|
||||
|
||||
/**
|
||||
* Constructs a MigrateSkipRowException object.
|
||||
*
|
||||
* @param string $message
|
||||
* The message for the exception.
|
||||
* @param bool $save_to_map
|
||||
* TRUE to record as STATUS_IGNORED in the map, FALSE to skip silently.
|
||||
*/
|
||||
public function __construct($message = '', $save_to_map = TRUE) {
|
||||
parent::__construct($message);
|
||||
$this->saveToMap = $save_to_map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the thrower wants to record this skip in the map table.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE to record as STATUS_IGNORED in the map, FALSE to skip silently.
|
||||
*/
|
||||
public function getSaveToMap() {
|
||||
return $this->saveToMap;
|
||||
}
|
||||
|
||||
}
|
||||
130
web/core/modules/migrate/src/MigrateStub.php
Normal file
130
web/core/modules/migrate/src/MigrateStub.php
Normal file
@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
|
||||
|
||||
/**
|
||||
* Provides the migrate stubbing service.
|
||||
*/
|
||||
class MigrateStub implements MigrateStubInterface {
|
||||
|
||||
/**
|
||||
* The migration plugin manager.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
|
||||
*/
|
||||
protected $migrationPluginManager;
|
||||
|
||||
/**
|
||||
* Constructs a MigrationStub object.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager
|
||||
* The migration plugin manager.
|
||||
*/
|
||||
public function __construct(MigrationPluginManagerInterface $migration_plugin_manager) {
|
||||
$this->migrationPluginManager = $migration_plugin_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a stub.
|
||||
*
|
||||
* @param string $migration_id
|
||||
* The migration to stub.
|
||||
* @param array $source_ids
|
||||
* An array of source ids.
|
||||
* @param array $default_values
|
||||
* (optional) An array of default values to add to the stub.
|
||||
* @param bool $key_by_destination_ids
|
||||
* (optional) NULL or TRUE to force indexing of the return array by
|
||||
* destination id keys (default), or FALSE to return the raw return value of
|
||||
* the destination plugin's ::import() method. The return value from
|
||||
* MigrateDestinationInterface::import() is very poorly defined as "The
|
||||
* entity ID or an indication of success". In practice, the mapping systems
|
||||
* expect and all destination plugins return an array of destination
|
||||
* identifiers. Unfortunately these arrays are inconsistently keyed. The
|
||||
* core destination plugins return a numerically indexed array of
|
||||
* destination identifiers, but several contrib destinations return an array
|
||||
* of identifiers indexed by the destination keys. This method will
|
||||
* generally index all return arrays for consistency and to provide as much
|
||||
* information as possible, but this parameter is added for backwards
|
||||
* compatibility to allow accessing the original array.
|
||||
*
|
||||
* @return array|false
|
||||
* An array of destination ids for the new stub, keyed by destination id
|
||||
* key, or false if the stub failed.
|
||||
*
|
||||
* @throws \Drupal\Component\Plugin\Exception\PluginException
|
||||
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
*/
|
||||
public function createStub($migration_id, array $source_ids, array $default_values = [], $key_by_destination_ids = NULL) {
|
||||
$migrations = $this->migrationPluginManager->createInstances([$migration_id]);
|
||||
if (!$migrations) {
|
||||
throw new PluginNotFoundException($migration_id);
|
||||
}
|
||||
if (count($migrations) !== 1) {
|
||||
throw new \LogicException(sprintf('Cannot stub derivable migration "%s". You must specify the id of a specific derivative to stub.', $migration_id));
|
||||
}
|
||||
$migration = reset($migrations);
|
||||
$source_id_keys = array_keys($migration->getSourcePlugin()->getIds());
|
||||
if (count($source_id_keys) !== count($source_ids)) {
|
||||
throw new \InvalidArgumentException('Expected and provided source id counts do not match.');
|
||||
}
|
||||
if (array_keys($source_ids) === range(0, count($source_ids) - 1)) {
|
||||
$source_ids = array_combine($source_id_keys, $source_ids);
|
||||
}
|
||||
$stub = $this->doCreateStub($migration, $source_ids, $default_values);
|
||||
|
||||
// If the return from ::import is numerically indexed, and we aren't
|
||||
// requesting the raw return value, index it associatively using the
|
||||
// destination id keys.
|
||||
if (($key_by_destination_ids !== FALSE) && array_keys($stub) === range(0, count($stub) - 1)) {
|
||||
$stub = array_combine(array_keys($migration->getDestinationPlugin()->getIds()), $stub);
|
||||
}
|
||||
return $stub;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a stub.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration to use to create the stub.
|
||||
* @param array $source_ids
|
||||
* The source ids to map to the stub.
|
||||
* @param array $default_values
|
||||
* (optional) An array of values to include in the stub.
|
||||
*
|
||||
* @return array|bool
|
||||
* An array of destination ids for the stub.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
*/
|
||||
protected function doCreateStub(MigrationInterface $migration, array $source_ids, array $default_values = []) {
|
||||
$destination = $migration->getDestinationPlugin(TRUE);
|
||||
$process = $migration->getProcess();
|
||||
$id_map = $migration->getIdMap();
|
||||
$migrate_executable = new MigrateExecutable($migration);
|
||||
$row = new Row($source_ids + $migration->getSourceConfiguration(), $migration->getSourcePlugin()->getIds(), TRUE);
|
||||
$migrate_executable->processRow($row, $process);
|
||||
foreach ($default_values as $key => $value) {
|
||||
$row->setDestinationProperty($key, $value);
|
||||
}
|
||||
$destination_ids = [];
|
||||
try {
|
||||
$destination_ids = $destination->import($row);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$id_map->saveMessage($row->getSourceIdValues(), $e->getMessage());
|
||||
}
|
||||
if ($destination_ids) {
|
||||
$id_map->saveIdMapping($row, $destination_ids, MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
|
||||
return $destination_ids;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
||||
26
web/core/modules/migrate/src/MigrateStubInterface.php
Normal file
26
web/core/modules/migrate/src/MigrateStubInterface.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
/**
|
||||
* Provides an interface for the migrate stub service.
|
||||
*/
|
||||
interface MigrateStubInterface {
|
||||
|
||||
/**
|
||||
* Creates a stub.
|
||||
*
|
||||
* @param string $migration_id
|
||||
* The migration to stub.
|
||||
* @param array $source_ids
|
||||
* An array of source ids.
|
||||
* @param array $default_values
|
||||
* (optional) An array of default values to add to the stub.
|
||||
*
|
||||
* @return array|false
|
||||
* An array of destination ids for the new stub, keyed by destination id
|
||||
* key, or false if the stub failed.
|
||||
*/
|
||||
public function createStub($migration_id, array $source_ids, array $default_values = []);
|
||||
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\Derivative;
|
||||
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* The migrate entity deriver.
|
||||
*/
|
||||
class MigrateEntity implements ContainerDeriverInterface {
|
||||
|
||||
/**
|
||||
* List of derivative definitions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $derivatives = [];
|
||||
|
||||
/**
|
||||
* The entity definitions.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeInterface[]
|
||||
*/
|
||||
protected $entityDefinitions;
|
||||
|
||||
/**
|
||||
* Constructs a MigrateEntity object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface[] $entity_definitions
|
||||
* A list of entity definition objects.
|
||||
*/
|
||||
public function __construct(array $entity_definitions) {
|
||||
$this->entityDefinitions = $entity_definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager')->getDefinitions()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
|
||||
if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
|
||||
return $this->derivatives[$derivative_id];
|
||||
}
|
||||
$this->getDerivativeDefinitions($base_plugin_definition);
|
||||
return $this->derivatives[$derivative_id];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
foreach ($this->entityDefinitions as $entity_type => $entity_info) {
|
||||
$class = is_subclass_of($entity_info->getClass(), 'Drupal\Core\Config\Entity\ConfigEntityInterface') ?
|
||||
'Drupal\migrate\Plugin\migrate\destination\EntityConfigBase' :
|
||||
'Drupal\migrate\Plugin\migrate\destination\EntityContentBase';
|
||||
$this->derivatives[$entity_type] = [
|
||||
'id' => "entity:$entity_type",
|
||||
'class' => $class,
|
||||
'requirements_met' => 1,
|
||||
'provider' => $entity_info->getProvider(),
|
||||
];
|
||||
}
|
||||
return $this->derivatives;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\Derivative;
|
||||
|
||||
use Drupal\migrate\Plugin\migrate\destination\EntityContentComplete;
|
||||
|
||||
/**
|
||||
* Deriver for entity_complete:ENTITY_TYPE entity migrations.
|
||||
*/
|
||||
class MigrateEntityComplete extends MigrateEntity {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
foreach ($this->entityDefinitions as $entity_type => $entity_info) {
|
||||
$this->derivatives[$entity_type] = [
|
||||
'id' => "entity_complete:$entity_type",
|
||||
'class' => EntityContentComplete::class,
|
||||
'requirements_met' => 1,
|
||||
'provider' => $entity_info->getProvider(),
|
||||
];
|
||||
}
|
||||
return $this->derivatives;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\Derivative;
|
||||
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* The migrate entity revision deriver.
|
||||
*/
|
||||
class MigrateEntityRevision implements ContainerDeriverInterface {
|
||||
|
||||
/**
|
||||
* List of derivative definitions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $derivatives = [];
|
||||
|
||||
/**
|
||||
* The entity definitions.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeInterface[]
|
||||
*/
|
||||
protected $entityDefinitions;
|
||||
|
||||
/**
|
||||
* Constructs a MigrateEntity object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface[] $entity_definitions
|
||||
* A list of entity definition objects.
|
||||
*/
|
||||
public function __construct(array $entity_definitions) {
|
||||
$this->entityDefinitions = $entity_definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager')->getDefinitions()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
|
||||
if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
|
||||
return $this->derivatives[$derivative_id];
|
||||
}
|
||||
$this->getDerivativeDefinitions($base_plugin_definition);
|
||||
return $this->derivatives[$derivative_id];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
foreach ($this->entityDefinitions as $entity_type => $entity_info) {
|
||||
if ($entity_info->getKey('revision')) {
|
||||
$this->derivatives[$entity_type] = [
|
||||
'id' => "entity_revision:$entity_type",
|
||||
'class' => 'Drupal\migrate\Plugin\migrate\destination\EntityRevision',
|
||||
'requirements_met' => 1,
|
||||
'provider' => $entity_info->getProvider(),
|
||||
];
|
||||
}
|
||||
}
|
||||
return $this->derivatives;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\Discovery;
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationRegistry;
|
||||
use Drupal\Component\Annotation\Doctrine\StaticReflectionParser as BaseStaticReflectionParser;
|
||||
use Drupal\Component\Annotation\Reflection\MockFileFinder;
|
||||
use Drupal\Component\ClassFinder\ClassFinder;
|
||||
use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
|
||||
|
||||
/**
|
||||
* Determines providers based on a class's and its parent's namespaces.
|
||||
*
|
||||
* @internal
|
||||
* This is a temporary solution to the fact that migration source plugins have
|
||||
* more than one provider. This functionality will be moved to core in
|
||||
* https://www.drupal.org/node/2786355.
|
||||
*/
|
||||
class AnnotatedClassDiscoveryAutomatedProviders extends AnnotatedClassDiscovery {
|
||||
|
||||
use AnnotatedDiscoveryAutomatedProvidersTrait;
|
||||
|
||||
/**
|
||||
* Constructs an AnnotatedClassDiscoveryAutomatedProviders object.
|
||||
*
|
||||
* @param string $subdir
|
||||
* Either the plugin's subdirectory, for example 'Plugin/views/filter', or
|
||||
* empty string if plugins are located at the top level of the namespace.
|
||||
* @param \Traversable $root_namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* If $subdir is not an empty string, it will be appended to each namespace.
|
||||
* @param string $plugin_definition_annotation_name
|
||||
* The name of the annotation that contains the plugin definition.
|
||||
* Defaults to 'Drupal\Component\Annotation\Plugin'.
|
||||
* @param string[] $annotation_namespaces
|
||||
* Additional namespaces to scan for annotation definitions.
|
||||
*/
|
||||
public function __construct($subdir, \Traversable $root_namespaces, $plugin_definition_annotation_name = 'Drupal\Component\Annotation\Plugin', array $annotation_namespaces = []) {
|
||||
parent::__construct($subdir, $root_namespaces, $plugin_definition_annotation_name, $annotation_namespaces);
|
||||
$this->finder = new ClassFinder();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinitions() {
|
||||
$definitions = [];
|
||||
|
||||
$reader = $this->getAnnotationReader();
|
||||
|
||||
// Clear the annotation loaders of any previous annotation classes.
|
||||
AnnotationRegistry::reset();
|
||||
|
||||
// Search for classes within all PSR-4 namespace locations.
|
||||
foreach ($this->getPluginNamespaces() as $namespace => $dirs) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($dir)) {
|
||||
$iterator = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS)
|
||||
);
|
||||
foreach ($iterator as $fileinfo) {
|
||||
if ($fileinfo->getExtension() == 'php') {
|
||||
if ($cached = $this->fileCache->get($fileinfo->getPathName())) {
|
||||
if (isset($cached['id'])) {
|
||||
// Explicitly unserialize this to create a new object
|
||||
// instance.
|
||||
$definitions[$cached['id']] = unserialize($cached['content']);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$sub_path = $iterator->getSubIterator()->getSubPath();
|
||||
$sub_path = $sub_path ? str_replace(DIRECTORY_SEPARATOR, '\\', $sub_path) . '\\' : '';
|
||||
$class = $namespace . '\\' . $sub_path . $fileinfo->getBasename('.php');
|
||||
|
||||
// The filename is already known, so there is no need to find the
|
||||
// file. However, StaticReflectionParser needs a finder, so use a
|
||||
// mock version.
|
||||
$finder = MockFileFinder::create($fileinfo->getPathName());
|
||||
$parser = new BaseStaticReflectionParser($class, $finder, FALSE);
|
||||
|
||||
/** @var \Drupal\Component\Annotation\AnnotationInterface $annotation */
|
||||
if ($annotation = $reader->getClassAnnotation($parser->getReflectionClass(), $this->pluginDefinitionAnnotationName)) {
|
||||
$this->prepareAnnotationDefinition($annotation, $class, $parser);
|
||||
|
||||
$id = $annotation->getId();
|
||||
$content = $annotation->get();
|
||||
$definitions[$id] = $content;
|
||||
// Explicitly serialize this to create a new object instance.
|
||||
$this->fileCache->set($fileinfo->getPathName(), ['id' => $id, 'content' => serialize($content)]);
|
||||
}
|
||||
else {
|
||||
// Store a NULL object, so the file is not parsed again.
|
||||
$this->fileCache->set($fileinfo->getPathName(), [NULL]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't let annotation loaders pile up.
|
||||
AnnotationRegistry::reset();
|
||||
|
||||
return $definitions;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\Discovery;
|
||||
|
||||
use Drupal\Component\Annotation\AnnotationInterface;
|
||||
use Drupal\Component\Annotation\Doctrine\StaticReflectionParser as BaseStaticReflectionParser;
|
||||
use Drupal\migrate\Annotation\MultipleProviderAnnotationInterface;
|
||||
|
||||
/**
|
||||
* Provides method for annotation discovery with multiple providers.
|
||||
*
|
||||
* @internal
|
||||
* This trait is a temporary solution until annotation discovery is removed.
|
||||
* @see https://www.drupal.org/project/drupal/issues/3521472
|
||||
*/
|
||||
trait AnnotatedDiscoveryAutomatedProvidersTrait {
|
||||
|
||||
/**
|
||||
* A utility object that can use active autoloaders to find files for classes.
|
||||
*
|
||||
* @var \Drupal\Component\ClassFinder\ClassFinderInterface
|
||||
*/
|
||||
protected $finder;
|
||||
|
||||
/**
|
||||
* Prepares the annotation definition.
|
||||
*
|
||||
* This is modified from the prepareAnnotationDefinition method from annotated
|
||||
* class discovery to account for multiple providers.
|
||||
*
|
||||
* @param \Drupal\Component\Annotation\AnnotationInterface $annotation
|
||||
* The annotation derived from the plugin.
|
||||
* @param class-string $class
|
||||
* The class used for the plugin.
|
||||
* @param \Drupal\Component\Annotation\Doctrine\StaticReflectionParser|null $parser
|
||||
* Static reflection parser.
|
||||
*
|
||||
* @see \Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery::prepareAnnotationDefinition()
|
||||
* @see \Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery::prepareAnnotationDefinition()
|
||||
*/
|
||||
protected function prepareAnnotationDefinition(AnnotationInterface $annotation, $class, ?BaseStaticReflectionParser $parser = NULL): void {
|
||||
if (!($annotation instanceof MultipleProviderAnnotationInterface)) {
|
||||
throw new \LogicException('AnnotatedClassDiscoveryAutomatedProviders annotations must implement ' . MultipleProviderAnnotationInterface::class);
|
||||
}
|
||||
if (!$parser) {
|
||||
throw new \LogicException('Parser argument must be passed for automated providers discovery.');
|
||||
}
|
||||
if (!method_exists($this, 'getProviderFromNamespace')) {
|
||||
throw new \LogicException('Classes using \Drupal\migrate\Plugin\Discovery\AnnotatedDiscoveryAutomatedProvidersTrait must have getProviderFromNamespace() method.');
|
||||
}
|
||||
// @see \Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery::prepareAnnotationDefinition()
|
||||
$annotation->setClass($class);
|
||||
$providers = $annotation->getProviders();
|
||||
// Loop through all the parent classes and add their providers (which we
|
||||
// infer by parsing their namespaces) to the $providers array.
|
||||
do {
|
||||
$providers[] = $this->getProviderFromNamespace($parser->getNamespaceName());
|
||||
} while ($parser = StaticReflectionParser::getParentParser($parser, $this->finder));
|
||||
$providers = array_diff(array_unique(array_filter($providers)), ['component']);
|
||||
$annotation->setProviders($providers);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\migrate\Plugin\Discovery;
|
||||
|
||||
use Drupal\Component\Plugin\Attribute\AttributeInterface;
|
||||
use Drupal\Core\Plugin\Discovery\AttributeClassDiscovery;
|
||||
use Drupal\migrate\Attribute\MultipleProviderAttributeInterface;
|
||||
|
||||
/**
|
||||
* Determines providers based on the namespaces of a class and its ancestors.
|
||||
*
|
||||
* @internal
|
||||
* This is a temporary solution to the fact that migration source plugins have
|
||||
* more than one provider. This functionality will be moved to core in
|
||||
* https://www.drupal.org/node/2786355.
|
||||
*/
|
||||
class AttributeClassDiscoveryAutomatedProviders extends AttributeClassDiscovery {
|
||||
|
||||
/**
|
||||
* Prepares the attribute definition.
|
||||
*
|
||||
* @param \Drupal\Component\Plugin\Attribute\AttributeInterface $attribute
|
||||
* The attribute derived from the plugin.
|
||||
* @param string $class
|
||||
* The class used for the plugin.
|
||||
*
|
||||
* @throws \LogicException
|
||||
* When the attribute class does not allow for multiple providers.
|
||||
*/
|
||||
protected function prepareAttributeDefinition(AttributeInterface $attribute, string $class): void {
|
||||
if (!($attribute instanceof MultipleProviderAttributeInterface)) {
|
||||
throw new \LogicException('AttributeClassDiscoveryAutomatedProviders must implement ' . MultipleProviderAttributeInterface::class);
|
||||
}
|
||||
// @see Drupal\Component\Plugin\Discovery\AttributeClassDiscovery::prepareAttributeDefinition()
|
||||
$attribute->setClass($class);
|
||||
|
||||
// Loop through all the parent classes and add their providers (which we
|
||||
// infer by parsing their namespaces) to the $providers array.
|
||||
$providers = $attribute->getProviders();
|
||||
do {
|
||||
$providers[] = $this->getProviderFromNamespace($class);
|
||||
} while (($class = get_parent_class($class)) !== FALSE);
|
||||
|
||||
$providers = array_diff(array_unique(array_filter($providers)), ['component']);
|
||||
$attribute->setProviders($providers);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\migrate\Plugin\Discovery;
|
||||
|
||||
use Drupal\Component\Annotation\Doctrine\StaticReflectionParser as BaseStaticReflectionParser;
|
||||
use Drupal\Component\Annotation\Reflection\MockFileFinder;
|
||||
use Drupal\Component\ClassFinder\ClassFinder;
|
||||
use Drupal\Component\Plugin\Attribute\AttributeInterface;
|
||||
use Drupal\Core\Plugin\Discovery\AttributeDiscoveryWithAnnotations;
|
||||
|
||||
/**
|
||||
* Enables both attribute and annotation discovery for plugin definitions.
|
||||
*
|
||||
* @internal
|
||||
* This is a temporary solution to the fact that migration source plugins have
|
||||
* more than one provider. This functionality will be moved to core in
|
||||
* https://www.drupal.org/node/2786355.
|
||||
*/
|
||||
class AttributeDiscoveryWithAnnotationsAutomatedProviders extends AttributeDiscoveryWithAnnotations {
|
||||
|
||||
use AnnotatedDiscoveryAutomatedProvidersTrait;
|
||||
|
||||
/**
|
||||
* Instance of attribute class discovery with automatic providers.
|
||||
*
|
||||
* Since there isn't multiple inheritance, instantiate the attribute only
|
||||
* discovery for code reuse.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\Discovery\AttributeClassDiscoveryAutomatedProviders
|
||||
*/
|
||||
private AttributeClassDiscoveryAutomatedProviders $attributeDiscovery;
|
||||
|
||||
public function __construct(
|
||||
string $subdir,
|
||||
\Traversable $rootNamespaces,
|
||||
string $pluginDefinitionAttributeName = 'Drupal\Component\Plugin\Attribute\Plugin',
|
||||
string $pluginDefinitionAnnotationName = 'Drupal\Component\Annotation\Plugin',
|
||||
array $additionalNamespaces = [],
|
||||
) {
|
||||
parent::__construct($subdir, $rootNamespaces, $pluginDefinitionAttributeName, $pluginDefinitionAnnotationName, $additionalNamespaces);
|
||||
$this->finder = new ClassFinder();
|
||||
$this->attributeDiscovery = new AttributeClassDiscoveryAutomatedProviders($subdir, $rootNamespaces, $pluginDefinitionAttributeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function prepareAttributeDefinition(AttributeInterface $attribute, string $class): void {
|
||||
$this->attributeDiscovery->prepareAttributeDefinition($attribute, $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function parseClass(string $class, \SplFileInfo $fileinfo): array {
|
||||
// Parse using attributes first.
|
||||
$definition = $this->attributeDiscovery->parseClass($class, $fileinfo);
|
||||
if (isset($definition['id'])) {
|
||||
return $definition;
|
||||
}
|
||||
|
||||
// The filename is already known, so there is no need to find the
|
||||
// file. However, StaticReflectionParser needs a finder, so use a
|
||||
// mock version.
|
||||
$finder = MockFileFinder::create($fileinfo->getPathName());
|
||||
// The parser is instantiated here with FALSE as the last parameter. This is
|
||||
// needed so that the parser includes the 'extends' declaration and extracts
|
||||
// providers from ancestor classes.
|
||||
$parser = new BaseStaticReflectionParser($class, $finder, FALSE);
|
||||
|
||||
$reflection_class = $parser->getReflectionClass();
|
||||
// @todo Handle deprecating definitions discovery via annotations in
|
||||
// https://www.drupal.org/project/drupal/issues/3522409.
|
||||
/** @var \Drupal\Component\Annotation\AnnotationInterface $annotation */
|
||||
if ($annotation = $this->getAnnotationReader()->getClassAnnotation($reflection_class, $this->pluginDefinitionAnnotationName)) {
|
||||
$this->prepareAnnotationDefinition($annotation, $class, $parser);
|
||||
return ['id' => $annotation->getId(), 'content' => $annotation->get()];
|
||||
}
|
||||
|
||||
return ['id' => NULL, 'content' => NULL];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\Discovery;
|
||||
|
||||
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
|
||||
use Drupal\Component\Plugin\Discovery\DiscoveryTrait;
|
||||
|
||||
/**
|
||||
* Remove plugin definitions with non-existing providers.
|
||||
*
|
||||
* @internal
|
||||
* This is a temporary solution to the fact that migration source plugins have
|
||||
* more than one provider. This functionality will be moved to core in
|
||||
* https://www.drupal.org/node/2786355.
|
||||
*/
|
||||
class ProviderFilterDecorator implements DiscoveryInterface {
|
||||
|
||||
use DiscoveryTrait;
|
||||
|
||||
/**
|
||||
* The Discovery object being decorated.
|
||||
*
|
||||
* @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface
|
||||
*/
|
||||
protected $decorated;
|
||||
|
||||
/**
|
||||
* A callable for testing if a provider exists.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
protected $providerExists;
|
||||
|
||||
/**
|
||||
* Constructs an InheritProviderDecorator object.
|
||||
*
|
||||
* @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $decorated
|
||||
* The object implementing DiscoveryInterface that is being decorated.
|
||||
* @param callable $provider_exists
|
||||
* A callable, gets passed a provider name, should return TRUE if the
|
||||
* provider exists and FALSE if not.
|
||||
*/
|
||||
public function __construct(DiscoveryInterface $decorated, callable $provider_exists) {
|
||||
$this->decorated = $decorated;
|
||||
$this->providerExists = $provider_exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes plugin definitions with non-existing providers.
|
||||
*
|
||||
* @param mixed[] $definitions
|
||||
* An array of plugin definitions (empty array if no definitions were
|
||||
* found). Keys are plugin IDs.
|
||||
* @param callable $provider_exists
|
||||
* A callable, gets passed a provider name, should return TRUE if the
|
||||
* provider exists and FALSE if not.
|
||||
*
|
||||
* @return array
|
||||
* An array of plugin definitions. If a definition is an array and has a
|
||||
* provider key that provider is guaranteed to exist.
|
||||
*/
|
||||
public static function filterDefinitions(array $definitions, callable $provider_exists) {
|
||||
// Besides what the caller accepts, we also accept core or component.
|
||||
$provider_exists = function ($provider) use ($provider_exists) {
|
||||
return in_array($provider, ['core', 'component']) || $provider_exists($provider);
|
||||
};
|
||||
return array_filter($definitions, function ($definition) use ($provider_exists) {
|
||||
// Plugin definitions can be objects (for example, Typed Data) those will
|
||||
// become empty array here and cause no problems.
|
||||
$definition = (array) $definition + [
|
||||
'provider' => [],
|
||||
'providers' => [],
|
||||
];
|
||||
// There can be one or many providers, handle them as multiple always.
|
||||
$providers = $definition['providers'] ?: (array) $definition['provider'];
|
||||
return count($providers) == count(array_filter($providers, $provider_exists));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinitions() {
|
||||
return static::filterDefinitions($this->decorated->getDefinitions(), $this->providerExists);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes through all unknown calls onto the decorated object.
|
||||
*
|
||||
* @param string $method
|
||||
* The method to call on the decorated object.
|
||||
* @param array $args
|
||||
* Call arguments.
|
||||
*
|
||||
* @return mixed
|
||||
* The return value from the method on the decorated object.
|
||||
*/
|
||||
public function __call($method, array $args) {
|
||||
return call_user_func_array([$this->decorated, $method], $args);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\Discovery;
|
||||
|
||||
use Drupal\Component\Annotation\Doctrine\StaticReflectionParser as BaseStaticReflectionParser;
|
||||
|
||||
/**
|
||||
* Allows getting the reflection parser for the parent class.
|
||||
*
|
||||
* @internal
|
||||
* This is a temporary solution to the fact that migration source plugins have
|
||||
* more than one provider. This functionality will be moved to core in
|
||||
* https://www.drupal.org/node/2786355.
|
||||
*/
|
||||
class StaticReflectionParser extends BaseStaticReflectionParser {
|
||||
|
||||
/**
|
||||
* If the current class extends another, get the parser for the latter.
|
||||
*
|
||||
* @param \Drupal\Component\Annotation\Doctrine\StaticReflectionParser $parser
|
||||
* The current static parser.
|
||||
* @param \Doctrine\Common\Reflection\ClassFinderInterface $finder
|
||||
* The class finder. Must implement
|
||||
* \Drupal\Component\ClassFinder\ClassFinderInterface, but can do so
|
||||
* implicitly (i.e., implements the interface's methods but not the actual
|
||||
* interface).
|
||||
*
|
||||
* @return static|null
|
||||
* The static parser for the parent if there's a parent class or NULL.
|
||||
*/
|
||||
public static function getParentParser(BaseStaticReflectionParser $parser, $finder) {
|
||||
// Ensure the class has been parsed before accessing the parentClassName
|
||||
// property.
|
||||
$parser->parse();
|
||||
if ($parser->parentClassName) {
|
||||
return new static($parser->parentClassName, $finder, $parser->classAnnotationOptimize);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\Exception;
|
||||
|
||||
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
|
||||
|
||||
/**
|
||||
* Defines a class for bad plugin definition exceptions.
|
||||
*/
|
||||
class BadPluginDefinitionException extends InvalidPluginDefinitionException {
|
||||
|
||||
/**
|
||||
* Constructs a BadPluginDefinitionException.
|
||||
*
|
||||
* @param string $plugin_id
|
||||
* The plugin ID of the mapper.
|
||||
* @param string $property
|
||||
* The name of the property that is missing from the plugin.
|
||||
* @param int $code
|
||||
* (optional) The exception code. Defaults to 0.
|
||||
* @param \Throwable|null $previous
|
||||
* The previous throwable used for exception chaining.
|
||||
*
|
||||
* @see \Exception
|
||||
*/
|
||||
public function __construct($plugin_id, $property, $code = 0, ?\Throwable $previous = NULL) {
|
||||
$message = sprintf('The %s plugin must define the %s property.', $plugin_id, $property);
|
||||
parent::__construct($plugin_id, $message, $code, $previous);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Defines an interface for Migration Destination classes.
|
||||
*
|
||||
* Destinations are responsible for persisting source data into the destination
|
||||
* Drupal.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\destination\DestinationBase
|
||||
* @see \Drupal\migrate\Plugin\MigrateDestinationPluginManager
|
||||
* @see \Drupal\migrate\Attribute\MigrateDestination
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
interface MigrateDestinationInterface extends PluginInspectionInterface {
|
||||
|
||||
/**
|
||||
* Gets the destination IDs.
|
||||
*
|
||||
* To support MigrateIdMap maps, derived destination classes should return
|
||||
* field definition(s) corresponding to the primary key of the destination
|
||||
* being implemented. These are used to construct the destination key fields
|
||||
* of the map table for a migration using this destination.
|
||||
*
|
||||
* @return array[]
|
||||
* An associative array of field definitions keyed by field ID. Values are
|
||||
* associative arrays with a structure that contains the field type ('type'
|
||||
* key). The other keys are the field storage settings as they are returned
|
||||
* by FieldStorageDefinitionInterface::getSettings(). As an example, for a
|
||||
* composite destination primary key that is defined by an integer and a
|
||||
* string, the returned value might look like:
|
||||
* @code
|
||||
* return [
|
||||
* 'id' => [
|
||||
* 'type' => 'integer',
|
||||
* 'unsigned' => FALSE,
|
||||
* 'size' => 'big',
|
||||
* ],
|
||||
* 'version' => [
|
||||
* 'type' => 'string',
|
||||
* 'max_length' => 64,
|
||||
* 'is_ascii' => TRUE,
|
||||
* ],
|
||||
* ];
|
||||
* @endcode
|
||||
* If 'type' points to a field plugin with multiple columns and needs to
|
||||
* refer to a column different than 'value', the key of that column will be
|
||||
* appended as a suffix to the plugin name, separated by dot ('.'). Example:
|
||||
* @code
|
||||
* return [
|
||||
* 'format' => [
|
||||
* 'type' => 'text.format',
|
||||
* ],
|
||||
* ];
|
||||
* @endcode
|
||||
* Additional custom keys/values, that are not part of field storage
|
||||
* definition, can be passed in definitions:
|
||||
* @code
|
||||
* return [
|
||||
* 'nid' => [
|
||||
* 'type' => 'integer',
|
||||
* 'custom_setting' => 'some_value',
|
||||
* ],
|
||||
* ];
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\Core\Field\FieldStorageDefinitionInterface::getSettings()
|
||||
* @see \Drupal\Core\Field\Plugin\Field\FieldType\IntegerItem
|
||||
* @see \Drupal\Core\Field\Plugin\Field\FieldType\StringItem
|
||||
* @see \Drupal\text\Plugin\Field\FieldType\TextItem
|
||||
*/
|
||||
public function getIds();
|
||||
|
||||
/**
|
||||
* Returns an array of destination fields.
|
||||
*
|
||||
* Derived classes must implement fields(), returning a list of available
|
||||
* destination fields.
|
||||
*
|
||||
* @return array
|
||||
* - Keys: machine names of the fields
|
||||
* - Values: Human-friendly descriptions of the fields.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
* Thrown when the destination plugin is not configured correctly.
|
||||
*/
|
||||
public function fields();
|
||||
|
||||
/**
|
||||
* Import the row.
|
||||
*
|
||||
* Derived classes must implement import(), to construct one new object
|
||||
* (pre-populated) using ID mappings in the Migration.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row object.
|
||||
* @param array $old_destination_id_values
|
||||
* (optional) The destination IDs from the previous import of this source
|
||||
* row. This is empty the first time a source row is migrated. Defaults to
|
||||
* an empty array.
|
||||
*
|
||||
* @return array|bool
|
||||
* An indexed array of destination IDs in the same order as defined in the
|
||||
* plugin's getIds() method if the plugin wants to save the IDs to the ID
|
||||
* map, TRUE to indicate success without saving IDs to the ID map, or
|
||||
* FALSE to indicate a failure.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
* Throws an exception if there is a problem importing the row. By default,
|
||||
* this causes the migration system to treat this row as having failed;
|
||||
* however, any \Drupal\migrate\Plugin\MigrateIdMapInterface status constant
|
||||
* can be set using the $status parameter of
|
||||
* \Drupal\migrate\MigrateException, such as
|
||||
* \Drupal\migrate\Plugin\MigrateIdMapInterface::STATUS_IGNORED.
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = []);
|
||||
|
||||
/**
|
||||
* Delete the specified destination object from the target Drupal.
|
||||
*
|
||||
* @param array $destination_identifier
|
||||
* An associative array of destination IDs for the object to delete. The
|
||||
* array keys are defined by the
|
||||
* \Drupal\migrate\Plugin\MigrateDestinationInterface::getIds() method used
|
||||
* by the destination object.
|
||||
*/
|
||||
public function rollback(array $destination_identifier);
|
||||
|
||||
/**
|
||||
* Whether the destination can be rolled back or not.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if rollback is supported, FALSE if not.
|
||||
*/
|
||||
public function supportsRollback();
|
||||
|
||||
/**
|
||||
* The rollback action for the last imported item.
|
||||
*
|
||||
* @return int
|
||||
* The MigrateIdMapInterface::ROLLBACK_ constant indicating how an imported
|
||||
* item should be handled on rollback.
|
||||
*/
|
||||
public function rollbackAction();
|
||||
|
||||
/**
|
||||
* Gets the destination module handling the destination data.
|
||||
*
|
||||
* @return string|null
|
||||
* The destination module or NULL if not found.
|
||||
*/
|
||||
public function getDestinationModule();
|
||||
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\migrate\Attribute\MigrateDestination;
|
||||
|
||||
/**
|
||||
* Plugin manager for migrate destination plugins.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateDestinationInterface
|
||||
* @see \Drupal\migrate\Plugin\migrate\destination\DestinationBase
|
||||
* @see \Drupal\migrate\Attribute\MigrateDestination
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
class MigrateDestinationPluginManager extends MigratePluginManager {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a MigrateDestinationPluginManager object.
|
||||
*
|
||||
* @param string $type
|
||||
* The type of the plugin: row, source, process, destination, entity_field,
|
||||
* id_map.
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* Cache backend instance to use.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler to invoke the alter hook with.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
* @param string $attribute
|
||||
* (optional) The attribute class name. Defaults to
|
||||
* 'Drupal\migrate\Attribute\MigrateDestination'.
|
||||
* @param string $annotation
|
||||
* (optional) The annotation class name. Defaults to
|
||||
* 'Drupal\migrate\Annotation\MigrateDestination'.
|
||||
*/
|
||||
public function __construct($type, \Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, EntityTypeManagerInterface $entity_type_manager, $attribute = MigrateDestination::class, $annotation = 'Drupal\migrate\Annotation\MigrateDestination') {
|
||||
parent::__construct($type, $namespaces, $cache_backend, $module_handler, $attribute, $annotation);
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* A specific createInstance method is necessary to pass the migration on.
|
||||
*/
|
||||
public function createInstance($plugin_id, array $configuration = [], ?MigrationInterface $migration = NULL) {
|
||||
if (str_starts_with($plugin_id, 'entity:') && !$this->entityTypeManager->getDefinition(substr($plugin_id, 7), FALSE)) {
|
||||
$plugin_id = 'null';
|
||||
}
|
||||
return parent::createInstance($plugin_id, $configuration, $migration);
|
||||
}
|
||||
|
||||
}
|
||||
311
web/core/modules/migrate/src/Plugin/MigrateIdMapInterface.php
Normal file
311
web/core/modules/migrate/src/Plugin/MigrateIdMapInterface.php
Normal file
@ -0,0 +1,311 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\migrate\MigrateMessageInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
// cspell:ignore destid sourceid
|
||||
|
||||
/**
|
||||
* Defines an interface for migrate ID mappings.
|
||||
*
|
||||
* Migrate ID mappings maintain a relation between source ID and destination ID
|
||||
* for audit and rollback purposes. The keys used in the migrate_map table are
|
||||
* of the form sourceidN and destidN for the source and destination values
|
||||
* respectively.
|
||||
*
|
||||
* The mappings are stored in a migrate_map table with properties:
|
||||
* - source_ids_hash: A hash of the source IDs.
|
||||
* - sourceidN: Any number of source IDs defined by a source plugin, where N
|
||||
* starts at 1, for example, sourceid1, sourceid2 ... sourceidN.
|
||||
* - destidN: Any number of destination IDs defined by a destination plugin,
|
||||
* where N starts at 1, for example, destid1, destid2 ... destidN.
|
||||
* - source_row_status: Indicates current status of the source row, valid
|
||||
* values are self::STATUS_IMPORTED, self::STATUS_NEEDS_UPDATE,
|
||||
* self::STATUS_IGNORED or self::STATUS_FAILED.
|
||||
* - rollback_action: Flag indicating what to do for this item on rollback. This
|
||||
* property is set in destination plugins. Valid values are
|
||||
* self::ROLLBACK_DELETE and self::ROLLBACK_PRESERVE.
|
||||
* - last_imported: UNIX timestamp of the last time the row was imported.
|
||||
* - hash: A hash of the source row data that is used to detect changes in the
|
||||
* source data.
|
||||
*/
|
||||
interface MigrateIdMapInterface extends \Iterator, PluginInspectionInterface {
|
||||
|
||||
/**
|
||||
* Indicates that the import of the row was successful.
|
||||
*/
|
||||
const STATUS_IMPORTED = 0;
|
||||
|
||||
/**
|
||||
* Indicates that the row needs to be updated.
|
||||
*/
|
||||
const STATUS_NEEDS_UPDATE = 1;
|
||||
|
||||
/**
|
||||
* Indicates that the import of the row was ignored.
|
||||
*/
|
||||
const STATUS_IGNORED = 2;
|
||||
|
||||
/**
|
||||
* Indicates that the import of the row failed.
|
||||
*/
|
||||
const STATUS_FAILED = 3;
|
||||
|
||||
/**
|
||||
* Indicates that the data for the row is to be deleted.
|
||||
*/
|
||||
const ROLLBACK_DELETE = 0;
|
||||
|
||||
/**
|
||||
* Indicates that the data for the row is to be preserved.
|
||||
*
|
||||
* Rows that refer to entities that already exist on the destination and are
|
||||
* being updated are preserved.
|
||||
*/
|
||||
const ROLLBACK_PRESERVE = 1;
|
||||
|
||||
/**
|
||||
* Saves a mapping from the source identifiers to the destination identifiers.
|
||||
*
|
||||
* Called upon import of one row, we record a mapping from the source ID to
|
||||
* the destination ID. Also may be called, setting the third parameter to
|
||||
* NEEDS_UPDATE, to signal an existing record should be re-migrated.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The raw source data. We use the ID map derived from the source object
|
||||
* to get the source identifier values.
|
||||
* @param array $destination_id_values
|
||||
* An array of destination identifier values.
|
||||
* @param int $status
|
||||
* (optional) Status of the source row in the map. Defaults to
|
||||
* self::STATUS_IMPORTED.
|
||||
* @param int $rollback_action
|
||||
* (optional) How to handle the destination object on rollback. Defaults to
|
||||
* self::ROLLBACK_DELETE.
|
||||
*/
|
||||
public function saveIdMapping(Row $row, array $destination_id_values, $status = self::STATUS_IMPORTED, $rollback_action = self::ROLLBACK_DELETE);
|
||||
|
||||
/**
|
||||
* Saves a message related to a source record in the migration message table.
|
||||
*
|
||||
* @param array $source_id_values
|
||||
* The source identifier keyed values of the record, e.g. ['nid' => 5].
|
||||
* @param string $message
|
||||
* The message to record.
|
||||
* @param int $level
|
||||
* (optional) The message severity. Defaults to
|
||||
* MigrationInterface::MESSAGE_ERROR.
|
||||
*/
|
||||
public function saveMessage(array $source_id_values, $message, $level = MigrationInterface::MESSAGE_ERROR);
|
||||
|
||||
/**
|
||||
* Retrieves a traversable object of messages related to source records.
|
||||
*
|
||||
* @param array $source_id_values
|
||||
* (optional) The source identifier keyed values of the record, e.g.
|
||||
* ['nid' => 5]. If empty (the default), all messages are retrieved.
|
||||
* @param int $level
|
||||
* (optional) Message severity. If NULL (the default), retrieve messages of
|
||||
* all severities.
|
||||
*
|
||||
* @return \Traversable
|
||||
* Retrieves a traversable object of message objects of unspecified class.
|
||||
* Each object has the following public properties:
|
||||
* - source_row_hash: the hash of the entire serialized source row data.
|
||||
* - message: the text of the message.
|
||||
* - level: one of MigrationInterface::MESSAGE_ERROR,
|
||||
* MigrationInterface::MESSAGE_WARNING, MigrationInterface::MESSAGE_NOTICE,
|
||||
* MigrationInterface::MESSAGE_INFORMATIONAL.
|
||||
*/
|
||||
public function getMessages(array $source_id_values = [], $level = NULL);
|
||||
|
||||
/**
|
||||
* Prepares to run a full update.
|
||||
*
|
||||
* Prepares this migration to run as an update - that is, in addition to
|
||||
* un-migrated content (source records not in the map table) being imported,
|
||||
* previously-migrated content will also be updated in place by marking all
|
||||
* previously-imported content as ready to be re-imported.
|
||||
*/
|
||||
public function prepareUpdate();
|
||||
|
||||
/**
|
||||
* Returns the number of processed items in the map.
|
||||
*
|
||||
* @return int
|
||||
* The count of records in the map table.
|
||||
*/
|
||||
public function processedCount();
|
||||
|
||||
/**
|
||||
* Returns the number of imported items in the map.
|
||||
*
|
||||
* @return int
|
||||
* The number of imported items.
|
||||
*/
|
||||
public function importedCount();
|
||||
|
||||
/**
|
||||
* Returns a count of items which are marked as needing update.
|
||||
*
|
||||
* @return int
|
||||
* The number of items which need updating.
|
||||
*/
|
||||
public function updateCount();
|
||||
|
||||
/**
|
||||
* Returns the number of items that failed to import.
|
||||
*
|
||||
* @return int
|
||||
* The number of items that failed to import.
|
||||
*/
|
||||
public function errorCount();
|
||||
|
||||
/**
|
||||
* Returns the number of messages saved.
|
||||
*
|
||||
* @return int
|
||||
* The number of messages.
|
||||
*/
|
||||
public function messageCount();
|
||||
|
||||
/**
|
||||
* Deletes the map and message entries for a given source record.
|
||||
*
|
||||
* @param array $source_id_values
|
||||
* The source identifier keyed values of the record, e.g. ['nid' => 5].
|
||||
* @param bool $messages_only
|
||||
* (optional) TRUE to only delete the migrate messages. Defaults to FALSE.
|
||||
*/
|
||||
public function delete(array $source_id_values, $messages_only = FALSE);
|
||||
|
||||
/**
|
||||
* Deletes the map and message table entries for a given destination row.
|
||||
*
|
||||
* @param array $destination_id_values
|
||||
* The destination identifier key value pairs we should do the deletes for.
|
||||
*/
|
||||
public function deleteDestination(array $destination_id_values);
|
||||
|
||||
/**
|
||||
* Clears all messages from the map.
|
||||
*/
|
||||
public function clearMessages();
|
||||
|
||||
/**
|
||||
* Retrieves a row from the map table based on source identifier values.
|
||||
*
|
||||
* @param array $source_id_values
|
||||
* The source identifier keyed values of the record, e.g. ['nid' => 5].
|
||||
*
|
||||
* @return array
|
||||
* The raw row data as an associative array.
|
||||
*/
|
||||
public function getRowBySource(array $source_id_values);
|
||||
|
||||
/**
|
||||
* Retrieves a row by the destination identifiers.
|
||||
*
|
||||
* @param array $destination_id_values
|
||||
* The destination identifier keyed values of the record, e.g. ['nid' => 5].
|
||||
*
|
||||
* @return array
|
||||
* The row(s) of data or an empty array when there is no matching map row.
|
||||
*/
|
||||
public function getRowByDestination(array $destination_id_values);
|
||||
|
||||
/**
|
||||
* Retrieves an array of map rows marked as needing update.
|
||||
*
|
||||
* @param int $count
|
||||
* The maximum number of rows to return.
|
||||
*
|
||||
* @return array
|
||||
* Array of map row objects that need updating.
|
||||
*/
|
||||
public function getRowsNeedingUpdate($count);
|
||||
|
||||
/**
|
||||
* Looks up the source identifier.
|
||||
*
|
||||
* Given a (possibly multi-field) destination identifier value, return the
|
||||
* (possibly multi-field) source identifier value mapped to it.
|
||||
*
|
||||
* @param array $destination_id_values
|
||||
* The destination identifier keyed values of the record, e.g. ['nid' => 5].
|
||||
*
|
||||
* @return array
|
||||
* The source identifier keyed values of the record, e.g. ['nid' => 5], or
|
||||
* an empty array on failure.
|
||||
*/
|
||||
public function lookupSourceId(array $destination_id_values);
|
||||
|
||||
/**
|
||||
* Looks up the destination identifiers corresponding to a source key.
|
||||
*
|
||||
* This can look up a subset of source keys if only some are provided, and
|
||||
* will return all destination keys that match.
|
||||
*
|
||||
* @param array $source_id_values
|
||||
* The source identifier keyed values of the records, e.g. ['nid' => 5].
|
||||
* If un-keyed, the first count($source_id_values) keys will be assumed.
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays of destination identifier values.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
* Thrown when $source_id_values contains unknown keys, or is the wrong
|
||||
* length.
|
||||
*/
|
||||
public function lookupDestinationIds(array $source_id_values);
|
||||
|
||||
/**
|
||||
* Looks up the destination identifier currently being iterated.
|
||||
*
|
||||
* @return array
|
||||
* The destination identifier values of the record, or NULL on failure.
|
||||
*/
|
||||
public function currentDestination();
|
||||
|
||||
/**
|
||||
* Looks up the source identifier(s) currently being iterated.
|
||||
*
|
||||
* @return array
|
||||
* The source identifier values of the record, or NULL on failure.
|
||||
*/
|
||||
public function currentSource();
|
||||
|
||||
/**
|
||||
* Removes any persistent storage used by this map.
|
||||
*
|
||||
* For example, remove the map and message tables.
|
||||
*/
|
||||
public function destroy();
|
||||
|
||||
/**
|
||||
* Gets the qualified map table.
|
||||
*
|
||||
* @todo Remove this as this is SQL only and so doesn't belong to the interface.
|
||||
*/
|
||||
public function getQualifiedMapTableName();
|
||||
|
||||
/**
|
||||
* Sets the migrate message service.
|
||||
*
|
||||
* @param \Drupal\migrate\MigrateMessageInterface $message
|
||||
* The migrate message service.
|
||||
*/
|
||||
public function setMessage(MigrateMessageInterface $message);
|
||||
|
||||
/**
|
||||
* Sets a specified record to be updated, if it exists.
|
||||
*
|
||||
* @param array $source_id_values
|
||||
* The source identifier values of the record.
|
||||
*/
|
||||
public function setUpdate(array $source_id_values);
|
||||
|
||||
}
|
||||
75
web/core/modules/migrate/src/Plugin/MigratePluginManager.php
Normal file
75
web/core/modules/migrate/src/Plugin/MigratePluginManager.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\Attribute\AttributeInterface;
|
||||
use Drupal\Component\Plugin\Attribute\PluginID;
|
||||
use Drupal\Component\Plugin\Factory\DefaultFactory;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
|
||||
/**
|
||||
* Manages migrate plugins.
|
||||
*
|
||||
* @see hook_migrate_info_alter()
|
||||
* @see \Drupal\migrate\Attribute\MigrateSource
|
||||
* @see \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
* @see \Drupal\migrate\Attribute\MigrateProcess
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
* @see \Drupal\migrate\Plugin\migrate\process\ProcessPluginBase
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
class MigratePluginManager extends DefaultPluginManager implements MigratePluginManagerInterface {
|
||||
|
||||
/**
|
||||
* Constructs a MigratePluginManager object.
|
||||
*
|
||||
* @param string $type
|
||||
* The type of the plugin: row, source, process, destination, entity_field,
|
||||
* id_map.
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* Cache backend instance to use.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler to invoke the alter hook with.
|
||||
* @param string $attribute
|
||||
* (optional) The attribute class name. Defaults to
|
||||
* 'Drupal\Component\Plugin\Attribute\PluginID'.
|
||||
* @param string $annotation
|
||||
* (optional) The annotation class name. Defaults to
|
||||
* 'Drupal\Component\Annotation\PluginID'.
|
||||
*/
|
||||
public function __construct($type, \Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, $attribute = PluginID::class, $annotation = 'Drupal\Component\Annotation\PluginID') {
|
||||
if (!is_subclass_of($attribute, AttributeInterface::class)) {
|
||||
// Backward compatibility.
|
||||
$annotation = $attribute;
|
||||
$attribute = PluginID::class;
|
||||
}
|
||||
parent::__construct("Plugin/migrate/$type", $namespaces, $module_handler, NULL, $attribute, $annotation);
|
||||
$this->alterInfo('migrate_' . $type . '_info');
|
||||
$this->setCacheBackend($cache_backend, 'migrate_plugins_' . $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createInstance($plugin_id, array $configuration = [], ?MigrationInterface $migration = NULL) {
|
||||
$plugin_definition = $this->getDefinition($plugin_id);
|
||||
$plugin_class = DefaultFactory::getPluginClass($plugin_id, $plugin_definition);
|
||||
// If the plugin provides a factory method, pass the container to it.
|
||||
if (is_subclass_of($plugin_class, 'Drupal\Core\Plugin\ContainerFactoryPluginInterface')) {
|
||||
$plugin = $plugin_class::create(\Drupal::getContainer(), $configuration, $plugin_id, $plugin_definition, $migration);
|
||||
}
|
||||
else {
|
||||
$plugin = new $plugin_class($configuration, $plugin_id, $plugin_definition, $migration);
|
||||
}
|
||||
return $plugin;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginManagerInterface;
|
||||
|
||||
/**
|
||||
* Interface for the migration plugin manager.
|
||||
*/
|
||||
interface MigratePluginManagerInterface extends PluginManagerInterface {
|
||||
|
||||
/**
|
||||
* Creates a pre-configured instance of a migration plugin.
|
||||
*
|
||||
* A specific createInstance method is necessary to pass the migration on.
|
||||
*
|
||||
* @param string $plugin_id
|
||||
* The ID of the plugin being instantiated.
|
||||
* @param array $configuration
|
||||
* An array of configuration relevant to the plugin instance.
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration context in which the plugin will run.
|
||||
*
|
||||
* @return object
|
||||
* A fully configured plugin instance.
|
||||
*
|
||||
* @throws \Drupal\Component\Plugin\Exception\PluginException
|
||||
* If the instance cannot be created, such as if the ID is invalid.
|
||||
*/
|
||||
public function createInstance($plugin_id, array $configuration = [], ?MigrationInterface $migration = NULL);
|
||||
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* An interface for migrate process plugins.
|
||||
*
|
||||
* Migrate process plugins transform the input value.For example, transform a
|
||||
* human provided name into a machine name, look up an identifier in a previous
|
||||
* migration and so on.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigratePluginManager
|
||||
* @see \Drupal\migrate\ProcessPluginBase
|
||||
* @see \Drupal\migrate\Attribute\MigrateProcess
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
interface MigrateProcessInterface extends PluginInspectionInterface {
|
||||
|
||||
/**
|
||||
* Performs the associated process.
|
||||
*
|
||||
* @param mixed $value
|
||||
* The value to be transformed.
|
||||
* @param \Drupal\migrate\MigrateExecutableInterface $migrate_executable
|
||||
* The migration in which this process is being executed.
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row from the source to process. Normally, just transforming the value
|
||||
* is adequate but very rarely you might need to change two columns at the
|
||||
* same time or something like that.
|
||||
* @param string $destination_property
|
||||
* The destination property currently worked on. This is only used together
|
||||
* with the $row above.
|
||||
*
|
||||
* @return mixed
|
||||
* The newly transformed value.
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property);
|
||||
|
||||
/**
|
||||
* Indicates whether the returned value requires multiple handling.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE when the returned value contains a list of values to be processed.
|
||||
* For example, when the 'source' property is a string and the value found
|
||||
* is an array.
|
||||
*/
|
||||
public function multiple();
|
||||
|
||||
/**
|
||||
* Determines if the pipeline should stop processing.
|
||||
*
|
||||
* @return bool
|
||||
* A boolean value indicating if the pipeline processing should stop.
|
||||
*/
|
||||
public function isPipelineStopped(): bool;
|
||||
|
||||
/**
|
||||
* Resets the internal data of a plugin.
|
||||
*/
|
||||
public function reset(): void;
|
||||
|
||||
}
|
||||
124
web/core/modules/migrate/src/Plugin/MigrateSourceInterface.php
Normal file
124
web/core/modules/migrate/src/Plugin/MigrateSourceInterface.php
Normal file
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Defines an interface for migrate sources.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigratePluginManager
|
||||
* @see \Drupal\migrate\Attribute\MigrateSource
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
interface MigrateSourceInterface extends \Countable, \Iterator, PluginInspectionInterface {
|
||||
|
||||
/**
|
||||
* Indicates that the source is not countable.
|
||||
*/
|
||||
const NOT_COUNTABLE = -1;
|
||||
|
||||
/**
|
||||
* Returns available fields on the source.
|
||||
*
|
||||
* @return array
|
||||
* Available fields in the source, keys are the field machine names as used
|
||||
* in field mappings, values are descriptions.
|
||||
*/
|
||||
public function fields();
|
||||
|
||||
/**
|
||||
* Adds additional data to the row.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row object.
|
||||
*
|
||||
* @return bool
|
||||
* FALSE if this row needs to be skipped.
|
||||
*/
|
||||
public function prepareRow(Row $row);
|
||||
|
||||
/**
|
||||
* Allows class to decide how it will react when it is treated like a string.
|
||||
*/
|
||||
public function __toString();
|
||||
|
||||
/**
|
||||
* Defines the source fields uniquely identifying a source row.
|
||||
*
|
||||
* None of these fields should contain a NULL value. If necessary, use
|
||||
* prepareRow() or hook_migrate_prepare_row() to rewrite NULL values to
|
||||
* appropriate empty values (such as '' or 0).
|
||||
*
|
||||
* @return array[]
|
||||
* An associative array of field definitions keyed by field ID. Values are
|
||||
* associative arrays with a structure that contains the field type ('type'
|
||||
* key). The other keys are the field storage settings as they are returned
|
||||
* by FieldStorageDefinitionInterface::getSettings().
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* A composite source primary key that is defined by an integer and a string
|
||||
* might look like this:
|
||||
* @code
|
||||
* return [
|
||||
* 'id' => [
|
||||
* 'type' => 'integer',
|
||||
* 'unsigned' => FALSE,
|
||||
* 'size' => 'big',
|
||||
* ],
|
||||
* 'version' => [
|
||||
* 'type' => 'string',
|
||||
* 'max_length' => 64,
|
||||
* 'is_ascii' => TRUE,
|
||||
* ],
|
||||
* ];
|
||||
* @endcode
|
||||
*
|
||||
* If 'type' points to a field plugin with multiple columns and needs to
|
||||
* refer to a column different than 'value', the key of that column will be
|
||||
* appended as a suffix to the plugin name, separated by dot ('.'). Example:
|
||||
* @code
|
||||
* return [
|
||||
* 'format' => [
|
||||
* 'type' => 'text.format',
|
||||
* ],
|
||||
* ];
|
||||
* @endcode
|
||||
*
|
||||
* Additional custom keys/values that are not part of field storage
|
||||
* definition can be added as shown below. The most common setting
|
||||
* passed along to the ID definition is table 'alias', used by the SqlBase
|
||||
* source plugin in order to distinguish between ambiguous column names -
|
||||
* for example, when a SQL source query joins two tables with the same
|
||||
* column names.
|
||||
* @code
|
||||
* return [
|
||||
* 'nid' => [
|
||||
* 'type' => 'integer',
|
||||
* 'alias' => 'n',
|
||||
* ],
|
||||
* ];
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\Core\Field\FieldStorageDefinitionInterface::getSettings()
|
||||
* @see \Drupal\Core\Field\Plugin\Field\FieldType\IntegerItem
|
||||
* @see \Drupal\Core\Field\Plugin\Field\FieldType\StringItem
|
||||
* @see \Drupal\text\Plugin\Field\FieldType\TextItem
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
|
||||
*/
|
||||
public function getIds();
|
||||
|
||||
/**
|
||||
* Gets the source module providing the source data.
|
||||
*
|
||||
* @return string|null
|
||||
* The source module or NULL if not found.
|
||||
*/
|
||||
public function getSourceModule();
|
||||
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
|
||||
use Drupal\migrate\Attribute\MigrateSource;
|
||||
use Drupal\migrate\Plugin\Discovery\AttributeDiscoveryWithAnnotationsAutomatedProviders;
|
||||
use Drupal\migrate\Plugin\Discovery\ProviderFilterDecorator;
|
||||
|
||||
/**
|
||||
* Plugin manager for migrate source plugins.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
* @see \Drupal\migrate\Attribute\MigrateSource
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
class MigrateSourcePluginManager extends MigratePluginManager {
|
||||
|
||||
/**
|
||||
* MigrateSourcePluginManager constructor.
|
||||
*
|
||||
* @param string $type
|
||||
* The type of the plugin: row, source, process, destination, entity_field,
|
||||
* id_map.
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* Cache backend instance to use.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler to invoke the alter hook with.
|
||||
*/
|
||||
public function __construct($type, \Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
|
||||
parent::__construct($type, $namespaces, $cache_backend, $module_handler, MigrateSource::class, 'Drupal\migrate\Annotation\MigrateSource');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getDiscovery() {
|
||||
if (!$this->discovery) {
|
||||
$discovery = new AttributeDiscoveryWithAnnotationsAutomatedProviders(
|
||||
$this->subdir,
|
||||
$this->namespaces,
|
||||
$this->pluginDefinitionAttributeName,
|
||||
$this->pluginDefinitionAnnotationName,
|
||||
$this->additionalAnnotationNamespaces,
|
||||
);
|
||||
$this->discovery = new ContainerDerivativeDiscoveryDecorator($discovery);
|
||||
}
|
||||
return $this->discovery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds plugin definitions.
|
||||
*
|
||||
* @return array
|
||||
* List of definitions to store in cache.
|
||||
*
|
||||
* @todo This is a temporary solution to the fact that migration source
|
||||
* plugins have more than one provider. This functionality will be moved to
|
||||
* core in https://www.drupal.org/node/2786355.
|
||||
*/
|
||||
protected function findDefinitions() {
|
||||
$definitions = $this->getDiscovery()->getDefinitions();
|
||||
foreach ($definitions as $plugin_id => &$definition) {
|
||||
$this->processDefinition($definition, $plugin_id);
|
||||
}
|
||||
$this->alterDefinitions($definitions);
|
||||
return ProviderFilterDecorator::filterDefinitions($definitions, function ($provider) {
|
||||
return $this->providerExists($provider);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
|
||||
/**
|
||||
* To implement by a destination plugin that should provide entity validation.
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
interface MigrateValidatableEntityInterface {
|
||||
|
||||
/**
|
||||
* Returns a state of whether an entity needs to be validated before saving.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
|
||||
* The entity to check for required validation.
|
||||
*
|
||||
* @return bool
|
||||
* A state of whether an entity needs to be validated.
|
||||
*/
|
||||
public function isEntityValidationRequired(FieldableEntityInterface $entity);
|
||||
|
||||
/**
|
||||
* Validates the entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
|
||||
* The entity to validate.
|
||||
*
|
||||
* @throws \Drupal\migrate\Exception\EntityValidationException
|
||||
* When the validation didn't succeed.
|
||||
*/
|
||||
public function validateEntity(FieldableEntityInterface $entity);
|
||||
|
||||
}
|
||||
772
web/core/modules/migrate/src/Plugin/Migration.php
Normal file
772
web/core/modules/migrate/src/Plugin/Migration.php
Normal file
@ -0,0 +1,772 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\migrate\Exception\RequirementsException;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateSkipRowException;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
// cspell:ignore idmap
|
||||
/**
|
||||
* Defines the Migration plugin.
|
||||
*
|
||||
* A migration plugin instance that represents one single migration and acts
|
||||
* like a container for the information about a single migration such as the
|
||||
* source, process and destination plugins.
|
||||
*
|
||||
* The configuration of a migration is defined using YAML format and placed in
|
||||
* the directory MODULENAME/migrations.
|
||||
*
|
||||
* Available definition keys:
|
||||
* - id: The migration ID.
|
||||
* - label: The human-readable label for the migration.
|
||||
* - source: The definition for a migrate source plugin.
|
||||
* - process: The definition for the migrate process pipelines for the
|
||||
* destination properties.
|
||||
* - destination: The definition a migrate destination plugin.
|
||||
* - audit: (optional) Audit the migration for conflicts with existing content.
|
||||
* - deriver: (optional) The fully qualified path to a deriver class.
|
||||
* - idMap: (optional) The definition for a migrate idMap plugin.
|
||||
* - migration_dependencies: (optional) An array with two keys 'required' and
|
||||
* 'optional' listing the migrations that this migration depends on. The
|
||||
* required migrations must be run first and completed successfully. The
|
||||
* optional migrations will be executed if they are present.
|
||||
* - migration_tags: (optional) An array of tags for this migration.
|
||||
* - provider: (optional) The name of the module that provides the plugin.
|
||||
*
|
||||
* Example with all keys:
|
||||
*
|
||||
* @code
|
||||
* id: d7_taxonomy_term_example
|
||||
* label: Taxonomy terms
|
||||
* audit: true
|
||||
* migration_tags:
|
||||
* - Drupal 7
|
||||
* - Content
|
||||
* - Term example
|
||||
* deriver: Drupal\taxonomy\Plugin\migrate\D7TaxonomyTermDeriver
|
||||
* provider: custom_module
|
||||
* source:
|
||||
* plugin: d7_taxonomy_term
|
||||
* process:
|
||||
* tid: tid
|
||||
* vid:
|
||||
* plugin: migration_lookup
|
||||
* migration: d7_taxonomy_vocabulary
|
||||
* source: vid
|
||||
* name: name
|
||||
* 'description/value': description
|
||||
* 'description/format': format
|
||||
* weight: weight
|
||||
* parent_id:
|
||||
* -
|
||||
* plugin: skip_on_empty
|
||||
* method: process
|
||||
* source: parent
|
||||
* -
|
||||
* plugin: migration_lookup
|
||||
* migration: d7_taxonomy_term
|
||||
* parent:
|
||||
* plugin: default_value
|
||||
* default_value: 0
|
||||
* source: '@parent_id'
|
||||
* destination:
|
||||
* plugin: entity:taxonomy_term
|
||||
* migration_dependencies:
|
||||
* required:
|
||||
* - d7_taxonomy_vocabulary
|
||||
* optional:
|
||||
* - d7_field_instance
|
||||
* @endcode
|
||||
*
|
||||
* For additional configuration keys, refer to these Migrate classes.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\destination\Config
|
||||
* @see \Drupal\migrate\Plugin\migrate\destination\EntityConfigBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\destination\EntityContentBase
|
||||
* @see \Drupal\Core\Plugin\PluginBase
|
||||
*
|
||||
* @link https://www.drupal.org/docs/8/api/migrate-api Migrate API handbook. @endlink
|
||||
*/
|
||||
#[\AllowDynamicProperties]
|
||||
class Migration extends PluginBase implements MigrationInterface, RequirementsInterface, ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The migration ID (machine name).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* The human-readable label for the migration.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* The source configuration, with at least a 'plugin' key.
|
||||
*
|
||||
* Used to initialize the $sourcePlugin.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $source;
|
||||
|
||||
/**
|
||||
* The source plugin.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
*/
|
||||
protected $sourcePlugin;
|
||||
|
||||
/**
|
||||
* The configuration describing the process plugins.
|
||||
*
|
||||
* This is a strictly internal property and should not returned to calling
|
||||
* code, use getProcess() instead.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $process = [];
|
||||
|
||||
/**
|
||||
* The cached process plugins.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $processPlugins = [];
|
||||
|
||||
/**
|
||||
* The destination configuration, with at least a 'plugin' key.
|
||||
*
|
||||
* Used to initialize $destinationPlugin.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $destination;
|
||||
|
||||
/**
|
||||
* The destination plugin.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrateDestinationInterface
|
||||
*/
|
||||
protected $destinationPlugin;
|
||||
|
||||
/**
|
||||
* The identifier map data.
|
||||
*
|
||||
* Used to initialize $idMapPlugin.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $idMap = [];
|
||||
|
||||
/**
|
||||
* The identifier map.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrateIdMapInterface
|
||||
*/
|
||||
protected $idMapPlugin;
|
||||
|
||||
/**
|
||||
* The destination identifiers.
|
||||
*
|
||||
* An array of destination identifiers: the keys are the name of the
|
||||
* properties, the values are dependent on the ID map plugin.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $destinationIds = [];
|
||||
|
||||
/**
|
||||
* The source_row_status for the current map row.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $sourceRowStatus = MigrateIdMapInterface::STATUS_IMPORTED;
|
||||
|
||||
/**
|
||||
* These migrations must be already executed before this migration can run.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $requirements = [];
|
||||
|
||||
/**
|
||||
* An optional list of tags, used by the plugin manager for filtering.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
// phpcs:ignore Drupal.NamingConventions.ValidVariableName.LowerCamelName, Drupal.Commenting.VariableComment.Missing
|
||||
protected $migration_tags = [];
|
||||
|
||||
/**
|
||||
* Whether the migration is auditable.
|
||||
*
|
||||
* If set to TRUE, the migration's IDs will be audited. This means that, if
|
||||
* the highest destination ID is greater than the highest source ID, a warning
|
||||
* will be displayed that entities might be overwritten.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $audit = FALSE;
|
||||
|
||||
/**
|
||||
* These migrations, if run, must be executed before this migration.
|
||||
*
|
||||
* These are different from the configuration dependencies. Migration
|
||||
* dependencies are only used to store relationships between migrations.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
* The migration_dependencies value is structured like this:
|
||||
* @code
|
||||
* [
|
||||
* 'required' => [
|
||||
* // An array of migration IDs that must be run before this migration.
|
||||
* ],
|
||||
* 'optional' => [
|
||||
* // An array of migration IDs that, if they exist, must be run before
|
||||
* // this migration.
|
||||
* ],
|
||||
* ];
|
||||
* @endcode
|
||||
*/
|
||||
// phpcs:ignore Drupal.NamingConventions.ValidVariableName.LowerCamelName
|
||||
protected $migration_dependencies = [];
|
||||
|
||||
/**
|
||||
* The migration plugin manager for loading other migration plugins.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
|
||||
*/
|
||||
protected $migrationPluginManager;
|
||||
|
||||
/**
|
||||
* The source plugin manager.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigratePluginManager
|
||||
*/
|
||||
protected $sourcePluginManager;
|
||||
|
||||
/**
|
||||
* The process plugin manager.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigratePluginManager
|
||||
*/
|
||||
protected $processPluginManager;
|
||||
|
||||
/**
|
||||
* The destination plugin manager.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrateDestinationPluginManager
|
||||
*/
|
||||
protected $destinationPluginManager;
|
||||
|
||||
/**
|
||||
* The ID map plugin manager.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigratePluginManager
|
||||
*/
|
||||
protected $idMapPluginManager;
|
||||
|
||||
/**
|
||||
* Labels corresponding to each defined status.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $statusLabels = [
|
||||
self::STATUS_IDLE => 'Idle',
|
||||
self::STATUS_IMPORTING => 'Importing',
|
||||
self::STATUS_ROLLING_BACK => 'Rolling back',
|
||||
self::STATUS_STOPPING => 'Stopping',
|
||||
self::STATUS_DISABLED => 'Disabled',
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructs a Migration.
|
||||
*
|
||||
* @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\MigratePluginManagerInterface $source_plugin_manager
|
||||
* The source migration plugin manager.
|
||||
* @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $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\MigratePluginManagerInterface $id_map_plugin_manager
|
||||
* The ID map migration plugin manager.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationPluginManagerInterface $migration_plugin_manager, MigratePluginManagerInterface $source_plugin_manager, MigratePluginManagerInterface $process_plugin_manager, MigrateDestinationPluginManager $destination_plugin_manager, MigratePluginManagerInterface $id_map_plugin_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->migrationPluginManager = $migration_plugin_manager;
|
||||
$this->sourcePluginManager = $source_plugin_manager;
|
||||
$this->processPluginManager = $process_plugin_manager;
|
||||
$this->destinationPluginManager = $destination_plugin_manager;
|
||||
$this->idMapPluginManager = $id_map_plugin_manager;
|
||||
|
||||
foreach (NestedArray::mergeDeepArray([$plugin_definition, $configuration], TRUE) as $key => $value) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
|
||||
$this->migration_dependencies = ($this->migration_dependencies ?: []) + ['required' => [], 'optional' => []];
|
||||
if (count($this->migration_dependencies) !== 2 || !is_array($this->migration_dependencies['required']) || !is_array($this->migration_dependencies['optional'])) {
|
||||
throw new InvalidPluginDefinitionException($this->id(), "Invalid migration dependencies configuration for migration {$this->id()}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function id() {
|
||||
return $this->pluginId;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function label() {
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the ID map plugin.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrateIdMapInterface
|
||||
* The ID map plugin.
|
||||
*/
|
||||
public function getIdMapPlugin() {
|
||||
return $this->idMapPlugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSourcePlugin() {
|
||||
if (!isset($this->sourcePlugin)) {
|
||||
$this->sourcePlugin = $this->sourcePluginManager->createInstance($this->source['plugin'], $this->source, $this);
|
||||
}
|
||||
return $this->sourcePlugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProcessPlugins(?array $process = NULL) {
|
||||
$process = isset($process) ? $this->getProcessNormalized($process) : $this->getProcess();
|
||||
$index = serialize($process);
|
||||
if (!isset($this->processPlugins[$index])) {
|
||||
$this->processPlugins[$index] = [];
|
||||
|
||||
foreach ($process as $property => $configurations) {
|
||||
$this->processPlugins[$index][$property] = [];
|
||||
foreach ($configurations as $configuration) {
|
||||
if (isset($configuration['source'])) {
|
||||
$this->processPlugins[$index][$property][] = $this->processPluginManager->createInstance('get', $configuration, $this);
|
||||
}
|
||||
// Get is already handled.
|
||||
if ($configuration['plugin'] != 'get') {
|
||||
$this->processPlugins[$index][$property][] = $this->processPluginManager->createInstance($configuration['plugin'], $configuration, $this);
|
||||
}
|
||||
if (!$this->processPlugins[$index][$property]) {
|
||||
throw new MigrateException("Invalid process configuration for $property");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->processPlugins[$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve shorthands into a list of plugin configurations.
|
||||
*
|
||||
* @param array $process
|
||||
* A process configuration array.
|
||||
*
|
||||
* @return array
|
||||
* The normalized process configuration.
|
||||
*/
|
||||
protected function getProcessNormalized(array $process) {
|
||||
$normalized_configurations = [];
|
||||
foreach ($process as $destination => $configuration) {
|
||||
if (is_string($configuration)) {
|
||||
$configuration = [
|
||||
'plugin' => 'get',
|
||||
'source' => $configuration,
|
||||
];
|
||||
}
|
||||
if (isset($configuration['plugin'])) {
|
||||
$configuration = [$configuration];
|
||||
}
|
||||
if (!is_array($configuration)) {
|
||||
$migration_id = $this->getPluginId();
|
||||
throw new MigrateException("Invalid process for destination '$destination' in migration '$migration_id'");
|
||||
}
|
||||
$normalized_configurations[$destination] = $configuration;
|
||||
}
|
||||
return $normalized_configurations;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDestinationPlugin($stub_being_requested = FALSE) {
|
||||
if ($stub_being_requested && !empty($this->destination['no_stub'])) {
|
||||
throw new MigrateSkipRowException('Stub requested but not made because no_stub configuration is set.');
|
||||
}
|
||||
if (!isset($this->destinationPlugin)) {
|
||||
$this->destinationPlugin = $this->destinationPluginManager->createInstance($this->destination['plugin'], $this->destination, $this);
|
||||
}
|
||||
return $this->destinationPlugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIdMap() {
|
||||
if (!isset($this->idMapPlugin)) {
|
||||
$configuration = $this->idMap;
|
||||
$plugin = $configuration['plugin'] ?? 'sql';
|
||||
$this->idMapPlugin = $this->idMapPluginManager->createInstance($plugin, $configuration, $this);
|
||||
}
|
||||
return $this->idMapPlugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRequirements(): array {
|
||||
return $this->requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkRequirements() {
|
||||
// Check whether the current migration source and destination plugin
|
||||
// requirements are met or not.
|
||||
if ($this->getSourcePlugin() instanceof RequirementsInterface) {
|
||||
$this->getSourcePlugin()->checkRequirements();
|
||||
}
|
||||
if ($this->getDestinationPlugin() instanceof RequirementsInterface) {
|
||||
$this->getDestinationPlugin()->checkRequirements();
|
||||
}
|
||||
|
||||
if (empty($this->requirements)) {
|
||||
// There are no requirements to check.
|
||||
return;
|
||||
}
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface[] $required_migrations */
|
||||
$required_migrations = $this->getMigrationPluginManager()->createInstances($this->requirements);
|
||||
|
||||
$missing_migrations = array_diff($this->requirements, array_keys($required_migrations));
|
||||
// Check if the dependencies are in good shape.
|
||||
foreach ($required_migrations as $migration_id => $required_migration) {
|
||||
if (!$required_migration->allRowsProcessed()) {
|
||||
$missing_migrations[] = $migration_id;
|
||||
}
|
||||
}
|
||||
if ($missing_migrations) {
|
||||
throw new RequirementsException('Missing migrations ' . implode(', ', $missing_migrations) . '.', ['requirements' => $missing_migrations]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the migration plugin manager.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrationPluginManagerInterface
|
||||
* The migration plugin manager.
|
||||
*/
|
||||
protected function getMigrationPluginManager() {
|
||||
return $this->migrationPluginManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setStatus($status) {
|
||||
\Drupal::keyValue('migrate_status')->set($this->id(), $status);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getStatus() {
|
||||
return \Drupal::keyValue('migrate_status')->get($this->id(), static::STATUS_IDLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getStatusLabel() {
|
||||
$status = $this->getStatus();
|
||||
if (isset($this->statusLabels[$status])) {
|
||||
return $this->statusLabels[$status];
|
||||
}
|
||||
else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInterruptionResult() {
|
||||
return \Drupal::keyValue('migrate_interruption_result')->get($this->id(), static::RESULT_INCOMPLETE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clearInterruptionResult() {
|
||||
\Drupal::keyValue('migrate_interruption_result')->delete($this->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function interruptMigration($result) {
|
||||
$this->setStatus(MigrationInterface::STATUS_STOPPING);
|
||||
\Drupal::keyValue('migrate_interruption_result')->set($this->id(), $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function allRowsProcessed() {
|
||||
$source_count = $this->getSourcePlugin()->count();
|
||||
// If the source is uncountable, we have no way of knowing if it's
|
||||
// complete, so stipulate that it is.
|
||||
if ($source_count < 0) {
|
||||
return TRUE;
|
||||
}
|
||||
$processed_count = $this->getIdMap()->processedCount();
|
||||
// We don't use == because in some circumstances (like unresolved stubs
|
||||
// being created), the processed count may be higher than the available
|
||||
// source rows.
|
||||
return $source_count <= $processed_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($property_name, $value) {
|
||||
if ($property_name == 'source') {
|
||||
// Invalidate the source plugin.
|
||||
unset($this->sourcePlugin);
|
||||
}
|
||||
elseif ($property_name === 'destination') {
|
||||
// Invalidate the destination plugin.
|
||||
unset($this->destinationPlugin);
|
||||
}
|
||||
elseif ($property_name === 'migration_dependencies') {
|
||||
$value = ($value ?: []) + ['required' => [], 'optional' => []];
|
||||
}
|
||||
$this->{$property_name} = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProcess() {
|
||||
return $this->getProcessNormalized($this->process);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setProcess(array $process) {
|
||||
$this->process = $process;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setProcessOfProperty($property, $process_of_property) {
|
||||
$this->process[$property] = $process_of_property;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add required migration dependencies.
|
||||
*
|
||||
* @param string[] $required_dependencies
|
||||
* An array of migration IDs to be added to the required migration
|
||||
* dependencies.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addRequiredDependencies(array $required_dependencies): MigrationInterface {
|
||||
$this->migration_dependencies['required'] = array_unique(array_merge($this->migration_dependencies['required'], $required_dependencies));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add optional migration dependencies.
|
||||
*
|
||||
* @param string[] $optional_dependencies
|
||||
* An array of migration IDs to be added to the optional migration
|
||||
* dependencies.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addOptionalDependencies(array $optional_dependencies): MigrationInterface {
|
||||
$this->migration_dependencies['optional'] = array_unique(array_merge($this->migration_dependencies['optional'], $optional_dependencies));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function mergeProcessOfProperty($property, array $process_of_property) {
|
||||
// If we already have a process value then merge the incoming process array
|
||||
// otherwise simply set it.
|
||||
$current_process = $this->getProcess();
|
||||
if (isset($current_process[$property])) {
|
||||
$this->process = NestedArray::mergeDeepArray([$current_process, $this->getProcessNormalized([$property => $process_of_property])], TRUE);
|
||||
}
|
||||
else {
|
||||
$this->setProcessOfProperty($property, $process_of_property);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the dependencies for this migration.
|
||||
*
|
||||
* @return array
|
||||
* The dependencies for this migration.
|
||||
*/
|
||||
public function getMigrationDependencies() {
|
||||
if (func_num_args() > 0) {
|
||||
@trigger_error('Calling ' . __METHOD__ . ' with the $expand parameter is deprecated in drupal:11.0.0 and is removed drupal:12.0.0. See https://www.drupal.org/node/3442785', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
$this->migration_dependencies = ($this->migration_dependencies ?: []) + ['required' => [], 'optional' => []];
|
||||
if (count($this->migration_dependencies) !== 2 || !is_array($this->migration_dependencies['required']) || !is_array($this->migration_dependencies['optional'])) {
|
||||
throw new InvalidPluginDefinitionException($this->id(), "Invalid migration dependencies configuration for migration {$this->id()}");
|
||||
}
|
||||
$this->migration_dependencies['optional'] = array_unique(array_merge($this->migration_dependencies['optional'], $this->findMigrationDependencies($this->process)));
|
||||
|
||||
return array_map(
|
||||
[$this->migrationPluginManager, 'expandPluginIds'],
|
||||
$this->migration_dependencies
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find migration dependencies from migration_lookup and sub_process plugins.
|
||||
*
|
||||
* @param array $process
|
||||
* A process configuration array.
|
||||
*
|
||||
* @return array
|
||||
* The migration dependencies.
|
||||
*/
|
||||
protected function findMigrationDependencies($process) {
|
||||
$return = [];
|
||||
foreach ($this->getProcessNormalized($process) as $process_pipeline) {
|
||||
foreach ($process_pipeline as $plugin_configuration) {
|
||||
// If the migration uses a deriver and has a migration_lookup with
|
||||
// itself as the source migration, then skip adding dependencies.
|
||||
// Otherwise the migration will depend on all the variations of itself.
|
||||
// See d7_taxonomy_term for an example.
|
||||
if (isset($this->deriver)
|
||||
&& $plugin_configuration['plugin'] === 'migration_lookup'
|
||||
&& $plugin_configuration['migration'] == $this->getBaseId()) {
|
||||
continue;
|
||||
}
|
||||
if (in_array($plugin_configuration['plugin'], ['migration', 'migration_lookup'], TRUE)) {
|
||||
$return = array_merge($return, (array) $plugin_configuration['migration']);
|
||||
}
|
||||
if (in_array($plugin_configuration['plugin'], ['iterator', 'sub_process'], TRUE)) {
|
||||
$return = array_merge($return, $this->findMigrationDependencies($plugin_configuration['process']));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPluginDefinition() {
|
||||
$definition = [];
|
||||
// While normal plugins do not change their definitions on the fly, this
|
||||
// one does so accommodate for that.
|
||||
foreach (parent::getPluginDefinition() as $key => $value) {
|
||||
$definition[$key] = $this->$key ?? $value;
|
||||
}
|
||||
return $definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDestinationConfiguration() {
|
||||
return $this->destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSourceConfiguration() {
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDestinationIds() {
|
||||
return $this->destinationIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMigrationTags() {
|
||||
return $this->migration_tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isAuditable() {
|
||||
return (bool) $this->audit;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
/**
|
||||
* Provides functionality for migration derivers.
|
||||
*/
|
||||
trait MigrationDeriverTrait {
|
||||
|
||||
/**
|
||||
* Returns a fully initialized instance of a source plugin.
|
||||
*
|
||||
* @param string $source_plugin_id
|
||||
* The source plugin ID.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrateSourceInterface|\Drupal\migrate\Plugin\RequirementsInterface
|
||||
* The fully initialized source plugin.
|
||||
*/
|
||||
public static function getSourcePlugin($source_plugin_id) {
|
||||
$definition = [
|
||||
'source' => [
|
||||
'ignore_map' => TRUE,
|
||||
'plugin' => $source_plugin_id,
|
||||
],
|
||||
'destination' => [
|
||||
'plugin' => 'null',
|
||||
],
|
||||
'idMap' => [
|
||||
'plugin' => 'null',
|
||||
],
|
||||
];
|
||||
return \Drupal::service('plugin.manager.migration')->createStubMigration($definition)->getSourcePlugin();
|
||||
}
|
||||
|
||||
}
|
||||
314
web/core/modules/migrate/src/Plugin/MigrationInterface.php
Normal file
314
web/core/modules/migrate/src/Plugin/MigrationInterface.php
Normal file
@ -0,0 +1,314 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\DerivativeInspectionInterface;
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
|
||||
/**
|
||||
* Interface for migrations.
|
||||
*/
|
||||
interface MigrationInterface extends PluginInspectionInterface, DerivativeInspectionInterface {
|
||||
|
||||
/**
|
||||
* The migration is currently not running.
|
||||
*/
|
||||
const STATUS_IDLE = 0;
|
||||
|
||||
/**
|
||||
* The migration is currently importing.
|
||||
*/
|
||||
const STATUS_IMPORTING = 1;
|
||||
|
||||
/**
|
||||
* The migration is currently being rolled back.
|
||||
*/
|
||||
const STATUS_ROLLING_BACK = 2;
|
||||
|
||||
/**
|
||||
* The migration is being stopped.
|
||||
*/
|
||||
const STATUS_STOPPING = 3;
|
||||
|
||||
/**
|
||||
* The migration has been disabled.
|
||||
*/
|
||||
const STATUS_DISABLED = 4;
|
||||
|
||||
/**
|
||||
* Migration error.
|
||||
*/
|
||||
const MESSAGE_ERROR = 1;
|
||||
|
||||
/**
|
||||
* Migration warning.
|
||||
*/
|
||||
const MESSAGE_WARNING = 2;
|
||||
|
||||
/**
|
||||
* Migration notice.
|
||||
*/
|
||||
const MESSAGE_NOTICE = 3;
|
||||
|
||||
/**
|
||||
* Migration info.
|
||||
*/
|
||||
const MESSAGE_INFORMATIONAL = 4;
|
||||
|
||||
/**
|
||||
* All records have been processed.
|
||||
*/
|
||||
const RESULT_COMPLETED = 1;
|
||||
|
||||
/**
|
||||
* The process has stopped itself (e.g., the memory limit is approaching).
|
||||
*/
|
||||
const RESULT_INCOMPLETE = 2;
|
||||
|
||||
/**
|
||||
* The process was stopped externally (e.g., via drush migrate-stop).
|
||||
*/
|
||||
const RESULT_STOPPED = 3;
|
||||
|
||||
/**
|
||||
* The process had a fatal error.
|
||||
*/
|
||||
const RESULT_FAILED = 4;
|
||||
|
||||
/**
|
||||
* Dependencies are unfulfilled - skip the process.
|
||||
*/
|
||||
const RESULT_SKIPPED = 5;
|
||||
|
||||
/**
|
||||
* This migration is disabled, skipping.
|
||||
*/
|
||||
const RESULT_DISABLED = 6;
|
||||
|
||||
/**
|
||||
* An alias for getPluginId() for backwards compatibility reasons.
|
||||
*
|
||||
* @return string
|
||||
* The plugin ID of the plugin instance.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrationInterface::getPluginId()
|
||||
*/
|
||||
public function id();
|
||||
|
||||
/**
|
||||
* Get the plugin label.
|
||||
*
|
||||
* @return string
|
||||
* The label for this migration.
|
||||
*/
|
||||
public function label();
|
||||
|
||||
/**
|
||||
* Get a list of required plugin IDs.
|
||||
*
|
||||
* @return string[]
|
||||
* An array of required plugin IDs.
|
||||
*/
|
||||
public function getRequirements(): array;
|
||||
|
||||
/**
|
||||
* Returns the initialized source plugin.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
* The source plugin.
|
||||
*/
|
||||
public function getSourcePlugin();
|
||||
|
||||
/**
|
||||
* Returns the process plugins.
|
||||
*
|
||||
* @param array|null $process
|
||||
* (Optional) A process configuration array. Defaults to NULL. If specified,
|
||||
* then the plugins from the given process array are returned. If not
|
||||
* specified, then the plugins from this migration's process array are
|
||||
* returned.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrateProcessInterface[][]
|
||||
* An associative array. The keys are the destination property names. Values
|
||||
* are process pipelines. Each pipeline contains an array of plugins.
|
||||
*/
|
||||
public function getProcessPlugins(?array $process = NULL);
|
||||
|
||||
/**
|
||||
* Returns the initialized destination plugin.
|
||||
*
|
||||
* @param bool $stub_being_requested
|
||||
* TRUE to indicate that this destination will be asked to construct a stub.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrateDestinationInterface
|
||||
* The destination plugin.
|
||||
*/
|
||||
public function getDestinationPlugin($stub_being_requested = FALSE);
|
||||
|
||||
/**
|
||||
* Returns the initialized id_map plugin.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrateIdMapInterface
|
||||
* The ID map.
|
||||
*/
|
||||
public function getIdMap();
|
||||
|
||||
/**
|
||||
* Check if all source rows from this migration have been processed.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if this migration is complete otherwise FALSE.
|
||||
*/
|
||||
public function allRowsProcessed();
|
||||
|
||||
/**
|
||||
* Set the current migration status.
|
||||
*
|
||||
* @param int $status
|
||||
* One of the STATUS_* constants.
|
||||
*/
|
||||
public function setStatus($status);
|
||||
|
||||
/**
|
||||
* Get the current migration status.
|
||||
*
|
||||
* @return int
|
||||
* The current migration status. Defaults to STATUS_IDLE.
|
||||
*/
|
||||
public function getStatus();
|
||||
|
||||
/**
|
||||
* Retrieve a label for the current status.
|
||||
*
|
||||
* @return string
|
||||
* User-friendly string corresponding to a STATUS_ constant.
|
||||
*/
|
||||
public function getStatusLabel();
|
||||
|
||||
/**
|
||||
* Get the result to return upon interruption.
|
||||
*
|
||||
* @return int
|
||||
* The current interruption result. Defaults to RESULT_INCOMPLETE.
|
||||
*/
|
||||
public function getInterruptionResult();
|
||||
|
||||
/**
|
||||
* Clears the result to return upon interruption.
|
||||
*/
|
||||
public function clearInterruptionResult();
|
||||
|
||||
/**
|
||||
* Sets the migration status as interrupted with a given result code.
|
||||
*
|
||||
* @param int $result
|
||||
* One of the MigrationInterface::RESULT_* constants.
|
||||
*/
|
||||
public function interruptMigration($result);
|
||||
|
||||
/**
|
||||
* Gets the normalized process plugin configuration.
|
||||
*
|
||||
* The process configuration is always normalized. All shorthand processing
|
||||
* will be expanded into their full representations.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2129651#get-shorthand
|
||||
*
|
||||
* @return array
|
||||
* The normalized configuration describing the process plugins.
|
||||
*/
|
||||
public function getProcess();
|
||||
|
||||
/**
|
||||
* Allows you to override the entire process configuration.
|
||||
*
|
||||
* @param array $process
|
||||
* The entire process pipeline configuration describing the process plugins.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setProcess(array $process);
|
||||
|
||||
/**
|
||||
* Set the process pipeline configuration for an individual destination field.
|
||||
*
|
||||
* This method allows you to set the process pipeline configuration for a
|
||||
* single property within the full process pipeline configuration.
|
||||
*
|
||||
* @param string $property
|
||||
* The property of which to set the process pipeline configuration.
|
||||
* @param mixed $process_of_property
|
||||
* The process pipeline configuration to be set for this property.
|
||||
*
|
||||
* @return $this
|
||||
* The migration entity.
|
||||
*/
|
||||
public function setProcessOfProperty($property, $process_of_property);
|
||||
|
||||
/**
|
||||
* Merge the process pipeline configuration for a single property.
|
||||
*
|
||||
* @param string $property
|
||||
* The property of which to merge the passed in process pipeline
|
||||
* configuration.
|
||||
* @param array $process_of_property
|
||||
* The process pipeline configuration to be merged with the existing process
|
||||
* pipeline configuration.
|
||||
*
|
||||
* @return $this
|
||||
* The migration entity.
|
||||
*/
|
||||
public function mergeProcessOfProperty($property, array $process_of_property);
|
||||
|
||||
/**
|
||||
* Get the dependencies for this migration.
|
||||
*
|
||||
* @return array
|
||||
* The dependencies for this migrations.
|
||||
*/
|
||||
public function getMigrationDependencies();
|
||||
|
||||
/**
|
||||
* Get the destination configuration, with at least a 'plugin' key.
|
||||
*
|
||||
* @return array
|
||||
* The destination configuration.
|
||||
*/
|
||||
public function getDestinationConfiguration();
|
||||
|
||||
/**
|
||||
* Get the source configuration, with at least a 'plugin' key.
|
||||
*
|
||||
* @return array
|
||||
* The source configuration.
|
||||
*/
|
||||
public function getSourceConfiguration();
|
||||
|
||||
/**
|
||||
* The destination identifiers.
|
||||
*
|
||||
* An array of destination identifiers: the keys are the name of the
|
||||
* properties, the values are dependent on the ID map plugin.
|
||||
*
|
||||
* @return array
|
||||
* Destination identifiers.
|
||||
*/
|
||||
public function getDestinationIds();
|
||||
|
||||
/**
|
||||
* The migration tags.
|
||||
*
|
||||
* @return array
|
||||
* Migration tags.
|
||||
*/
|
||||
public function getMigrationTags();
|
||||
|
||||
/**
|
||||
* Indicates if the migration is auditable.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the migration is auditable, FALSE otherwise.
|
||||
*/
|
||||
public function isAuditable();
|
||||
|
||||
}
|
||||
265
web/core/modules/migrate/src/Plugin/MigrationPluginManager.php
Normal file
265
web/core/modules/migrate/src/Plugin/MigrationPluginManager.php
Normal file
@ -0,0 +1,265 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Graph\Graph;
|
||||
use Drupal\Component\Plugin\PluginBase;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
|
||||
use Drupal\migrate\Plugin\Discovery\ProviderFilterDecorator;
|
||||
use Drupal\Core\Plugin\Discovery\YamlDirectoryDiscovery;
|
||||
use Drupal\Core\Plugin\Factory\ContainerFactory;
|
||||
use Drupal\migrate\MigrateBuildDependencyInterface;
|
||||
|
||||
/**
|
||||
* Plugin manager for migration plugins.
|
||||
*/
|
||||
class MigrationPluginManager extends DefaultPluginManager implements MigrationPluginManagerInterface, MigrateBuildDependencyInterface {
|
||||
|
||||
/**
|
||||
* Provides default values for migrations.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaults = [
|
||||
'class' => '\Drupal\migrate\Plugin\Migration',
|
||||
];
|
||||
|
||||
/**
|
||||
* The interface the plugins should implement.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $pluginInterface = 'Drupal\migrate\Plugin\MigrationInterface';
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* Construct a migration plugin manager.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* The cache backend for the definitions.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
*/
|
||||
public function __construct(ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager) {
|
||||
$this->factory = new ContainerFactory($this, $this->pluginInterface);
|
||||
$this->alterInfo('migration_plugins');
|
||||
$this->setCacheBackend($cache_backend, 'migration_plugins');
|
||||
$this->moduleHandler = $module_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin discovery.
|
||||
*
|
||||
* This method overrides DefaultPluginManager::getDiscovery() in order to
|
||||
* search for migration configurations in the MODULENAME/migrations
|
||||
* directory.
|
||||
*/
|
||||
protected function getDiscovery() {
|
||||
if (!isset($this->discovery)) {
|
||||
$directories = array_map(function ($directory) {
|
||||
return [$directory . '/migrations'];
|
||||
}, $this->moduleHandler->getModuleDirectories());
|
||||
|
||||
$yaml_discovery = new YamlDirectoryDiscovery($directories, 'migrate');
|
||||
// This gets rid of migrations which try to use a non-existent source
|
||||
// plugin. The common case for this is if the source plugin has, or
|
||||
// specifies, a non-existent provider.
|
||||
$only_with_source_discovery = new NoSourcePluginDecorator($yaml_discovery);
|
||||
// This gets rid of migrations with explicit providers set if one of the
|
||||
// providers do not exist before we try to use a potentially non-existing
|
||||
// deriver. This is a rare case.
|
||||
$filtered_discovery = new ProviderFilterDecorator($only_with_source_discovery, [$this->moduleHandler, 'moduleExists']);
|
||||
$this->discovery = new ContainerDerivativeDiscoveryDecorator($filtered_discovery);
|
||||
}
|
||||
return $this->discovery;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createInstance($plugin_id, array $configuration = []) {
|
||||
$instances = $this->createInstances([$plugin_id], [$plugin_id => $configuration]);
|
||||
return reset($instances);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createInstances($migration_id, array $configuration = []) {
|
||||
if (empty($migration_id)) {
|
||||
$migration_id = array_keys($this->getDefinitions());
|
||||
}
|
||||
|
||||
$factory = $this->getFactory();
|
||||
$migration_ids = (array) $migration_id;
|
||||
|
||||
// We need to expand any derivative migrations. Derivative migrations are
|
||||
// calculated by migration derivers such as D6NodeDeriver. This allows
|
||||
// migrations to depend on the base id and then have a dependency on all
|
||||
// derivative migrations. For example, d6_comment depends on d6_node but
|
||||
// after we've expanded the dependencies it will depend on d6_node:page,
|
||||
// d6_node:story and so on, for other derivative migrations.
|
||||
$plugin_ids = $this->expandPluginIds($migration_ids);
|
||||
|
||||
$instances = [];
|
||||
foreach ($plugin_ids as $plugin_id) {
|
||||
$instances[$plugin_id] = $factory->createInstance($plugin_id, $configuration[$plugin_id] ?? []);
|
||||
}
|
||||
|
||||
// @todo Remove loop when the ability to call ::getMigrationDependencies()
|
||||
// without expanding plugins is removed.
|
||||
foreach ($instances as $migration) {
|
||||
$migration->set('migration_dependencies', $migration->getMigrationDependencies());
|
||||
}
|
||||
|
||||
// Sort the migrations based on their dependencies.
|
||||
return $this->buildDependencyMigration($instances, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createInstancesByTag($tag) {
|
||||
$migrations = array_filter($this->getDefinitions(), function ($migration) use ($tag) {
|
||||
return !empty($migration['migration_tags']) && in_array($tag, $migration['migration_tags']);
|
||||
});
|
||||
return $migrations ? $this->createInstances(array_keys($migrations)) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function expandPluginIds(array $migration_ids) {
|
||||
$plugin_ids = [];
|
||||
$all_ids = array_keys($this->getDefinitions());
|
||||
foreach ($migration_ids as $id) {
|
||||
$plugin_ids = array_merge($plugin_ids, preg_grep('/^' . preg_quote($id, '/') . PluginBase::DERIVATIVE_SEPARATOR . '/', $all_ids));
|
||||
if ($this->hasDefinition($id)) {
|
||||
$plugin_ids[] = $id;
|
||||
}
|
||||
}
|
||||
return $plugin_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildDependencyMigration(array $migrations, array $dynamic_ids) {
|
||||
// Migration dependencies can be optional or required. If an optional
|
||||
// dependency does not run, the current migration is still OK to go. Both
|
||||
// optional and required dependencies (if run at all) must run before the
|
||||
// current migration.
|
||||
$dependency_graph = [];
|
||||
$required_dependency_graph = [];
|
||||
$have_optional = FALSE;
|
||||
foreach ($migrations as $migration) {
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
|
||||
$id = $migration->id();
|
||||
$requirements[$id] = [];
|
||||
$dependency_graph[$id]['edges'] = [];
|
||||
$migration_dependencies = $migration->getMigrationDependencies();
|
||||
|
||||
if (isset($migration_dependencies['required'])) {
|
||||
foreach ($migration_dependencies['required'] as $dependency) {
|
||||
if (!isset($dynamic_ids[$dependency])) {
|
||||
$this->addDependency($required_dependency_graph, $id, $dependency, $dynamic_ids);
|
||||
}
|
||||
$this->addDependency($dependency_graph, $id, $dependency, $dynamic_ids);
|
||||
}
|
||||
}
|
||||
if (!empty($migration_dependencies['optional'])) {
|
||||
foreach ($migration_dependencies['optional'] as $dependency) {
|
||||
$this->addDependency($dependency_graph, $id, $dependency, $dynamic_ids);
|
||||
}
|
||||
$have_optional = TRUE;
|
||||
}
|
||||
}
|
||||
$dependency_graph = (new Graph($dependency_graph))->searchAndSort();
|
||||
if ($have_optional) {
|
||||
$required_dependency_graph = (new Graph($required_dependency_graph))->searchAndSort();
|
||||
}
|
||||
else {
|
||||
$required_dependency_graph = $dependency_graph;
|
||||
}
|
||||
$weights = [];
|
||||
foreach ($migrations as $migration_id => $migration) {
|
||||
// Populate a weights array to use with array_multisort() later.
|
||||
$weights[] = $dependency_graph[$migration_id]['weight'];
|
||||
if (!empty($required_dependency_graph[$migration_id]['paths'])) {
|
||||
$migration->set('requirements', $required_dependency_graph[$migration_id]['paths']);
|
||||
}
|
||||
}
|
||||
// Sort weights, labels, and keys in the same order as each other.
|
||||
array_multisort(
|
||||
// Use the numerical weight as the primary sort.
|
||||
$weights, SORT_DESC, SORT_NUMERIC,
|
||||
// When migrations have the same weight, sort them alphabetically by ID.
|
||||
array_keys($migrations), SORT_ASC, SORT_NATURAL,
|
||||
$migrations
|
||||
);
|
||||
|
||||
return $migrations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one or more dependencies to a graph.
|
||||
*
|
||||
* @param array $graph
|
||||
* The graph so far, passed by reference.
|
||||
* @param int $id
|
||||
* The migration ID.
|
||||
* @param string $dependency
|
||||
* The dependency string.
|
||||
* @param array $dynamic_ids
|
||||
* The dynamic ID mapping.
|
||||
*/
|
||||
protected function addDependency(array &$graph, $id, $dependency, $dynamic_ids) {
|
||||
$dependencies = $dynamic_ids[$dependency] ?? [$dependency];
|
||||
if (!isset($graph[$id]['edges'])) {
|
||||
$graph[$id]['edges'] = [];
|
||||
}
|
||||
$graph[$id]['edges'] += array_combine($dependencies, $dependencies);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createStubMigration(array $definition) {
|
||||
$id = $definition['id'] ?? uniqid();
|
||||
return Migration::create(\Drupal::getContainer(), [], $id, $definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds plugin definitions.
|
||||
*
|
||||
* @return array
|
||||
* List of definitions to store in cache.
|
||||
*
|
||||
* @todo This is a temporary solution to the fact that migration source
|
||||
* plugins have more than one provider. This functionality will be moved to
|
||||
* core in https://www.drupal.org/node/2786355.
|
||||
*/
|
||||
protected function findDefinitions() {
|
||||
$definitions = $this->getDiscovery()->getDefinitions();
|
||||
foreach ($definitions as $plugin_id => &$definition) {
|
||||
$this->processDefinition($definition, $plugin_id);
|
||||
}
|
||||
$this->alterDefinitions($definitions);
|
||||
return ProviderFilterDecorator::filterDefinitions($definitions, function ($provider) {
|
||||
return $this->providerExists($provider);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginManagerInterface;
|
||||
|
||||
/**
|
||||
* Migration plugin manager interface.
|
||||
*/
|
||||
interface MigrationPluginManagerInterface extends PluginManagerInterface {
|
||||
|
||||
/**
|
||||
* Create pre-configured instance of plugin derivatives.
|
||||
*
|
||||
* @param array $id
|
||||
* Either the plugin ID or the base plugin ID of the plugins being
|
||||
* instantiated. Also accepts an array of plugin IDs and an empty array to
|
||||
* load all plugins.
|
||||
* @param array $configuration
|
||||
* An array of configuration relevant to the plugin instances. Keyed by the
|
||||
* plugin ID.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrationInterface[]
|
||||
* Fully configured plugin instances.
|
||||
*
|
||||
* @throws \Drupal\Component\Plugin\Exception\PluginException
|
||||
* If an instance cannot be created, such as if the ID is invalid.
|
||||
*/
|
||||
public function createInstances($id, array $configuration = []);
|
||||
|
||||
/**
|
||||
* Expand derivative migration dependencies.
|
||||
*
|
||||
* @param string[] $migration_ids
|
||||
* A list of plugin IDs.
|
||||
*
|
||||
* @return array
|
||||
* An array of expanded plugin ids.
|
||||
*/
|
||||
public function expandPluginIds(array $migration_ids);
|
||||
|
||||
/**
|
||||
* Creates a stub migration plugin from a definition array.
|
||||
*
|
||||
* @param array $definition
|
||||
* The migration definition. If an 'id' key is set then this will be used as
|
||||
* the migration ID, if not a random ID will be assigned.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\Migration
|
||||
* The stub migration.
|
||||
*/
|
||||
public function createStubMigration(array $definition);
|
||||
|
||||
/**
|
||||
* Create migrations given a tag.
|
||||
*
|
||||
* @param string $tag
|
||||
* A migration tag we want to filter by.
|
||||
*
|
||||
* @return array|\Drupal\migrate\Plugin\MigrationInterface[]
|
||||
* An array of migration objects with the given tag, or an empty array if no
|
||||
* migrations with that tag exist.
|
||||
*/
|
||||
public function createInstancesByTag($tag);
|
||||
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
|
||||
use Drupal\Component\Plugin\Discovery\DiscoveryTrait;
|
||||
|
||||
/**
|
||||
* Remove definitions which refer to a non-existing source plugin.
|
||||
*/
|
||||
class NoSourcePluginDecorator implements DiscoveryInterface {
|
||||
|
||||
use DiscoveryTrait;
|
||||
|
||||
/**
|
||||
* The Discovery object being decorated.
|
||||
*
|
||||
* @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface
|
||||
*/
|
||||
protected $decorated;
|
||||
|
||||
/**
|
||||
* Constructs a NoSourcePluginDecorator object.
|
||||
*
|
||||
* @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $decorated
|
||||
* The object implementing DiscoveryInterface that is being decorated.
|
||||
*/
|
||||
public function __construct(DiscoveryInterface $decorated) {
|
||||
$this->decorated = $decorated;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinitions() {
|
||||
/** @var \Drupal\Component\Plugin\PluginManagerInterface $source_plugin_manager */
|
||||
$source_plugin_manager = \Drupal::service('plugin.manager.migrate.source');
|
||||
return array_filter($this->decorated->getDefinitions(), function (array $definition) use ($source_plugin_manager) {
|
||||
return !empty($definition['source']['plugin']) && $source_plugin_manager->hasDefinition($definition['source']['plugin']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes through all unknown calls onto the decorated object.
|
||||
*
|
||||
* @param string $method
|
||||
* The method to call on the decorated object.
|
||||
* @param array $args
|
||||
* Call arguments.
|
||||
*
|
||||
* @return mixed
|
||||
* The return value from the method on the decorated object.
|
||||
*/
|
||||
public function __call($method, array $args) {
|
||||
return call_user_func_array([$this->decorated, $method], $args);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\migrate\Event\ImportAwareInterface;
|
||||
use Drupal\migrate\Event\MigrateEvents;
|
||||
use Drupal\migrate\Event\MigrateImportEvent;
|
||||
use Drupal\migrate\Event\MigrateRollbackEvent;
|
||||
use Drupal\migrate\Event\RollbackAwareInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Event subscriber to forward Migrate events to source and destination plugins.
|
||||
*/
|
||||
class PluginEventSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* Tries to invoke event handling methods on source and destination plugins.
|
||||
*
|
||||
* @param string $method
|
||||
* The method to invoke.
|
||||
* @param \Drupal\migrate\Event\MigrateImportEvent|\Drupal\migrate\Event\MigrateRollbackEvent $event
|
||||
* The event that has triggered the invocation.
|
||||
* @param string $plugin_interface
|
||||
* The interface which plugins must implement in order to be invoked.
|
||||
*/
|
||||
protected function invoke($method, $event, $plugin_interface) {
|
||||
$migration = $event->getMigration();
|
||||
|
||||
$source = $migration->getSourcePlugin();
|
||||
if ($source instanceof $plugin_interface) {
|
||||
call_user_func([$source, $method], $event);
|
||||
}
|
||||
|
||||
$destination = $migration->getDestinationPlugin();
|
||||
if ($destination instanceof $plugin_interface) {
|
||||
call_user_func([$destination, $method], $event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards pre-import events to the source and destination plugins.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateImportEvent $event
|
||||
* The import event.
|
||||
*/
|
||||
public function preImport(MigrateImportEvent $event) {
|
||||
$this->invoke('preImport', $event, ImportAwareInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards post-import events to the source and destination plugins.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateImportEvent $event
|
||||
* The import event.
|
||||
*/
|
||||
public function postImport(MigrateImportEvent $event) {
|
||||
$this->invoke('postImport', $event, ImportAwareInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards pre-rollback events to the source and destination plugins.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateRollbackEvent $event
|
||||
* The rollback event.
|
||||
*/
|
||||
public function preRollback(MigrateRollbackEvent $event) {
|
||||
$this->invoke('preRollback', $event, RollbackAwareInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards post-rollback events to the source and destination plugins.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateRollbackEvent $event
|
||||
* The rollback event.
|
||||
*/
|
||||
public function postRollback(MigrateRollbackEvent $event) {
|
||||
$this->invoke('postRollback', $event, RollbackAwareInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents(): array {
|
||||
$events = [];
|
||||
$events[MigrateEvents::PRE_IMPORT][] = ['preImport'];
|
||||
$events[MigrateEvents::POST_IMPORT][] = ['postImport'];
|
||||
$events[MigrateEvents::PRE_ROLLBACK][] = ['preRollback'];
|
||||
$events[MigrateEvents::POST_ROLLBACK][] = ['postRollback'];
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
/**
|
||||
* An interface to check for a migrate plugin requirements.
|
||||
*/
|
||||
interface RequirementsInterface {
|
||||
|
||||
/**
|
||||
* Checks if requirements for this plugin are OK.
|
||||
*
|
||||
* @throws \Drupal\migrate\Exception\RequirementsException
|
||||
* Thrown when requirements are not met.
|
||||
*/
|
||||
public function checkRequirements();
|
||||
|
||||
}
|
||||
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a destination plugin for migrating entity display components.
|
||||
*
|
||||
* Display modes provide different presentations for viewing ('view modes') or
|
||||
* editing ('form modes') content. This destination plugin is an abstract base
|
||||
* class for migrating fields and other components into view and form modes.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\destination\PerComponentEntityDisplay
|
||||
* @see \Drupal\migrate\Plugin\migrate\destination\PerComponentEntityFormDisplay
|
||||
*/
|
||||
abstract class ComponentEntityDisplayBase extends DestinationBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
const MODE_NAME = '';
|
||||
|
||||
/**
|
||||
* The entity display repository.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
|
||||
*/
|
||||
protected $entityDisplayRepository;
|
||||
|
||||
/**
|
||||
* PerComponentEntityDisplay constructor.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration.
|
||||
* @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
|
||||
* The entity display repository service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityDisplayRepositoryInterface $entity_display_repository) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
|
||||
$this->entityDisplayRepository = $entity_display_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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_display.repository')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = []) {
|
||||
$values = [];
|
||||
// array_intersect_key() won't work because the order is important because
|
||||
// this is also the return value.
|
||||
foreach (array_keys($this->getIds()) as $id) {
|
||||
$values[$id] = $row->getDestinationProperty($id);
|
||||
}
|
||||
$entity = $this->getEntity($values['entity_type'], $values['bundle'], $values[static::MODE_NAME]);
|
||||
if (!$row->getDestinationProperty('hidden')) {
|
||||
$entity->setComponent($values['field_name'], $row->getDestinationProperty('options') ?: []);
|
||||
}
|
||||
else {
|
||||
$entity->removeComponent($values['field_name']);
|
||||
}
|
||||
$entity->save();
|
||||
return array_values($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['entity_type']['type'] = 'string';
|
||||
$ids['bundle']['type'] = 'string';
|
||||
$ids[static::MODE_NAME]['type'] = 'string';
|
||||
$ids['field_name']['type'] = 'string';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
// This is intentionally left empty.
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The entity type to retrieve.
|
||||
* @param string $bundle
|
||||
* The entity bundle.
|
||||
* @param string $mode
|
||||
* The display mode.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\Display\EntityDisplayInterface
|
||||
* The entity display object.
|
||||
*/
|
||||
abstract protected function getEntity($entity_type, $bundle, $mode);
|
||||
|
||||
}
|
||||
@ -0,0 +1,241 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Component\Plugin\DependentPluginInterface;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Config\TypedConfigManagerInterface;
|
||||
use Drupal\Core\Entity\DependencyTrait;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\Attribute\MigrateDestination;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides Configuration Management destination plugin.
|
||||
*
|
||||
* Persists data to the config system.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - store null: (optional) Boolean, if TRUE, when a property is NULL, NULL is
|
||||
* stored, otherwise the default is used. Defaults to FALSE.
|
||||
* - translations: (optional) Boolean, if TRUE, the destination will be
|
||||
* associated with the langcode provided by the source plugin. Defaults to
|
||||
* FALSE.
|
||||
*
|
||||
* Destination properties expected in the imported row:
|
||||
* - config_name: The machine name of the config.
|
||||
* - langcode: (optional) The language code of the config.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* plugin: variable
|
||||
* variables:
|
||||
* - node_admin_theme
|
||||
* process:
|
||||
* use_admin_theme: node_admin_theme
|
||||
* destination:
|
||||
* plugin: config
|
||||
* config_name: node.settings
|
||||
* @endcode
|
||||
*
|
||||
* This will add the value of the variable "node_admin_theme" to the config with
|
||||
* the machine name "node.settings" as "node.settings.use_admin_theme".
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* plugin: d6_variable_translation
|
||||
* variables:
|
||||
* - site_offline_message
|
||||
* process:
|
||||
* langcode: language
|
||||
* message: site_offline_message
|
||||
* destination:
|
||||
* plugin: config
|
||||
* config_name: system.maintenance
|
||||
* translations: true
|
||||
* @endcode
|
||||
*
|
||||
* This will add the value of the variable "site_offline_message" to the config
|
||||
* with the machine name "system.maintenance" as "system.maintenance.message",
|
||||
* coupled with the relevant langcode as obtained from the
|
||||
* "d6_variable_translation" source plugin.
|
||||
*
|
||||
* @see \Drupal\migrate_drupal\Plugin\migrate\source\d6\VariableTranslation
|
||||
*/
|
||||
#[MigrateDestination('config')]
|
||||
class Config extends DestinationBase implements ContainerFactoryPluginInterface, DependentPluginInterface {
|
||||
|
||||
use DependencyTrait;
|
||||
|
||||
/**
|
||||
* The config object.
|
||||
*
|
||||
* @var \Drupal\Core\Config\Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
// phpcs:ignore Drupal.NamingConventions.ValidVariableName.LowerCamelName, Drupal.Commenting.VariableComment.Missing
|
||||
protected $language_manager;
|
||||
|
||||
/**
|
||||
* Constructs a Config destination object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration entity.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The configuration factory.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
|
||||
* The typed config manager.
|
||||
*/
|
||||
public function __construct(
|
||||
array $configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
MigrationInterface $migration,
|
||||
ConfigFactoryInterface $config_factory,
|
||||
LanguageManagerInterface $language_manager,
|
||||
protected TypedConfigManagerInterface $typedConfigManager,
|
||||
) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
|
||||
$this->config = $config_factory->getEditable($configuration['config_name']);
|
||||
$this->language_manager = $language_manager;
|
||||
if ($this->isTranslationDestination()) {
|
||||
$this->supportsRollback = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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('config.factory'),
|
||||
$container->get('language_manager'),
|
||||
$container->get(TypedConfigManagerInterface::class)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = []) {
|
||||
if ($this->isTranslationDestination()) {
|
||||
$this->config = $this->language_manager->getLanguageConfigOverride($row->getDestinationProperty('langcode'), $this->config->getName());
|
||||
}
|
||||
|
||||
foreach ($row->getRawDestination() as $key => $value) {
|
||||
if (isset($value) || !empty($this->configuration['store null'])) {
|
||||
$this->config->set(str_replace(Row::PROPERTY_SEPARATOR, '.', $key), $value);
|
||||
}
|
||||
}
|
||||
|
||||
$name = $this->config->getName();
|
||||
// Ensure that translatable config has `langcode` specified.
|
||||
// @see \Drupal\Core\Config\Plugin\Validation\Constraint\LangcodeRequiredIfTranslatableValuesConstraint
|
||||
if ($this->typedConfigManager->hasConfigSchema($name)
|
||||
&& $this->typedConfigManager->createFromNameAndData($name, $this->config->getRawData())->hasTranslatableElements()
|
||||
&& !$this->config->get('langcode')
|
||||
) {
|
||||
$this->config->set('langcode', $this->language_manager->getDefaultLanguage()->getId());
|
||||
}
|
||||
$this->config->save();
|
||||
$ids[] = $this->config->getName();
|
||||
if ($this->isTranslationDestination()) {
|
||||
$ids[] = $row->getDestinationProperty('langcode');
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
// @todo Dynamically fetch fields using Config Schema API.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['config_name']['type'] = 'string';
|
||||
if ($this->isTranslationDestination()) {
|
||||
$ids['langcode']['type'] = 'string';
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
$provider = explode('.', $this->config->getName(), 2)[0];
|
||||
$this->addDependency('module', $provider);
|
||||
return $this->dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether this destination is for translations.
|
||||
*
|
||||
* @return bool
|
||||
* Whether this destination is for translations.
|
||||
*/
|
||||
protected function isTranslationDestination() {
|
||||
return !empty($this->configuration['translations']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollback(array $destination_identifier) {
|
||||
if ($this->isTranslationDestination()) {
|
||||
$language = $destination_identifier['langcode'];
|
||||
$config = $this->language_manager->getLanguageConfigOverride($language, $this->config->getName());
|
||||
$config->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDestinationModule() {
|
||||
if (!empty($this->configuration['destination_module'])) {
|
||||
return $this->configuration['destination_module'];
|
||||
}
|
||||
if (!empty($this->pluginDefinition['destination_module'])) {
|
||||
return $this->pluginDefinition['destination_module'];
|
||||
}
|
||||
// Config translations require the config_translation module so set the
|
||||
// migration provider to 'config_translation'. The corresponding non
|
||||
// translated configuration is expected to be handled in a separate
|
||||
// migration.
|
||||
if (isset($this->configuration['translations'])) {
|
||||
return 'config_translation';
|
||||
}
|
||||
// Get the module handling this configuration object from the config_name,
|
||||
// which is of the form <module_name>.<configuration object name>
|
||||
return !empty($this->configuration['config_name']) ? explode('.', $this->configuration['config_name'], 2)[0] : NULL;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Exception\RequirementsException;
|
||||
use Drupal\migrate\Plugin\MigrateDestinationInterface;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Plugin\RequirementsInterface;
|
||||
|
||||
// cspell:ignore sourceid
|
||||
|
||||
/**
|
||||
* Base class for migrate destination classes.
|
||||
*
|
||||
* Migrate destination plugins perform the import operation of the migration.
|
||||
* Destination plugins extend this abstract base class. A destination plugin
|
||||
* must implement at least fields(), getIds() and import() methods. Destination
|
||||
* plugins can also support rollback operations. For more
|
||||
* information, refer to \Drupal\migrate\Plugin\MigrateDestinationInterface.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateDestinationPluginManager
|
||||
* @see \Drupal\migrate\Attribute\MigrateDestination
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
abstract class DestinationBase extends PluginBase implements MigrateDestinationInterface, RequirementsInterface {
|
||||
|
||||
/**
|
||||
* Indicates whether the destination can be rolled back.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $supportsRollback = FALSE;
|
||||
|
||||
/**
|
||||
* The rollback action to be saved for the last imported item.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $rollbackAction = MigrateIdMapInterface::ROLLBACK_DELETE;
|
||||
|
||||
/**
|
||||
* The migration.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationInterface
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* Constructs an entity destination plugin.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->migration = $migration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollbackAction() {
|
||||
return $this->rollbackAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkRequirements() {
|
||||
if (empty($this->pluginDefinition['requirements_met'])) {
|
||||
throw new RequirementsException(sprintf("Destination plugin '%s' did not meet the requirements", $this->pluginId));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollback(array $destination_identifier) {
|
||||
// By default we do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function supportsRollback() {
|
||||
return $this->supportsRollback;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a destination item being updated, set the appropriate rollback action.
|
||||
*
|
||||
* @param array $id_map
|
||||
* The map row data for the item.
|
||||
* @param int $update_action
|
||||
* The rollback action to take if we are updating an existing item.
|
||||
*/
|
||||
protected function setRollbackAction(array $id_map, $update_action = MigrateIdMapInterface::ROLLBACK_PRESERVE) {
|
||||
// If the entity we're updating was previously migrated by us, preserve the
|
||||
// existing rollback action.
|
||||
if (isset($id_map['sourceid1'])) {
|
||||
$this->rollbackAction = $id_map['rollback_action'];
|
||||
}
|
||||
// Otherwise, we're updating an entity which already existed on the
|
||||
// destination and want to make sure we do not delete it on rollback.
|
||||
else {
|
||||
$this->rollbackAction = $update_action;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDestinationModule() {
|
||||
if (!empty($this->configuration['destination_module'])) {
|
||||
return $this->configuration['destination_module'];
|
||||
}
|
||||
if (!empty($this->pluginDefinition['destination_module'])) {
|
||||
return $this->pluginDefinition['destination_module'];
|
||||
}
|
||||
if (is_string($this->migration->provider)) {
|
||||
return $this->migration->provider;
|
||||
}
|
||||
else {
|
||||
return reset($this->migration->provider);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,268 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Component\Plugin\DependentPluginInterface;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\DependencyTrait;
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\Attribute\MigrateDestination;
|
||||
use Drupal\migrate\EntityFieldDefinitionTrait;
|
||||
use Drupal\migrate\Plugin\Derivative\MigrateEntity;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
// cspell:ignore tnid
|
||||
|
||||
/**
|
||||
* Provides a generic destination to import entities.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - default_bundle: (optional) The bundle to use for this row if 'bundle' is
|
||||
* not defined on the row. Setting this also allows the fields() method to
|
||||
* return bundle fields as well as base fields.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* plugin: d7_node
|
||||
* process:
|
||||
* nid: tnid
|
||||
* vid: vid
|
||||
* langcode: language
|
||||
* title: title
|
||||
* ...
|
||||
* revision_timestamp: timestamp
|
||||
* destination:
|
||||
* plugin: entity:node
|
||||
* @endcode
|
||||
*
|
||||
* This will save the processed, migrated row as a node.
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* plugin: d7_node
|
||||
* process:
|
||||
* nid: tnid
|
||||
* vid: vid
|
||||
* langcode: language
|
||||
* title: title
|
||||
* ...
|
||||
* revision_timestamp: timestamp
|
||||
* destination:
|
||||
* plugin: entity:node
|
||||
* default_bundle: custom
|
||||
* @endcode
|
||||
*
|
||||
* This will save the processed, migrated row as a node of type 'custom'.
|
||||
*/
|
||||
#[MigrateDestination(
|
||||
id: 'entity',
|
||||
deriver: MigrateEntity::class
|
||||
)]
|
||||
abstract class Entity extends DestinationBase implements ContainerFactoryPluginInterface, DependentPluginInterface {
|
||||
|
||||
use DependencyTrait;
|
||||
use EntityFieldDefinitionTrait;
|
||||
|
||||
/**
|
||||
* The entity storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* The entity field manager.
|
||||
*/
|
||||
protected EntityFieldManagerInterface $entityFieldManager;
|
||||
|
||||
/**
|
||||
* The list of the bundles of this entity type.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $bundles;
|
||||
|
||||
/**
|
||||
* Construct a new entity.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration.
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
|
||||
* The storage for this entity type.
|
||||
* @param array $bundles
|
||||
* The list of bundles this entity type has.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles) {
|
||||
$plugin_definition += [
|
||||
'label' => $storage->getEntityType()->getPluralLabel(),
|
||||
];
|
||||
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
|
||||
$this->storage = $storage;
|
||||
$this->bundles = $bundles;
|
||||
$this->supportsRollback = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, ?MigrationInterface $migration = NULL) {
|
||||
$entity_type_id = static::getEntityTypeId($plugin_id);
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('entity_type.manager')->getStorage($entity_type_id),
|
||||
array_keys($container->get('entity_type.bundle.info')->getBundleInfo($entity_type_id))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the bundle for the row taking into account the default.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The current row we're importing.
|
||||
*
|
||||
* @return string
|
||||
* The bundle for this row.
|
||||
*/
|
||||
public function getBundle(Row $row) {
|
||||
$default_bundle = $this->configuration['default_bundle'] ?? '';
|
||||
$bundle_key = $this->getKey('bundle');
|
||||
return $row->getDestinationProperty($bundle_key) ?: $default_bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or loads an entity.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row object.
|
||||
* @param array $old_destination_id_values
|
||||
* The old destination IDs.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
* The entity we are importing into.
|
||||
*/
|
||||
protected function getEntity(Row $row, array $old_destination_id_values) {
|
||||
$entity_id = reset($old_destination_id_values) ?: $this->getEntityId($row);
|
||||
if (!empty($entity_id) && ($entity = $this->storage->load($entity_id))) {
|
||||
// Allow updateEntity() to change the entity.
|
||||
$entity = $this->updateEntity($entity, $row) ?: $entity;
|
||||
}
|
||||
else {
|
||||
// Attempt to ensure we always have a bundle.
|
||||
if ($bundle = $this->getBundle($row)) {
|
||||
$row->setDestinationProperty($this->getKey('bundle'), $bundle);
|
||||
}
|
||||
|
||||
// Stubs might need some required fields filled in.
|
||||
if ($row->isStub()) {
|
||||
$this->processStubRow($row);
|
||||
}
|
||||
$entity = $this->storage->create($row->getDestination());
|
||||
$entity->enforceIsNew();
|
||||
}
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an entity with the new values from row.
|
||||
*
|
||||
* This method should be implemented in extending classes.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to update.
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row object to update from.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
* An updated entity from row values.
|
||||
*
|
||||
* @throws \LogicException
|
||||
* Thrown for config entities, if the destination is for translations and
|
||||
* either the "property" or "translation" property does not exist.
|
||||
*/
|
||||
abstract protected function updateEntity(EntityInterface $entity, Row $row);
|
||||
|
||||
/**
|
||||
* Populates as much of the stub row as possible.
|
||||
*
|
||||
* This method can be implemented in extending classes when needed.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row of data.
|
||||
*/
|
||||
protected function processStubRow(Row $row) {}
|
||||
|
||||
/**
|
||||
* Gets the entity ID of the row.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row of data.
|
||||
*
|
||||
* @return string
|
||||
* The entity ID for the row that we are importing.
|
||||
*/
|
||||
protected function getEntityId(Row $row) {
|
||||
return $row->getDestinationProperty($this->getKey('id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific entity key.
|
||||
*
|
||||
* @param string $key
|
||||
* The name of the entity key to return.
|
||||
*
|
||||
* @return string|bool
|
||||
* The entity key, or FALSE if it does not exist.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityTypeInterface::getKeys()
|
||||
*/
|
||||
protected function getKey($key) {
|
||||
return $this->storage->getEntityType()->getKey($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollback(array $destination_identifier) {
|
||||
// Delete the specified entity from Drupal if it exists.
|
||||
$entity = $this->storage->load(reset($destination_identifier));
|
||||
if ($entity) {
|
||||
if ($entity instanceof ContentEntityInterface) {
|
||||
$entity->setSyncing(TRUE);
|
||||
}
|
||||
$entity->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
$this->addDependency('module', $this->storage->getEntityType()->getProvider());
|
||||
return $this->dependencies;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\migrate\Attribute\MigrateDestination;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Provides entity base field override destination plugin.
|
||||
*
|
||||
* Base fields are non-configurable fields that always exist on a given entity
|
||||
* type, like the 'title', 'created' and 'sticky' fields of the 'node' entity
|
||||
* type. Some entity types can have bundles, for example the node content types.
|
||||
* The base fields exist on all bundles but the bundles can override the
|
||||
* definitions. For example, the label for node 'title' base field can be
|
||||
* different on different content types.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* The example below migrates the node 'sticky' settings for each content type.
|
||||
* @code
|
||||
* id: d6_node_setting_sticky
|
||||
* label: Node type 'sticky' setting
|
||||
* migration_tags:
|
||||
* - Drupal 6
|
||||
* source:
|
||||
* plugin: d6_node_type
|
||||
* constants:
|
||||
* entity_type: node
|
||||
* field_name: sticky
|
||||
* process:
|
||||
* entity_type: 'constants/entity_type'
|
||||
* bundle: type
|
||||
* field_name: 'constants/field_name'
|
||||
* label:
|
||||
* plugin: default_value
|
||||
* default_value: 'Sticky at the top of lists'
|
||||
* 'default_value/0/value': 'options/sticky'
|
||||
* destination:
|
||||
* plugin: entity:base_field_override
|
||||
* migration_dependencies:
|
||||
* required:
|
||||
* - d6_node_type
|
||||
* @endcode
|
||||
*/
|
||||
#[MigrateDestination('entity:base_field_override')]
|
||||
class EntityBaseFieldOverride extends EntityConfigBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEntityId(Row $row) {
|
||||
$entity_type = $row->getDestinationProperty('entity_type');
|
||||
$bundle = $row->getDestinationProperty('bundle');
|
||||
$field_name = $row->getDestinationProperty('field_name');
|
||||
return "$entity_type.$bundle.$field_name";
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,307 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\language\ConfigurableLanguageManager;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Base destination class for importing configuration entities.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - translations: (optional) Boolean, if TRUE, the destination will be
|
||||
* associated with the langcode provided by the source plugin. Defaults to
|
||||
* FALSE.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* plugin: d7_block_custom
|
||||
* process:
|
||||
* id: bid
|
||||
* info: info
|
||||
* langcode: language
|
||||
* body: body
|
||||
* destination:
|
||||
* plugin: entity:block
|
||||
* @endcode
|
||||
*
|
||||
* This will save the migrated, processed row as a block config entity.
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* plugin: d6_profile_field_translation
|
||||
* constants:
|
||||
* entity_type: user
|
||||
* bundle: user
|
||||
* process:
|
||||
* langcode: language
|
||||
* entity_type: 'constants/entity_type'
|
||||
* bundle: 'constants/bundle'
|
||||
* field_name: name
|
||||
* ...
|
||||
* property: property
|
||||
* translation: translation
|
||||
* destination:
|
||||
* plugin: entity:field_config
|
||||
* translations: true
|
||||
* @endcode
|
||||
*
|
||||
* Because the translations configuration is set to "true", this will save the
|
||||
* migrated, processed row to a "field_config" entity associated with the
|
||||
* designated langcode. Note that the this makes the "translation" and
|
||||
* "property" properties required.
|
||||
*/
|
||||
class EntityConfigBase extends Entity {
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* The configuration factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* Construct a new entity.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration.
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
|
||||
* The storage for this entity type.
|
||||
* @param array $bundles
|
||||
* The list of bundles this entity type has.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The configuration factory.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, LanguageManagerInterface $language_manager, ConfigFactoryInterface $config_factory) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles);
|
||||
$this->languageManager = $language_manager;
|
||||
$this->configFactory = $config_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, ?MigrationInterface $migration = NULL) {
|
||||
$entity_type_id = static::getEntityTypeId($plugin_id);
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('entity_type.manager')->getStorage($entity_type_id),
|
||||
array_keys($container->get('entity_type.bundle.info')->getBundleInfo($entity_type_id)),
|
||||
$container->get('language_manager'),
|
||||
$container->get('config.factory')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = []) {
|
||||
if ($row->isStub()) {
|
||||
throw new MigrateException('Config entities can not be stubbed.');
|
||||
}
|
||||
$this->rollbackAction = MigrateIdMapInterface::ROLLBACK_DELETE;
|
||||
$ids = $this->getIds();
|
||||
$id_key = $this->getKey('id');
|
||||
if (count($ids) > 1) {
|
||||
// Ids is keyed by the key name so grab the keys.
|
||||
$id_keys = array_keys($ids);
|
||||
if (!$row->getDestinationProperty($id_key)) {
|
||||
// Set the ID into the destination in for form "val1.val2.val3".
|
||||
$row->setDestinationProperty($id_key, $this->generateId($row, $id_keys));
|
||||
}
|
||||
}
|
||||
$entity = $this->getEntity($row, $old_destination_id_values);
|
||||
// Translations are already saved in updateEntity by configuration override.
|
||||
if (!$this->isTranslationDestination()) {
|
||||
$entity->save();
|
||||
}
|
||||
if (count($ids) > 1) {
|
||||
// This can only be a config entity, content entities have their ID key
|
||||
// and that's it.
|
||||
$return = [];
|
||||
foreach ($id_keys as $id_key) {
|
||||
if (($this->isTranslationDestination()) && ($id_key == 'langcode')) {
|
||||
// Config entities do not have a language property, get the language
|
||||
// code from the destination.
|
||||
$return[] = $row->getDestinationProperty($id_key);
|
||||
}
|
||||
else {
|
||||
$return[] = $entity->get($id_key);
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
return [$entity->id()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether this destination is for translations.
|
||||
*
|
||||
* @return bool
|
||||
* Whether this destination is for translations.
|
||||
*/
|
||||
protected function isTranslationDestination() {
|
||||
return !empty($this->configuration['translations']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$id_key = $this->getKey('id');
|
||||
$ids[$id_key]['type'] = 'string';
|
||||
if ($this->isTranslationDestination()) {
|
||||
$ids['langcode']['type'] = 'string';
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an entity with the contents of a row.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to update.
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row object to update from.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
* An updated entity from row values.
|
||||
*
|
||||
* @throws \LogicException
|
||||
* Thrown if the destination is for translations and either the "property"
|
||||
* or "translation" property does not exist.
|
||||
*/
|
||||
protected function updateEntity(EntityInterface $entity, Row $row) {
|
||||
// This is a translation if the language in the active config does not
|
||||
// match the language of this row.
|
||||
$translation = FALSE;
|
||||
if ($this->isTranslationDestination() && $row->hasDestinationProperty('langcode') && $this->languageManager instanceof ConfigurableLanguageManager) {
|
||||
$config = $entity->getConfigDependencyName();
|
||||
$langcode = $this->configFactory->get('langcode');
|
||||
if ($langcode != $row->getDestinationProperty('langcode')) {
|
||||
$translation = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if ($translation) {
|
||||
if (!$row->hasDestinationProperty('property')) {
|
||||
throw new \LogicException('The "property" property is required');
|
||||
}
|
||||
if (!$row->hasDestinationProperty('translation')) {
|
||||
throw new \LogicException('The "translation" property is required');
|
||||
}
|
||||
$config_override = $this->languageManager->getLanguageConfigOverride($row->getDestinationProperty('langcode'), $config);
|
||||
$config_override->set(str_replace(Row::PROPERTY_SEPARATOR, '.', $row->getDestinationProperty('property')), $row->getDestinationProperty('translation'));
|
||||
$config_override->save();
|
||||
}
|
||||
else {
|
||||
foreach ($row->getRawDestination() as $property => $value) {
|
||||
$this->updateEntityProperty($entity, explode(Row::PROPERTY_SEPARATOR, $property), $value);
|
||||
}
|
||||
$this->setRollbackAction($row->getIdMap());
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a (possible nested) entity property with a value.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The config entity.
|
||||
* @param array $parents
|
||||
* The array of parents.
|
||||
* @param string|object $value
|
||||
* The value to update to.
|
||||
*/
|
||||
protected function updateEntityProperty(EntityInterface $entity, array $parents, $value) {
|
||||
$top_key = array_shift($parents);
|
||||
$entity_value = $entity->get($top_key);
|
||||
if (is_array($entity_value)) {
|
||||
NestedArray::setValue($entity_value, $parents, $value);
|
||||
}
|
||||
else {
|
||||
$entity_value = $value;
|
||||
}
|
||||
$entity->set($top_key, $entity_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an entity ID.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The current row.
|
||||
* @param array $ids
|
||||
* The destination IDs.
|
||||
*
|
||||
* @return string
|
||||
* The generated entity ID.
|
||||
*/
|
||||
protected function generateId(Row $row, array $ids) {
|
||||
$id_values = [];
|
||||
foreach ($ids as $id) {
|
||||
if ($this->isTranslationDestination() && $id == 'langcode') {
|
||||
continue;
|
||||
}
|
||||
$id_values[] = $row->getDestinationProperty($id);
|
||||
}
|
||||
return implode('.', $id_values);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollback(array $destination_identifier) {
|
||||
if ($this->isTranslationDestination()) {
|
||||
// The entity id does not include the langcode.
|
||||
$id_values = [];
|
||||
foreach ($destination_identifier as $key => $value) {
|
||||
if ($this->isTranslationDestination() && $key === 'langcode') {
|
||||
continue;
|
||||
}
|
||||
$id_values[] = $value;
|
||||
}
|
||||
$entity_id = implode('.', $id_values);
|
||||
$language = $destination_identifier['langcode'];
|
||||
|
||||
$config = $this->storage->load($entity_id)->getConfigDependencyName();
|
||||
$config_override = $this->languageManager->getLanguageConfigOverride($language, $config);
|
||||
// Rollback the translation.
|
||||
$config_override->delete();
|
||||
}
|
||||
else {
|
||||
$destination_identifier = implode('.', $destination_identifier);
|
||||
parent::rollback([$destination_identifier]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,464 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\Core\Field\FieldTypePluginManagerInterface;
|
||||
use Drupal\Core\Session\AccountSwitcherInterface;
|
||||
use Drupal\Core\TypedData\TranslatableInterface;
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
use Drupal\migrate\Audit\HighestIdInterface;
|
||||
use Drupal\migrate\Exception\EntityValidationException;
|
||||
use Drupal\migrate\Plugin\MigrateValidatableEntityInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\user\EntityOwnerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
// cspell:ignore huhuu maailma otsikko sivun validatable
|
||||
|
||||
/**
|
||||
* Provides destination class for all content entities lacking a specific class.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - translations: (optional) A boolean that indicates if the entity is
|
||||
* translatable. When TRUE, migration rows will be considered as translations.
|
||||
* This means the migration will attempt to load an existing entity and, if
|
||||
* found, save the row data into it as a new translation rather than creating
|
||||
* a new entity. For this functionality, the migration process definition must
|
||||
* include mappings for the entity ID and the entity language field. If this
|
||||
* property is TRUE, the migration will also have an additional destination ID
|
||||
* for the language code.
|
||||
* - overwrite_properties: (optional) A list of properties that will be
|
||||
* overwritten if an entity with the same ID already exists. Any properties
|
||||
* that are not listed will not be overwritten.
|
||||
* - validate: (optional) Boolean, indicates whether an entity should be
|
||||
* validated, defaults to FALSE.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* The example below will create a 'node' entity of content type 'article'.
|
||||
*
|
||||
* The language of the source will be used because the configuration
|
||||
* 'translations: true' was set. Without this configuration option the site's
|
||||
* default language would be used.
|
||||
*
|
||||
* The example content type has fields 'title', 'body' and 'field_example'.
|
||||
* The text format of the body field is defaulted to 'basic_html'. The example
|
||||
* uses the EmbeddedDataSource source plugin for the sake of simplicity.
|
||||
*
|
||||
* If the migration is executed again in an update mode, any updates done in the
|
||||
* destination Drupal site to the 'title' and 'body' fields would be overwritten
|
||||
* with the original source values. Updates done to 'field_example' would be
|
||||
* preserved because 'field_example' is not included in 'overwrite_properties'
|
||||
* configuration.
|
||||
* @code
|
||||
* id: custom_article_migration
|
||||
* label: Custom article migration
|
||||
* source:
|
||||
* plugin: embedded_data
|
||||
* data_rows:
|
||||
* -
|
||||
* id: 1
|
||||
* langcode: 'fi'
|
||||
* title: 'Sivun otsikko'
|
||||
* field_example: 'Huhuu'
|
||||
* content: '<p>Hoi maailma</p>'
|
||||
* ids:
|
||||
* id:
|
||||
* type: integer
|
||||
* process:
|
||||
* nid: id
|
||||
* langcode: langcode
|
||||
* title: title
|
||||
* field_example: field_example
|
||||
* 'body/0/value': content
|
||||
* 'body/0/format':
|
||||
* plugin: default_value
|
||||
* default_value: basic_html
|
||||
* destination:
|
||||
* plugin: entity:node
|
||||
* default_bundle: article
|
||||
* translations: true
|
||||
* overwrite_properties:
|
||||
* - title
|
||||
* - body
|
||||
* # Run entity and fields validation before saving an entity.
|
||||
* # @see \Drupal\Core\Entity\FieldableEntityInterface::validate()
|
||||
* validate: true
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\destination\Entity
|
||||
* @see \Drupal\migrate\Plugin\migrate\destination\EntityRevision
|
||||
*/
|
||||
class EntityContentBase extends Entity implements HighestIdInterface, MigrateValidatableEntityInterface {
|
||||
|
||||
/**
|
||||
* Field type plugin manager.
|
||||
*
|
||||
* @var \Drupal\Core\Field\FieldTypePluginManagerInterface
|
||||
*/
|
||||
protected $fieldTypeManager;
|
||||
|
||||
/**
|
||||
* The account switcher service.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountSwitcherInterface
|
||||
*/
|
||||
protected $accountSwitcher;
|
||||
|
||||
/**
|
||||
* Entity type bundle info.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
|
||||
*/
|
||||
protected EntityTypeBundleInfoInterface $entityTypeBundleInfo;
|
||||
|
||||
/**
|
||||
* Constructs a content entity.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration entity.
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
|
||||
* The storage for this entity type.
|
||||
* @param array $bundles
|
||||
* The list of bundles this entity type has.
|
||||
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
|
||||
* The entity field manager.
|
||||
* @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
|
||||
* The field type plugin manager service.
|
||||
* @param \Drupal\Core\Session\AccountSwitcherInterface $account_switcher
|
||||
* The account switcher service.
|
||||
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface|null $entity_type_bundle_info
|
||||
* The entity type bundle info service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityFieldManagerInterface $entity_field_manager, FieldTypePluginManagerInterface $field_type_manager, ?AccountSwitcherInterface $account_switcher = NULL, ?EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles);
|
||||
$this->entityFieldManager = $entity_field_manager;
|
||||
$this->fieldTypeManager = $field_type_manager;
|
||||
$this->accountSwitcher = $account_switcher;
|
||||
if ($entity_type_bundle_info === NULL) {
|
||||
@trigger_error('Calling ' . __NAMESPACE__ . '\EntityContentBase::__construct() without the $entity_type_bundle_info argument is deprecated in drupal:11.2.0 and will be required in drupal:12.0.0. See https://www.drupal.org/node/3476634', E_USER_DEPRECATED);
|
||||
$entity_type_bundle_info = \Drupal::service('entity_type.bundle.info');
|
||||
}
|
||||
$this->entityTypeBundleInfo = $entity_type_bundle_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, ?MigrationInterface $migration = NULL) {
|
||||
$entity_type = static::getEntityTypeId($plugin_id);
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('entity_type.manager')->getStorage($entity_type),
|
||||
array_keys($container->get('entity_type.bundle.info')->getBundleInfo($entity_type)),
|
||||
$container->get('entity_field.manager'),
|
||||
$container->get('plugin.manager.field.field_type'),
|
||||
$container->get('account_switcher'),
|
||||
$container->get('entity_type.bundle.info'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
* When an entity cannot be looked up.
|
||||
* @throws \Drupal\migrate\Exception\EntityValidationException
|
||||
* When an entity validation hasn't been passed.
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = []) {
|
||||
$this->rollbackAction = MigrateIdMapInterface::ROLLBACK_DELETE;
|
||||
$entity = $this->getEntity($row, $old_destination_id_values);
|
||||
if (!$entity) {
|
||||
throw new MigrateException('Unable to get entity');
|
||||
}
|
||||
assert($entity instanceof ContentEntityInterface);
|
||||
if ($this->isEntityValidationRequired($entity)) {
|
||||
$this->validateEntity($entity);
|
||||
}
|
||||
$ids = $this->save($entity, $old_destination_id_values);
|
||||
if ($this->isTranslationDestination()) {
|
||||
$ids[] = $entity->language()->getId();
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isEntityValidationRequired(FieldableEntityInterface $entity) {
|
||||
// Prioritize the entity method over migration config because it won't be
|
||||
// possible to save that entity non validated.
|
||||
/* @see \Drupal\Core\Entity\ContentEntityBase::preSave() */
|
||||
return $entity->isValidationRequired() || !empty($this->configuration['validate']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateEntity(FieldableEntityInterface $entity) {
|
||||
// Entity validation can require the user that owns the entity. Switch to
|
||||
// use that user during validation.
|
||||
// As an example:
|
||||
// @see \Drupal\Core\Entity\Plugin\Validation\Constraint\ValidReferenceConstraint
|
||||
$account = $entity instanceof EntityOwnerInterface ? $entity->getOwner() : NULL;
|
||||
// Validate account exists as the owner reference could be invalid for any
|
||||
// number of reasons.
|
||||
if ($account) {
|
||||
$this->accountSwitcher->switchTo($account);
|
||||
}
|
||||
// This finally block ensures that the account is always switched back, even
|
||||
// if an exception was thrown. Any validation exceptions are intentionally
|
||||
// left unhandled. They should be caught and logged by a catch block
|
||||
// surrounding the row import and then added to the migration messages table
|
||||
// for the current row.
|
||||
try {
|
||||
$violations = $entity->validate();
|
||||
} finally {
|
||||
if ($account) {
|
||||
$this->accountSwitcher->switchBack();
|
||||
}
|
||||
}
|
||||
|
||||
if (count($violations) > 0) {
|
||||
throw new EntityValidationException($violations);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* The content entity.
|
||||
* @param array $old_destination_id_values
|
||||
* (optional) An array of destination ID values. Defaults to an empty array.
|
||||
*
|
||||
* @return array
|
||||
* An array containing the entity ID.
|
||||
*/
|
||||
protected function save(ContentEntityInterface $entity, array $old_destination_id_values = []) {
|
||||
$entity->setSyncing(TRUE);
|
||||
$entity->save();
|
||||
return [$entity->id()];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isTranslationDestination() {
|
||||
return !empty($this->configuration['translations']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids = [];
|
||||
|
||||
$id_key = $this->getKey('id');
|
||||
$ids[$id_key] = $this->getDefinitionFromEntity($id_key);
|
||||
|
||||
if ($this->isTranslationDestination()) {
|
||||
$langcode_key = $this->getKey('langcode');
|
||||
if (!$langcode_key) {
|
||||
throw new MigrateException(sprintf('The "%s" entity type does not support translations.', $this->storage->getEntityTypeId()));
|
||||
}
|
||||
$ids[$langcode_key] = $this->getDefinitionFromEntity($langcode_key);
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function updateEntity(EntityInterface $entity, Row $row) {
|
||||
$empty_destinations = $row->getEmptyDestinationProperties();
|
||||
// By default, an update will be preserved.
|
||||
$rollback_action = MigrateIdMapInterface::ROLLBACK_PRESERVE;
|
||||
|
||||
// Make sure we have the right translation.
|
||||
if ($this->isTranslationDestination()) {
|
||||
$property = $this->storage->getEntityType()->getKey('langcode');
|
||||
if ($row->hasDestinationProperty($property)) {
|
||||
$language = $row->getDestinationProperty($property);
|
||||
if (!$entity->hasTranslation($language)) {
|
||||
$entity->addTranslation($language);
|
||||
|
||||
// We're adding a translation, so delete it on rollback.
|
||||
$rollback_action = MigrateIdMapInterface::ROLLBACK_DELETE;
|
||||
}
|
||||
$entity = $entity->getTranslation($language);
|
||||
}
|
||||
}
|
||||
|
||||
// If the migration has specified a list of properties to be overwritten,
|
||||
// clone the row with an empty set of destination values, and re-add only
|
||||
// the specified properties.
|
||||
if (isset($this->configuration['overwrite_properties'])) {
|
||||
$empty_destinations = array_intersect($empty_destinations, $this->configuration['overwrite_properties']);
|
||||
$clone = $row->cloneWithoutDestination();
|
||||
foreach ($this->configuration['overwrite_properties'] as $property) {
|
||||
$clone->setDestinationProperty($property, $row->getDestinationProperty($property));
|
||||
}
|
||||
$row = $clone;
|
||||
}
|
||||
|
||||
foreach ($row->getDestination() as $field_name => $values) {
|
||||
$field = $entity->$field_name;
|
||||
if ($field instanceof TypedDataInterface) {
|
||||
$field->setValue($values);
|
||||
}
|
||||
}
|
||||
foreach ($empty_destinations as $field_name) {
|
||||
$entity->$field_name = NULL;
|
||||
}
|
||||
|
||||
$this->setRollbackAction($row->getIdMap(), $rollback_action);
|
||||
|
||||
// We might have a different (translated) entity, so return it.
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function processStubRow(Row $row) {
|
||||
$bundle_key = $this->getKey('bundle');
|
||||
if ($bundle_key && empty($row->getDestinationProperty($bundle_key))) {
|
||||
if (empty($this->bundles)) {
|
||||
throw new MigrateException('Stubbing failed, no bundles available for entity type: ' . $this->storage->getEntityTypeId());
|
||||
}
|
||||
$row->setDestinationProperty($bundle_key, reset($this->bundles));
|
||||
}
|
||||
|
||||
$bundle = $row->getDestinationProperty($bundle_key) ?? $this->storage->getEntityTypeId();
|
||||
// Populate any required fields not already populated.
|
||||
$fields = $this->entityFieldManager
|
||||
->getFieldDefinitions($this->storage->getEntityTypeId(), $bundle);
|
||||
foreach ($fields as $field_name => $field_definition) {
|
||||
if ($field_definition->isRequired() && is_null($row->getDestinationProperty($field_name))) {
|
||||
// Use the configured default value for this specific field, if any.
|
||||
if ($default_value = $field_definition->getDefaultValueLiteral()) {
|
||||
$values = $default_value;
|
||||
}
|
||||
else {
|
||||
/** @var \Drupal\Core\Field\FieldItemInterface $field_type_class */
|
||||
$field_type_class = $this->fieldTypeManager
|
||||
->getPluginClass($field_definition->getType());
|
||||
$values = $field_type_class::generateSampleValue($field_definition);
|
||||
if (is_null($values)) {
|
||||
// Handle failure to generate a sample value.
|
||||
throw new MigrateException('Stubbing failed, unable to generate value for field ' . $field_name);
|
||||
}
|
||||
}
|
||||
|
||||
$row->setDestinationProperty($field_name, $values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollback(array $destination_identifier) {
|
||||
if ($this->isTranslationDestination()) {
|
||||
// Attempt to remove the translation.
|
||||
$entity = $this->storage->load(reset($destination_identifier));
|
||||
if ($entity && $entity instanceof TranslatableInterface) {
|
||||
if ($key = $this->getKey('langcode')) {
|
||||
if (isset($destination_identifier[$key])) {
|
||||
$langcode = $destination_identifier[$key];
|
||||
if ($entity->hasTranslation($langcode)) {
|
||||
// Make sure we don't remove the default translation.
|
||||
$translation = $entity->getTranslation($langcode);
|
||||
if (!$translation->isDefaultTranslation()) {
|
||||
$entity->removeTranslation($langcode);
|
||||
$entity->setSyncing(TRUE);
|
||||
$entity->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
parent::rollback($destination_identifier);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHighestId() {
|
||||
$values = $this->storage->getQuery()
|
||||
->accessCheck(FALSE)
|
||||
->sort($this->getKey('id'), 'DESC')
|
||||
->range(0, 1)
|
||||
->execute();
|
||||
return (int) current($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields(): array {
|
||||
$entity_type = $this->storage->getEntityType();
|
||||
// Retrieving fields from a non-fieldable content entity will return a
|
||||
// LogicException. Return an empty list of fields instead.
|
||||
if (!$entity_type->entityClassImplements(FieldableEntityInterface::class)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Try to determine the bundle to use when getting fields.
|
||||
$bundle_info = $this->entityTypeBundleInfo->getBundleInfo($entity_type->id());
|
||||
|
||||
if (!empty($this->configuration['default_bundle'])) {
|
||||
// If the migration destination configuration specifies a default_bundle,
|
||||
// then use that.
|
||||
$bundle = $this->configuration['default_bundle'];
|
||||
|
||||
if (!isset($bundle_info[$bundle])) {
|
||||
throw new MigrateException(sprintf("The default_bundle value '%s' is not a valid bundle for the destination entity type '%s'.", $bundle, $entity_type->id()));
|
||||
}
|
||||
}
|
||||
elseif (count($bundle_info) === 1) {
|
||||
// If the destination entity type has only one bundle, use that.
|
||||
$bundle = array_key_first($bundle_info);
|
||||
}
|
||||
|
||||
if (isset($bundle)) {
|
||||
// If we have a bundle, get all the fields.
|
||||
$field_definitions = $this->entityFieldManager->getFieldDefinitions($entity_type->id(), $bundle);
|
||||
}
|
||||
else {
|
||||
// Without a bundle, we can only get the base fields.
|
||||
$field_definitions = $this->entityFieldManager->getBaseFieldDefinitions($entity_type->id());
|
||||
}
|
||||
|
||||
$fields = [];
|
||||
foreach ($field_definitions as $field_name => $definition) {
|
||||
$fields[$field_name] = (string) $definition->getLabel();
|
||||
}
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityChangedInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\migrate\Attribute\MigrateDestination;
|
||||
use Drupal\migrate\EntityFieldDefinitionTrait;
|
||||
use Drupal\migrate\Plugin\Derivative\MigrateEntityComplete;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Provides a destination for migrating the entire entity revision table.
|
||||
*/
|
||||
#[MigrateDestination(
|
||||
id: 'entity_complete',
|
||||
deriver: MigrateEntityComplete::class
|
||||
)]
|
||||
class EntityContentComplete extends EntityContentBase {
|
||||
|
||||
use EntityFieldDefinitionTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids = [];
|
||||
$id_key = $this->getKey('id');
|
||||
$ids[$id_key] = $this->getDefinitionFromEntity($id_key);
|
||||
|
||||
$revision_key = $this->getKey('revision');
|
||||
if ($revision_key) {
|
||||
$ids[$revision_key] = $this->getDefinitionFromEntity($revision_key);
|
||||
}
|
||||
|
||||
$langcode_key = $this->getKey('langcode');
|
||||
if ($langcode_key) {
|
||||
$ids[$langcode_key] = $this->getDefinitionFromEntity($langcode_key);
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row object.
|
||||
* @param array $old_destination_id_values
|
||||
* The old destination IDs.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
* The entity.
|
||||
*/
|
||||
protected function getEntity(Row $row, array $old_destination_id_values) {
|
||||
$revision_id = $old_destination_id_values
|
||||
? $old_destination_id_values[1]
|
||||
: $row->getDestinationProperty($this->getKey('revision'));
|
||||
// If we are re-running a migration with set revision IDs and the
|
||||
// destination revision ID already exists then do not create a new revision.
|
||||
$entity = NULL;
|
||||
if (!empty($revision_id)) {
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = $this->storage;
|
||||
if ($entity = $storage->loadRevision($revision_id)) {
|
||||
$entity->setNewRevision(FALSE);
|
||||
}
|
||||
}
|
||||
if ($entity === NULL && ($entity_id = $row->getDestinationProperty($this->getKey('id'))) && ($entity = $this->storage->load($entity_id))) {
|
||||
// We want to create a new entity. Set enforceIsNew() FALSE is necessary
|
||||
// to properly save a new entity while setting the ID. Without it, the
|
||||
// system would see that the ID is already set and assume it is an update.
|
||||
$entity->enforceIsNew(FALSE);
|
||||
// Intentionally create a new revision. Setting new revision TRUE here may
|
||||
// not be necessary, it is done for clarity.
|
||||
$entity->setNewRevision(TRUE);
|
||||
}
|
||||
if ($entity === NULL) {
|
||||
// Attempt to set the bundle.
|
||||
if ($bundle = $this->getBundle($row)) {
|
||||
$row->setDestinationProperty($this->getKey('bundle'), $bundle);
|
||||
}
|
||||
|
||||
// Stubs might need some required fields filled in.
|
||||
if ($row->isStub()) {
|
||||
$this->processStubRow($row);
|
||||
}
|
||||
$entity = $this->storage->create($row->getDestination());
|
||||
$entity->enforceIsNew();
|
||||
}
|
||||
|
||||
// We need to update the entity, so that the destination row IDs are
|
||||
// correct.
|
||||
$entity = $this->updateEntity($entity, $row);
|
||||
$entity->isDefaultRevision(TRUE);
|
||||
if ($entity instanceof EntityChangedInterface && $entity instanceof ContentEntityInterface) {
|
||||
// If we updated any untranslatable fields, update the timestamp for the
|
||||
// other translations.
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface|\Drupal\Core\Entity\EntityChangedInterface $entity */
|
||||
foreach ($entity->getTranslationLanguages() as $langcode => $language) {
|
||||
// If we updated an untranslated field, then set the changed time for
|
||||
// for all translations to match the current row that we are saving.
|
||||
// In this context, getChangedTime() should return the value we just
|
||||
// set in the updateEntity() call above.
|
||||
if ($entity->getTranslation($langcode)->hasTranslationChanges()) {
|
||||
$entity->getTranslation($langcode)->setChangedTime($entity->getChangedTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function updateEntity(EntityInterface $entity, Row $row) {
|
||||
$entity = parent::updateEntity($entity, $row);
|
||||
// Always set the rollback action to delete. This is because the parent
|
||||
// updateEntity will set the rollback action to preserve for the original
|
||||
// language row, which is needed for the classic node migrations.
|
||||
$this->setRollbackAction($row->getIdMap(), MigrateIdMapInterface::ROLLBACK_DELETE);
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function save(ContentEntityInterface $entity, array $old_destination_id_values = []) {
|
||||
parent::save($entity, $old_destination_id_values);
|
||||
return [
|
||||
$entity->id(),
|
||||
$entity->getRevisionId(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollback(array $destination_identifier) {
|
||||
// We want to delete the entity and all the translations so use
|
||||
// Entity:rollback because EntityContentBase::rollback will not remove the
|
||||
// default translation.
|
||||
Entity::rollback($destination_identifier);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\migrate\Attribute\MigrateDestination;
|
||||
|
||||
/**
|
||||
* Provides destination plugin for field_config configuration entities.
|
||||
*
|
||||
* The Field API defines two primary data structures, FieldStorage and Field.
|
||||
* A FieldStorage defines a particular type of data that can be attached to
|
||||
* entities as a Field instance.
|
||||
*
|
||||
* The example below adds an instance of 'field_text_example' to 'article'
|
||||
* bundle (node content type). The example uses the EmptySource source plugin
|
||||
* and constant source values for the sake of simplicity. For an example on how
|
||||
* the FieldStorage 'field_text_example' can be migrated, refer to
|
||||
* \Drupal\migrate\Plugin\migrate\destination\EntityFieldStorageConfig.
|
||||
* @code
|
||||
* id: field_instance_example
|
||||
* label: Field instance example
|
||||
* source:
|
||||
* plugin: empty
|
||||
* constants:
|
||||
* entity_type: node
|
||||
* field_name: field_text_example
|
||||
* bundle: article
|
||||
* label: Text field example
|
||||
* translatable: true
|
||||
* process:
|
||||
* entity_type: constants/entity_type
|
||||
* field_name: constants/field_name
|
||||
* bundle: constants/bundle
|
||||
* label: constants/label
|
||||
* translatable: constants/translatable
|
||||
* destination:
|
||||
* plugin: entity:field_config
|
||||
* migration_dependencies:
|
||||
* required:
|
||||
* - field_storage_example
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\field\Entity\FieldConfig
|
||||
* @see \Drupal\field\Entity\FieldConfigBase
|
||||
*/
|
||||
#[MigrateDestination('entity:field_config')]
|
||||
class EntityFieldInstance extends EntityConfigBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['entity_type']['type'] = 'string';
|
||||
$ids['bundle']['type'] = 'string';
|
||||
$ids['field_name']['type'] = 'string';
|
||||
if ($this->isTranslationDestination()) {
|
||||
$ids['langcode']['type'] = 'string';
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\migrate\Attribute\MigrateDestination;
|
||||
|
||||
/**
|
||||
* Provides destination plugin for field_storage_config configuration entities.
|
||||
*
|
||||
* The Field API defines two primary data structures, FieldStorage and Field.
|
||||
* A FieldStorage defines a particular type of data that can be attached to
|
||||
* entities as a Field instance.
|
||||
*
|
||||
* The example below creates a storage for a simple text field. The example uses
|
||||
* the EmptySource source plugin and constant source values for the sake of
|
||||
* simplicity.
|
||||
* @code
|
||||
* id: field_storage_example
|
||||
* label: Field storage example
|
||||
* source:
|
||||
* plugin: empty
|
||||
* constants:
|
||||
* entity_type: node
|
||||
* id: node.field_text_example
|
||||
* field_name: field_text_example
|
||||
* type: string
|
||||
* cardinality: 1
|
||||
* settings:
|
||||
* max_length: 10
|
||||
* langcode: en
|
||||
* translatable: true
|
||||
* process:
|
||||
* entity_type: constants/entity_type
|
||||
* id: constants/id
|
||||
* field_name: constants/field_name
|
||||
* type: constants/type
|
||||
* cardinality: constants/cardinality
|
||||
* settings: constants/settings
|
||||
* langcode: constants/langcode
|
||||
* translatable: constants/translatable
|
||||
* destination:
|
||||
* plugin: entity:field_storage_config
|
||||
* @endcode
|
||||
*
|
||||
* For a full list of the properties of a FieldStorage configuration entity,
|
||||
* refer to \Drupal\field\Entity\FieldStorageConfig.
|
||||
*
|
||||
* For an example on how to migrate a Field instance of this FieldStorage,
|
||||
* refer to \Drupal\migrate\Plugin\migrate\destination\EntityFieldInstance.
|
||||
*/
|
||||
#[MigrateDestination('entity:field_storage_config')]
|
||||
class EntityFieldStorageConfig extends EntityConfigBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['entity_type']['type'] = 'string';
|
||||
$ids['field_name']['type'] = 'string';
|
||||
// @todo Remove conditional. https://www.drupal.org/node/3004574
|
||||
if ($this->isTranslationDestination()) {
|
||||
$ids['langcode']['type'] = 'string';
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollback(array $destination_identifier) {
|
||||
if ($this->isTranslationDestination()) {
|
||||
$language = $destination_identifier['langcode'];
|
||||
unset($destination_identifier['langcode']);
|
||||
$destination_identifier = [
|
||||
implode('.', $destination_identifier),
|
||||
'langcode' => $language,
|
||||
];
|
||||
}
|
||||
else {
|
||||
$destination_identifier = [implode('.', $destination_identifier)];
|
||||
}
|
||||
parent::rollback($destination_identifier);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
|
||||
use Drupal\Core\Field\FieldTypePluginManagerInterface;
|
||||
use Drupal\Core\Session\AccountSwitcherInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\migrate\Attribute\MigrateDestination;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\Plugin\Derivative\MigrateEntityRevision;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Provides entity revision destination plugin.
|
||||
*
|
||||
* Refer to the parent class for configuration keys:
|
||||
* \Drupal\migrate\Plugin\migrate\destination\EntityContentBase
|
||||
*
|
||||
* Entity revisions can only be migrated after the entity to which the revisions
|
||||
* belong has been migrated. For example, revisions of a given content type can
|
||||
* be migrated only after the nodes of that content type have been migrated.
|
||||
*
|
||||
* In order to avoid revision ID conflicts, make sure that the entity migration
|
||||
* also includes the revision ID. If the entity migration did not include the
|
||||
* revision ID, the entity would get the next available revision ID (1 when
|
||||
* migrating to a clean database). Then, when revisions are migrated after the
|
||||
* entities, the revision IDs would almost certainly collide.
|
||||
*
|
||||
* The examples below contain simple node and node revision migrations. The
|
||||
* examples use the EmbeddedDataSource source plugin for the sake of
|
||||
* simplicity. The important part of both examples is the 'vid' property, which
|
||||
* is the revision ID for nodes.
|
||||
*
|
||||
* Example of 'article' node migration, which must be executed before the
|
||||
* 'article' revisions.
|
||||
* @code
|
||||
* id: custom_article_migration
|
||||
* label: 'Custom article migration'
|
||||
* source:
|
||||
* plugin: embedded_data
|
||||
* data_rows:
|
||||
* -
|
||||
* nid: 1
|
||||
* vid: 2
|
||||
* revision_timestamp: 1514661000
|
||||
* revision_log: 'Second revision'
|
||||
* title: 'Current title'
|
||||
* content: '<p>Current content</p>'
|
||||
* ids:
|
||||
* nid:
|
||||
* type: integer
|
||||
* process:
|
||||
* nid: nid
|
||||
* vid: vid
|
||||
* revision_timestamp: revision_timestamp
|
||||
* revision_log: revision_log
|
||||
* title: title
|
||||
* 'body/0/value': content
|
||||
* 'body/0/format':
|
||||
* plugin: default_value
|
||||
* default_value: basic_html
|
||||
* destination:
|
||||
* plugin: entity:node
|
||||
* default_bundle: article
|
||||
* @endcode
|
||||
*
|
||||
* Example of the corresponding node revision migration, which must be executed
|
||||
* after the above migration.
|
||||
* @code
|
||||
* id: custom_article_revision_migration
|
||||
* label: 'Custom article revision migration'
|
||||
* source:
|
||||
* plugin: embedded_data
|
||||
* data_rows:
|
||||
* -
|
||||
* nid: 1
|
||||
* vid: 1
|
||||
* revision_timestamp: 1514660000
|
||||
* revision_log: 'First revision'
|
||||
* title: 'Previous title'
|
||||
* content: '<p>Previous content</p>'
|
||||
* ids:
|
||||
* nid:
|
||||
* type: integer
|
||||
* process:
|
||||
* nid:
|
||||
* plugin: migration_lookup
|
||||
* migration: custom_article_migration
|
||||
* source: nid
|
||||
* vid: vid
|
||||
* revision_timestamp: revision_timestamp
|
||||
* revision_log: revision_log
|
||||
* title: title
|
||||
* 'body/0/value': content
|
||||
* 'body/0/format':
|
||||
* plugin: default_value
|
||||
* default_value: basic_html
|
||||
* destination:
|
||||
* plugin: entity_revision:node
|
||||
* default_bundle: article
|
||||
* migration_dependencies:
|
||||
* required:
|
||||
* - custom_article_migration
|
||||
* @endcode
|
||||
*/
|
||||
#[MigrateDestination(
|
||||
id: 'entity_revision',
|
||||
deriver: MigrateEntityRevision::class
|
||||
)]
|
||||
class EntityRevision extends EntityContentBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityFieldManagerInterface $entity_field_manager, FieldTypePluginManagerInterface $field_type_manager, AccountSwitcherInterface $account_switcher, ?EntityTypeBundleInfoInterface $entity_type_bundle_info = NULL) {
|
||||
$plugin_definition += [
|
||||
'label' => new TranslatableMarkup('@entity_type revisions', ['@entity_type' => $storage->getEntityType()->getSingularLabel()]),
|
||||
];
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_field_manager, $field_type_manager, $account_switcher, $entity_type_bundle_info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row object.
|
||||
* @param array $old_destination_id_values
|
||||
* The old destination IDs.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|false
|
||||
* The entity or false if it can not be created.
|
||||
*/
|
||||
protected function getEntity(Row $row, array $old_destination_id_values) {
|
||||
$revision_id = $old_destination_id_values ?
|
||||
reset($old_destination_id_values) :
|
||||
$row->getDestinationProperty($this->getKey('revision'));
|
||||
$entity = NULL;
|
||||
if (!empty($revision_id)) {
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = $this->storage;
|
||||
if ($entity = $storage->loadRevision($revision_id)) {
|
||||
$entity->setNewRevision(FALSE);
|
||||
}
|
||||
}
|
||||
if ($entity === NULL) {
|
||||
// If the entity could not be loaded by revision then the given
|
||||
// revision does not yet exist. Load the current default revision and
|
||||
// prepare to save it as a new non-default revision. setNewRevision()
|
||||
// will unset the current revision ID and the entity is then updated
|
||||
// with the source revision ID and saved as that.
|
||||
$entity_id = $row->getDestinationProperty($this->getKey('id'));
|
||||
$entity = $this->storage->load($entity_id);
|
||||
|
||||
// If we fail to load the original entity something is wrong and we need
|
||||
// to return immediately.
|
||||
if (!$entity) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$entity->enforceIsNew(FALSE);
|
||||
$entity->setNewRevision(TRUE);
|
||||
$entity->isDefaultRevision(FALSE);
|
||||
}
|
||||
// We need to update the entity, so that the destination row IDs are
|
||||
// correct.
|
||||
$entity = $this->updateEntity($entity, $row);
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function save(ContentEntityInterface $entity, array $old_destination_id_values = []) {
|
||||
$entity->setSyncing(TRUE);
|
||||
$entity->save();
|
||||
return [$entity->getRevisionId()];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids = [];
|
||||
|
||||
$revision_key = $this->getKey('revision');
|
||||
if (!$revision_key) {
|
||||
throw new MigrateException(sprintf('The "%s" entity type does not support revisions.', $this->storage->getEntityTypeId()));
|
||||
}
|
||||
$ids[$revision_key] = $this->getDefinitionFromEntity($revision_key);
|
||||
|
||||
if ($this->isTranslationDestination()) {
|
||||
$langcode_key = $this->getKey('langcode');
|
||||
if (!$langcode_key) {
|
||||
throw new MigrateException(sprintf('The "%s" entity type does not support translations.', $this->storage->getEntityTypeId()));
|
||||
}
|
||||
$ids[$langcode_key] = $this->getDefinitionFromEntity($langcode_key);
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHighestId() {
|
||||
$values = $this->storage->getQuery()
|
||||
->accessCheck(FALSE)
|
||||
->allRevisions()
|
||||
->sort($this->getKey('revision'), 'DESC')
|
||||
->range(0, 1)
|
||||
->execute();
|
||||
// The array keys are the revision IDs.
|
||||
// The array contains only one entry, so we can use key().
|
||||
return (int) key($values);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\migrate\Attribute\MigrateDestination;
|
||||
|
||||
/**
|
||||
* Provides entity view mode destination plugin.
|
||||
*
|
||||
* See EntityConfigBase for the available configuration options.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\destination\EntityConfigBase
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* plugin: d7_view_mode
|
||||
* process:
|
||||
* mode: view_mode
|
||||
* label: view_mode
|
||||
* targetEntityType: entity_type
|
||||
* destination:
|
||||
* plugin: entity:entity_view_mode
|
||||
* @endcode
|
||||
*
|
||||
* This will add the results of the process ("mode", "label" and
|
||||
* "targetEntityType") to an "entity_view_mode" entity.
|
||||
*/
|
||||
#[MigrateDestination('entity:entity_view_mode')]
|
||||
class EntityViewMode extends EntityConfigBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['targetEntityType']['type'] = 'string';
|
||||
$ids['mode']['type'] = 'string';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollback(array $destination_identifier) {
|
||||
$destination_identifier = implode('.', $destination_identifier);
|
||||
parent::rollback([$destination_identifier]);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\migrate\Attribute\MigrateDestination;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Provides null destination plugin.
|
||||
*/
|
||||
#[MigrateDestination(
|
||||
id: 'null',
|
||||
requirements_met: FALSE
|
||||
)]
|
||||
class NullDestination extends DestinationBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = []) {
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\migrate\Attribute\MigrateDestination;
|
||||
|
||||
/**
|
||||
* This class imports one component of an entity display.
|
||||
*
|
||||
* Destination properties expected in the imported row:
|
||||
* - entity_type: The entity type ID.
|
||||
* - bundle: The entity bundle.
|
||||
* - view_mode: The machine name of the view mode.
|
||||
* - field_name: The machine name of the field to be imported into the display.
|
||||
* - options: (optional) An array of options for displaying the field in this
|
||||
* view mode.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* constants:
|
||||
* entity_type: user
|
||||
* bundle: user
|
||||
* view_mode: default
|
||||
* field_name: user_picture
|
||||
* type: image
|
||||
* options:
|
||||
* label: hidden
|
||||
* settings:
|
||||
* image_style: ''
|
||||
* image_link: content
|
||||
* process:
|
||||
* entity_type: 'constants/entity_type'
|
||||
* bundle: 'constants/bundle'
|
||||
* view_mode: 'constants/view_mode'
|
||||
* field_name: 'constants/field_name'
|
||||
* type: 'constants/type'
|
||||
* options: 'constants/options'
|
||||
* 'options/type': '@type'
|
||||
* destination:
|
||||
* plugin: component_entity_display
|
||||
* @endcode
|
||||
*
|
||||
* This will add the "user_picture" image field to the "default" view mode of
|
||||
* the "user" bundle of the "user" entity type with options as defined by the
|
||||
* "options" constant, for example the label will be hidden.
|
||||
*/
|
||||
#[MigrateDestination('component_entity_display')]
|
||||
class PerComponentEntityDisplay extends ComponentEntityDisplayBase {
|
||||
|
||||
const MODE_NAME = 'view_mode';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEntity($entity_type, $bundle, $view_mode) {
|
||||
return $this->entityDisplayRepository->getViewDisplay($entity_type, $bundle, $view_mode);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\migrate\Attribute\MigrateDestination;
|
||||
|
||||
/**
|
||||
* This class imports one component of an entity form display.
|
||||
*
|
||||
* Destination properties expected in the imported row:
|
||||
* - entity_type: The entity type ID.
|
||||
* - bundle: The entity bundle.
|
||||
* - form_mode: The machine name of the form mode.
|
||||
* - field_name: The machine name of the field to be imported into the display.
|
||||
* - options: (optional) An array of options for displaying the field in this
|
||||
* form mode.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* constants:
|
||||
* entity_type: node
|
||||
* field_name: comment
|
||||
* form_mode: default
|
||||
* options:
|
||||
* type: comment_default
|
||||
* weight: 20
|
||||
* process:
|
||||
* entity_type: 'constants/entity_type'
|
||||
* field_name: 'constants/field_name'
|
||||
* form_mode: 'constants/form_mode'
|
||||
* options: 'constants/options'
|
||||
* bundle: node_type
|
||||
* destination:
|
||||
* plugin: component_entity_form_display
|
||||
* @endcode
|
||||
*
|
||||
* This will add a "comment" field on the "default" form mode of the "node"
|
||||
* entity type with options defined by the "options" constant.
|
||||
*/
|
||||
#[MigrateDestination('component_entity_form_display')]
|
||||
class PerComponentEntityFormDisplay extends ComponentEntityDisplayBase {
|
||||
|
||||
const MODE_NAME = 'form_mode';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEntity($entity_type, $bundle, $form_mode) {
|
||||
return $this->entityDisplayRepository->getFormDisplay($entity_type, $bundle, $form_mode);
|
||||
}
|
||||
|
||||
}
|
||||
214
web/core/modules/migrate/src/Plugin/migrate/id_map/NullIdMap.php
Normal file
214
web/core/modules/migrate/src/Plugin/migrate/id_map/NullIdMap.php
Normal file
@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\id_map;
|
||||
|
||||
use Drupal\Component\Plugin\Attribute\PluginID;
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\migrate\MigrateMessageInterface;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Defines the null ID map implementation.
|
||||
*
|
||||
* This serves as a dummy in order to not store anything.
|
||||
*/
|
||||
#[PluginID('null')]
|
||||
class NullIdMap extends PluginBase implements MigrateIdMapInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setMessage(MigrateMessageInterface $message) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRowBySource(array $source_id_values) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRowByDestination(array $destination_id_values) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRowsNeedingUpdate($count) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lookupSourceId(array $destination_id_values) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lookupDestinationIds(array $source_id_values) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function saveIdMapping(Row $row, array $destination_id_values, $source_row_status = MigrateIdMapInterface::STATUS_IMPORTED, $rollback_action = MigrateIdMapInterface::ROLLBACK_DELETE) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function saveMessage(array $source_id_values, $message, $level = MigrationInterface::MESSAGE_ERROR) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMessages(array $source_id_values = [], $level = NULL) {
|
||||
return new \ArrayIterator([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareUpdate() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processedCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function importedCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function updateCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function errorCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function messageCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(array $source_id_values, $messages_only = FALSE) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteDestination(array $destination_id_values) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUpdate(array $source_id_values) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clearMessages() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function destroy() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function currentDestination() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function currentSource() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQualifiedMapTableName() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rewind(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function current(): mixed {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function key(): mixed {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function next(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function valid(): bool {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
||||
1060
web/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php
Normal file
1060
web/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\Attribute\MigrateProcess;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Builds an array based on the key and value configuration.
|
||||
*
|
||||
* The array_build plugin builds a single associative array by extracting keys
|
||||
* and values from each array in the input value, which is expected to be an
|
||||
* array of arrays. The keys of the returned array will be determined by the
|
||||
* 'key' configuration option, and the values will be determined by the 'value'
|
||||
* option.
|
||||
*
|
||||
* Available configuration keys
|
||||
* - key: The key used to lookup a value in the source arrays to be used as
|
||||
* a key in the destination array.
|
||||
* - value: The key used to lookup a value in the source arrays to be used as
|
||||
* a value in the destination array.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* Consider the migration of language negotiation by domain.
|
||||
* The source is an array of all the languages:
|
||||
*
|
||||
* @code
|
||||
* languages: Array
|
||||
* (
|
||||
* [0] => Array
|
||||
* (
|
||||
* [language] => en
|
||||
* ...
|
||||
* [domain] => http://example.com
|
||||
* )
|
||||
* [1] => Array
|
||||
* (
|
||||
* [language] => fr
|
||||
* ...
|
||||
* [domain] => http://fr.example.com
|
||||
* )
|
||||
* ...
|
||||
* @endcode
|
||||
*
|
||||
* The destination should be an array of all the domains keyed by their
|
||||
* language code:
|
||||
*
|
||||
* @code
|
||||
* domains: Array
|
||||
* (
|
||||
* [en] => http://example.com
|
||||
* [fr] => http://fr.example.com
|
||||
* ...
|
||||
* @endcode
|
||||
*
|
||||
* The array_build process plugin would be used like this:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* domains:
|
||||
* plugin: array_build
|
||||
* key: language
|
||||
* value: domain
|
||||
* source: languages
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*/
|
||||
#[MigrateProcess(
|
||||
id: "array_build",
|
||||
handle_multiples: TRUE,
|
||||
)]
|
||||
class ArrayBuild extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$new_value = [];
|
||||
|
||||
foreach ((array) $value as $old_value) {
|
||||
// Checks that $old_value is an array.
|
||||
if (!is_array($old_value)) {
|
||||
throw new MigrateException("The input should be an array of arrays");
|
||||
}
|
||||
|
||||
// Checks that the key exists.
|
||||
if (!array_key_exists($this->configuration['key'], $old_value)) {
|
||||
throw new MigrateException("The key '" . $this->configuration['key'] . "' does not exist");
|
||||
}
|
||||
|
||||
// Checks that the value exists.
|
||||
if (!array_key_exists($this->configuration['value'], $old_value)) {
|
||||
throw new MigrateException("The key '" . $this->configuration['value'] . "' does not exist");
|
||||
}
|
||||
|
||||
$new_value[$old_value[$this->configuration['key']]] = $old_value[$this->configuration['value']];
|
||||
}
|
||||
|
||||
return $new_value;
|
||||
}
|
||||
|
||||
}
|
||||
106
web/core/modules/migrate/src/Plugin/migrate/process/Callback.php
Normal file
106
web/core/modules/migrate/src/Plugin/migrate/process/Callback.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\Attribute\MigrateProcess;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Passes the source value to a callback.
|
||||
*
|
||||
* The callback process plugin allows simple processing of the value, such as
|
||||
* strtolower(). To pass more than one argument, pass an array as the source
|
||||
* and set the unpack_source option.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - callable: The name of the callable method.
|
||||
* - unpack_source: (optional) Whether to interpret the source as an array of
|
||||
* arguments.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* destination_field:
|
||||
* plugin: callback
|
||||
* callable: mb_strtolower
|
||||
* source: source_field
|
||||
* @endcode
|
||||
*
|
||||
* An example where the callable is a static method in a class:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* destination_field:
|
||||
* plugin: callback
|
||||
* callable:
|
||||
* - '\Drupal\Component\Utility\Unicode'
|
||||
* - ucfirst
|
||||
* source: source_field
|
||||
* @endcode
|
||||
*
|
||||
* An example where the callback accepts no arguments:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* time:
|
||||
* plugin: callback
|
||||
* callable: time
|
||||
* unpack_source: true
|
||||
* source: [ ]
|
||||
* @endcode
|
||||
*
|
||||
* An example where the callback accepts more than one argument:
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* plugin: source_plugin_goes_here
|
||||
* constants:
|
||||
* slash: /
|
||||
* process:
|
||||
* field_link_url:
|
||||
* plugin: callback
|
||||
* callable: rtrim
|
||||
* unpack_source: true
|
||||
* source:
|
||||
* - url
|
||||
* - constants/slash
|
||||
* @endcode
|
||||
*
|
||||
* This will remove the trailing '/', if any, from a URL.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*/
|
||||
#[MigrateProcess('callback')]
|
||||
class Callback extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
|
||||
if (!isset($configuration['callable'])) {
|
||||
throw new \InvalidArgumentException('The "callable" must be set.');
|
||||
}
|
||||
elseif (!is_callable($configuration['callable'])) {
|
||||
throw new \InvalidArgumentException('The "callable" must be a valid function or method.');
|
||||
}
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (!empty($this->configuration['unpack_source'])) {
|
||||
if (!is_array($value)) {
|
||||
throw new MigrateException(sprintf("When 'unpack_source' is set, the source must be an array. Instead it was of type '%s'", gettype($value)));
|
||||
}
|
||||
return call_user_func($this->configuration['callable'], ...$value);
|
||||
}
|
||||
return call_user_func($this->configuration['callable'], $value);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\Attribute\MigrateProcess;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Concatenates a set of strings.
|
||||
*
|
||||
* The concat plugin is used to concatenate strings. For example, imploding a
|
||||
* set of strings into a single string.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - delimiter: (optional) A delimiter, or glue string, to insert between the
|
||||
* strings.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* new_text_field:
|
||||
* plugin: concat
|
||||
* source:
|
||||
* - foo
|
||||
* - bar
|
||||
* @endcode
|
||||
*
|
||||
* This will set new_text_field to the concatenation of the 'foo' and 'bar'
|
||||
* source values. For example, if the 'foo' property is "Rosa" and the 'bar'
|
||||
* property is "Parks", new_text_field will be "RosaParks".
|
||||
*
|
||||
* You can also specify a delimiter.
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* new_text_field:
|
||||
* plugin: concat
|
||||
* source:
|
||||
* - foo
|
||||
* - bar
|
||||
* delimiter: /
|
||||
* @endcode
|
||||
*
|
||||
* This will set new_text_field to the concatenation of the 'foo' source value,
|
||||
* the delimiter and the 'bar' source value. For example, using the values above
|
||||
* and "/" as the delimiter, if the 'foo' property is "Rosa" and the 'bar'
|
||||
* property is "Rosa", new_text_field will be "Rosa/Parks".
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*/
|
||||
#[MigrateProcess(
|
||||
id: "concat",
|
||||
handle_multiples: TRUE,
|
||||
)]
|
||||
class Concat extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (is_array($value)) {
|
||||
$delimiter = $this->configuration['delimiter'] ?? '';
|
||||
return implode($delimiter, $value);
|
||||
}
|
||||
else {
|
||||
throw new MigrateException(sprintf('%s is not an array', var_export($value, TRUE)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\Attribute\MigrateProcess;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Returns a given default value if the input is empty.
|
||||
*
|
||||
* The default_value process plugin provides the ability to set a fixed default
|
||||
* value. The plugin returns a default value if the input value is considered
|
||||
* empty (NULL, FALSE, 0, '0', an empty string, or an empty array). The strict
|
||||
* configuration key can be used to set the default only when the incoming
|
||||
* value is NULL.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - default_value: The fixed default value to apply.
|
||||
* - strict: (optional) Use strict value checking. Defaults to false.
|
||||
* - FALSE: Apply default when input value is empty().
|
||||
* - TRUE: Apply default when input value is NULL.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* uid:
|
||||
* -
|
||||
* plugin: migration_lookup
|
||||
* migration: users
|
||||
* source: author
|
||||
* no_stub: true
|
||||
* -
|
||||
* plugin: default_value
|
||||
* default_value: 44
|
||||
* @endcode
|
||||
*
|
||||
* This will look up the source value of author in the users migration and if
|
||||
* not found, set the destination property uid to 44.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*/
|
||||
#[MigrateProcess(
|
||||
id: "default_value",
|
||||
handle_multiples: TRUE,
|
||||
)]
|
||||
class DefaultValue extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (!empty($this->configuration['strict'])) {
|
||||
return $value ?? $this->configuration['default_value'];
|
||||
}
|
||||
return $value ?: $this->configuration['default_value'];
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user