Move all files to 2017/
This commit is contained in:
parent
ac7370f67f
commit
2875863330
15717 changed files with 0 additions and 0 deletions
170
2017/web/core/modules/migrate/migrate.api.php
Normal file
170
2017/web/core/modules/migrate/migrate.api.php
Normal file
|
@ -0,0 +1,170 @@
|
|||
<?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. Processing can also determine if a 'stub'
|
||||
* needs to be created. For example, if a term has a parent term which hasn't
|
||||
* been migrated yet, a stub term is created so that the parent relation can be
|
||||
* established, and the stub is updated at a later point. 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 are annotated
|
||||
* with \Drupal\migrate\Annotation\MigrateSource annotation 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 are annotated with
|
||||
* \Drupal\migrate\Annotation\MigrateProcessPlugin annotation 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 are
|
||||
* annotated with \Drupal\migrate\Annotation\MigrateDestination annotation 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_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) {
|
||||
if ($migration->id() == 'd6_filter_formats') {
|
||||
$value = $source->getDatabase()->query('SELECT value FROM {variable} WHERE name = :name', [':name' => 'mymodule_filter_foo_' . $row->getSourceProperty('format')])->fetchField();
|
||||
if ($value) {
|
||||
$row->setSourceProperty('settings:mymodule: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' => 'mymodule_filter_foo_' . $row->getSourceProperty('format')])->fetchField();
|
||||
if ($value) {
|
||||
$row->setSourceProperty('settings:mymodule: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".
|
||||
*/
|
6
2017/web/core/modules/migrate/migrate.info.yml
Normal file
6
2017/web/core/modules/migrate/migrate.info.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
name: Migrate
|
||||
type: module
|
||||
description: 'Handles migrations'
|
||||
package: Migration
|
||||
version: VERSION
|
||||
core: 8.x
|
18
2017/web/core/modules/migrate/migrate.install
Normal file
18
2017/web/core/modules/migrate/migrate.install
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains install and update functions for Migrate.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Remove load plugin references from existing migrations.
|
||||
*/
|
||||
function migrate_update_8001() {
|
||||
$config_factory = \Drupal::configFactory();
|
||||
foreach ($config_factory->listAll('migrate.migration.') as $migration_config_name) {
|
||||
$migration = $config_factory->getEditable($migration_config_name);
|
||||
$migration->clear('load');
|
||||
$migration->save(TRUE);
|
||||
}
|
||||
}
|
22
2017/web/core/modules/migrate/migrate.module
Normal file
22
2017/web/core/modules/migrate/migrate.module
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides the Migrate API.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function migrate_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
case 'help.page.migrate':
|
||||
$output = '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>';
|
||||
$output .= 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;
|
||||
}
|
||||
}
|
32
2017/web/core/modules/migrate/migrate.services.yml
Normal file
32
2017/web/core/modules/migrate/migrate.services.yml
Normal file
|
@ -0,0 +1,32 @@
|
|||
services:
|
||||
migrate.plugin_event_subscriber:
|
||||
class: Drupal\migrate\Plugin\PluginEventSubscriber
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
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\Annotation\MigrateProcessPlugin']
|
||||
plugin.manager.migrate.destination:
|
||||
class: Drupal\migrate\Plugin\MigrateDestinationPluginManager
|
||||
arguments: [destination, '@container.namespaces', '@cache.discovery', '@module_handler', '@entity.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']
|
|
@ -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,48 @@
|
|||
<?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.
|
||||
*
|
||||
* 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 (optional)
|
||||
*/
|
||||
public $handle_multiples = FALSE;
|
||||
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
* @see \Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase::checkRequirements
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
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);
|
||||
|
||||
}
|
27
2017/web/core/modules/migrate/src/Audit/AuditException.php
Normal file
27
2017/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 \Exception $previous
|
||||
* (optional) The previous exception.
|
||||
*/
|
||||
public function __construct(MigrationInterface $migration, $message, \Exception $previous = NULL) {
|
||||
$message = sprintf('Cannot audit migration %s: %s', $migration->id(), $message);
|
||||
parent::__construct($message, 0, $previous);
|
||||
}
|
||||
|
||||
}
|
146
2017/web/core/modules/migrate/src/Audit/AuditResult.php
Normal file
146
2017/web/core/modules/migrate/src/Audit/AuditResult.php
Normal file
|
@ -0,0 +1,146 @@
|
|||
<?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
|
||||
*
|
||||
* @see \Drupal\Component\Render\MarkupInterface
|
||||
*/
|
||||
public function count() {
|
||||
return count($this->reasons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reasons the migration passed or failed, as a string.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @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[]
|
||||
*/
|
||||
public function jsonSerialize() {
|
||||
return $this->reasons;
|
||||
}
|
||||
|
||||
}
|
42
2017/web/core/modules/migrate/src/Audit/AuditorInterface.php
Normal file
42
2017/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);
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
58
2017/web/core/modules/migrate/src/Audit/IdAuditor.php
Normal file
58
2017/web/core/modules/migrate/src/Audit/IdAuditor.php
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Audit;
|
||||
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
|
||||
/**
|
||||
* 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()) {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
60
2017/web/core/modules/migrate/src/Event/EventBase.php
Normal file
60
2017/web/core/modules/migrate/src/Event/EventBase.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateMessageInterface;
|
||||
use Symfony\Component\EventDispatcher\Event as SymfonyEvent;
|
||||
|
||||
class EventBase extends SymfonyEvent {
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
184
2017/web/core/modules/migrate/src/Event/MigrateEvents.php
Normal file
184
2017/web/core/modules/migrate/src/Event/MigrateEvents.php
Normal file
|
@ -0,0 +1,184 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
/**
|
||||
* 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 idmap.
|
||||
*
|
||||
* This event allows modules to perform an action whenever a message is being
|
||||
* logged by the idmap. 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';
|
||||
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Wraps an idmap 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 (one of the
|
||||
* MigrationInterface::MESSAGE_* constants).
|
||||
*
|
||||
* @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 {}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Symfony\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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Symfony\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,45 @@
|
|||
<?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
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Symfony\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 an 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Symfony\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,69 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Exception;
|
||||
|
||||
use 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 \Exception $previous
|
||||
* (optional) The previous exception used for the exception chaining.
|
||||
*/
|
||||
public function __construct($message = "", array $requirements = [], $code = 0, Exception $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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
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);
|
||||
|
||||
}
|
72
2017/web/core/modules/migrate/src/MigrateException.php
Normal file
72
2017/web/core/modules/migrate/src/MigrateException.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?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 Migration::MESSAGE_* constant.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $level;
|
||||
|
||||
/**
|
||||
* The status to record in the map table for the current item.
|
||||
*
|
||||
* The value is a MigrateMap::STATUS_* constant.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $status;
|
||||
|
||||
/**
|
||||
* Constructs a MigrateException object.
|
||||
*
|
||||
* @param string $message
|
||||
* The message for the exception.
|
||||
* @param int $code
|
||||
* The Exception code.
|
||||
* @param \Exception $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 = NULL, $code = 0, \Exception $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;
|
||||
}
|
||||
|
||||
}
|
563
2017/web/core/modules/migrate/src/MigrateExecutable.php
Normal file
563
2017/web/core/modules/migrate/src/MigrateExecutable.php
Normal file
|
@ -0,0 +1,563 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
use Drupal\Component\Utility\Bytes;
|
||||
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\Component\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\Component\EventDispatcher\EventDispatcherInterface
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
|
||||
/**
|
||||
* Migration message service.
|
||||
*
|
||||
* @todo https://www.drupal.org/node/2822663 Make this protected.
|
||||
*
|
||||
* @var \Drupal\migrate\MigrateMessageInterface
|
||||
*/
|
||||
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\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
|
||||
* (optional) The event dispatcher.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
*/
|
||||
public function __construct(MigrationInterface $migration, MigrateMessageInterface $message = NULL, EventDispatcherInterface $event_dispatcher = NULL) {
|
||||
$this->migration = $migration;
|
||||
$this->message = $message ?: new MigrateMessage();
|
||||
$this->migration->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::toInt($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\Component\EventDispatcher\EventDispatcherInterface
|
||||
*/
|
||||
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(),
|
||||
'@status' => $this->t($this->migration->getStatusLabel()),
|
||||
]), 'error');
|
||||
return MigrationInterface::RESULT_FAILED;
|
||||
}
|
||||
$this->getEventDispatcher()->dispatch(MigrateEvents::PRE_IMPORT, new MigrateImportEvent($this->migration, $this->message));
|
||||
|
||||
// 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 @requirements',
|
||||
[
|
||||
'@id' => $this->migration->id(),
|
||||
'@message' => $e->getMessage(),
|
||||
'@requirements' => $e->getRequirementsString(),
|
||||
]
|
||||
),
|
||||
'error'
|
||||
);
|
||||
|
||||
return MigrationInterface::RESULT_FAILED;
|
||||
}
|
||||
|
||||
$this->migration->setStatus(MigrationInterface::STATUS_IMPORTING);
|
||||
$return = MigrationInterface::RESULT_COMPLETED;
|
||||
$source = $this->getSource();
|
||||
$id_map = $this->migration->getIdMap();
|
||||
|
||||
try {
|
||||
$source->rewind();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->message->display(
|
||||
$this->t('Migration failed with source plugin exception: @e', ['@e' => $e->getMessage()]), 'error');
|
||||
$this->migration->setStatus(MigrationInterface::STATUS_IDLE);
|
||||
return MigrationInterface::RESULT_FAILED;
|
||||
}
|
||||
|
||||
$destination = $this->migration->getDestinationPlugin();
|
||||
while ($source->valid()) {
|
||||
$row = $source->current();
|
||||
$this->sourceIdValues = $row->getSourceIdValues();
|
||||
|
||||
try {
|
||||
$this->processRow($row);
|
||||
$save = TRUE;
|
||||
}
|
||||
catch (MigrateException $e) {
|
||||
$this->migration->getIdMap()->saveIdMapping($row, [], $e->getStatus());
|
||||
$this->saveMessage($e->getMessage(), $e->getLevel());
|
||||
$save = FALSE;
|
||||
}
|
||||
catch (MigrateSkipRowException $e) {
|
||||
if ($e->getSaveToMap()) {
|
||||
$id_map->saveIdMapping($row, [], MigrateIdMapInterface::STATUS_IGNORED);
|
||||
}
|
||||
if ($message = trim($e->getMessage())) {
|
||||
$this->saveMessage($message, MigrationInterface::MESSAGE_INFORMATIONAL);
|
||||
}
|
||||
$save = FALSE;
|
||||
}
|
||||
|
||||
if ($save) {
|
||||
try {
|
||||
$this->getEventDispatcher()->dispatch(MigrateEvents::PRE_ROW_SAVE, new MigratePreRowSaveEvent($this->migration, $this->message, $row));
|
||||
$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(MigrateEvents::POST_ROW_SAVE, new MigratePostRowSaveEvent($this->migration, $this->message, $row, $destination_id_values));
|
||||
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->migration->getIdMap()->saveIdMapping($row, [], $e->getStatus());
|
||||
$this->saveMessage($e->getMessage(), $e->getLevel());
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->migration->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',
|
||||
['@e' => $e->getMessage()]), 'error');
|
||||
$this->migration->setStatus(MigrationInterface::STATUS_IDLE);
|
||||
return MigrationInterface::RESULT_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
$this->getEventDispatcher()->dispatch(MigrateEvents::POST_IMPORT, new MigrateImportEvent($this->migration, $this->message));
|
||||
$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) {
|
||||
$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(MigrateEvents::PRE_ROLLBACK, new MigrateRollbackEvent($this->migration));
|
||||
|
||||
// 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->migration->getIdMap();
|
||||
$destination = $this->migration->getDestinationPlugin();
|
||||
|
||||
// Loop through each row in the map, and try to roll it back.
|
||||
foreach ($id_map as $map_row) {
|
||||
$destination_key = $id_map->currentDestination();
|
||||
if ($destination_key) {
|
||||
$map_row = $id_map->getRowByDestination($destination_key);
|
||||
if ($map_row['rollback_action'] == MigrateIdMapInterface::ROLLBACK_DELETE) {
|
||||
$this->getEventDispatcher()
|
||||
->dispatch(MigrateEvents::PRE_ROW_DELETE, new MigrateRowDeleteEvent($this->migration, $destination_key));
|
||||
$destination->rollback($destination_key);
|
||||
$this->getEventDispatcher()
|
||||
->dispatch(MigrateEvents::POST_ROW_DELETE, new MigrateRowDeleteEvent($this->migration, $destination_key));
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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(MigrateEvents::POST_ROLLBACK, new MigrateRollbackEvent($this->migration));
|
||||
$this->migration->setStatus(MigrationInterface::STATUS_IDLE);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processRow(Row $row, array $process = NULL, $value = NULL) {
|
||||
foreach ($this->migration->getProcessPlugins($process) as $destination => $plugins) {
|
||||
$multiple = FALSE;
|
||||
/** @var $plugin \Drupal\migrate\Plugin\MigrateProcessInterface */
|
||||
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) {
|
||||
try {
|
||||
$new_value[] = $plugin->transform($scalar_value, $this, $row, $destination);
|
||||
}
|
||||
catch (MigrateSkipProcessException $e) {
|
||||
$new_value[] = NULL;
|
||||
$break = TRUE;
|
||||
}
|
||||
}
|
||||
$value = $new_value;
|
||||
if ($break) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
$value = $plugin->transform($value, $this, $row, $destination);
|
||||
}
|
||||
catch (MigrateSkipProcessException $e) {
|
||||
$value = NULL;
|
||||
break;
|
||||
}
|
||||
$multiple = $plugin->multiple();
|
||||
}
|
||||
}
|
||||
// Ensure all values, including nulls, are migrated.
|
||||
if ($plugins) {
|
||||
if (isset($value)) {
|
||||
$row->setDestinationProperty($destination, $value);
|
||||
}
|
||||
else {
|
||||
$row->setEmptyDestinationProperty($destination);
|
||||
}
|
||||
}
|
||||
// Reset the value.
|
||||
$value = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->migration->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' => $this->formatSize($usage),
|
||||
'@limit' => $this->formatSize($this->memoryLimit),
|
||||
]
|
||||
),
|
||||
'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' => $this->formatSize($usage),
|
||||
'@limit' => $this->formatSize($this->memoryLimit),
|
||||
]
|
||||
),
|
||||
'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' => $this->formatSize($usage),
|
||||
'@limit' => $this->formatSize($this->memoryLimit),
|
||||
]
|
||||
),
|
||||
'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();
|
||||
|
||||
// Entity storage can blow up with caches so clear them out.
|
||||
$manager = \Drupal::entityManager();
|
||||
foreach ($manager->getDefinitions() as $id => $definition) {
|
||||
$manager->getStorage($id)->resetCache();
|
||||
}
|
||||
|
||||
// @TODO: explore resetting the container.
|
||||
|
||||
// Run garbage collector to further reduce memory.
|
||||
gc_collect_cycles();
|
||||
|
||||
return memory_get_usage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a string representation for the given byte count.
|
||||
*
|
||||
* @param int $size
|
||||
* A size in bytes.
|
||||
*
|
||||
* @return string
|
||||
* A translated string representation of the size.
|
||||
*/
|
||||
protected function formatSize($size) {
|
||||
return format_size($size);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
|
||||
interface MigrateExecutableInterface {
|
||||
|
||||
/**
|
||||
* Performs an import operation - migrate items from source to destination.
|
||||
*/
|
||||
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);
|
||||
|
||||
}
|
30
2017/web/core/modules/migrate/src/MigrateMessage.php
Normal file
30
2017/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 = isset($this->map[$type]) ? $this->map[$type] : RfcLogLevel::NOTICE;
|
||||
\Drupal::logger('migrate')->log($type, $message);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
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');
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
/**
|
||||
* This exception is thrown when the rest of the process should be skipped.
|
||||
*/
|
||||
class MigrateSkipProcessException extends \Exception {
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
/**
|
||||
* This exception is thrown when a row should be skipped.
|
||||
*/
|
||||
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 = NULL, $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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\Derivative;
|
||||
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
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.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,71 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\Derivative;
|
||||
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
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.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,137 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\Discovery;
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationRegistry;
|
||||
use Doctrine\Common\Reflection\StaticReflectionParser as BaseStaticReflectionParser;
|
||||
use Drupal\Component\Annotation\AnnotationInterface;
|
||||
use Drupal\Component\Annotation\Reflection\MockFileFinder;
|
||||
use Drupal\Component\ClassFinder\ClassFinder;
|
||||
use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
|
||||
use Drupal\migrate\Annotation\MultipleProviderAnnotationInterface;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
* A utility object that can use active autoloaders to find files for classes.
|
||||
*
|
||||
* @var \Doctrine\Common\Reflection\ClassFinderInterface
|
||||
*/
|
||||
protected $finder;
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
protected function prepareAnnotationDefinition(AnnotationInterface $annotation, $class, BaseStaticReflectionParser $parser = NULL) {
|
||||
if (!($annotation instanceof MultipleProviderAnnotationInterface)) {
|
||||
throw new \LogicException('AnnotatedClassDiscoveryAutomatedProviders annotations must implement \Drupal\migrate\Annotation\MultipleProviderAnnotationInterface');
|
||||
}
|
||||
$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_unique(array_filter($providers, function ($provider) {
|
||||
return $provider && $provider !== 'component';
|
||||
}));
|
||||
$annotation->setProviders($providers);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinitions() {
|
||||
$definitions = [];
|
||||
|
||||
$reader = $this->getAnnotationReader();
|
||||
|
||||
// Clear the annotation loaders of any previous annotation classes.
|
||||
AnnotationRegistry::reset();
|
||||
// Register the namespaces of classes that can be used for annotations.
|
||||
AnnotationRegistry::registerLoader('class_exists');
|
||||
|
||||
// Search for classes within all PSR-0 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 $annotation \Drupal\Component\Annotation\AnnotationInterface */
|
||||
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 reparsed again.
|
||||
$this->fileCache->set($fileinfo->getPathName(), [NULL]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't let annotation loaders pile up.
|
||||
AnnotationRegistry::reset();
|
||||
|
||||
return $definitions;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
<?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 a 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|\mixed[]
|
||||
* 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' => []];
|
||||
// There can be one or many providers, handle them as multiple always.
|
||||
$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 Doctrine\Common\Reflection\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 \Doctrine\Common\Reflection\StaticReflectionParser $parser
|
||||
* The current static parser.
|
||||
* @param $finder
|
||||
* The class finder. Must implement
|
||||
* \Doctrine\Common\Reflection\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,29 @@
|
|||
<?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.
|
||||
*
|
||||
* For the remaining parameters see \Exception.
|
||||
*
|
||||
* @param string $plugin_id
|
||||
* The plugin ID of the mapper.
|
||||
* @param string $property
|
||||
* The name of the property that is missing from the plugin.
|
||||
*
|
||||
* @see \Exception
|
||||
*/
|
||||
public function __construct($plugin_id, $property, $code = 0, \Exception $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,144 @@
|
|||
<?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\Annotation\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.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* Unused, will be removed before Drupal 9.0.x. Defaults to NULL.
|
||||
*
|
||||
* @return array
|
||||
* - Keys: machine names of the fields
|
||||
* - Values: Human-friendly descriptions of the fields.
|
||||
*/
|
||||
public function fields(MigrationInterface $migration = NULL);
|
||||
|
||||
/**
|
||||
* 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 old destination IDs. Defaults to an empty array.
|
||||
*
|
||||
* @return mixed
|
||||
* The entity ID or an indication of success.
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = []);
|
||||
|
||||
/**
|
||||
* Delete the specified destination object from the target Drupal.
|
||||
*
|
||||
* @param array $destination_identifier
|
||||
* The ID of the destination object to delete.
|
||||
*/
|
||||
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,64 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
|
||||
/**
|
||||
* Plugin manager for migrate destination plugins.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateDestinationInterface
|
||||
* @see \Drupal\migrate\Plugin\migrate\destination\DestinationBase
|
||||
* @see \Drupal\migrate\Annotation\MigrateDestination
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
class MigrateDestinationPluginManager extends MigratePluginManager {
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* 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\EntityManagerInterface $entity_manager
|
||||
* The entity manager.
|
||||
* @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, EntityManagerInterface $entity_manager, $annotation = 'Drupal\migrate\Annotation\MigrateDestination') {
|
||||
parent::__construct($type, $namespaces, $cache_backend, $module_handler, $annotation);
|
||||
$this->entityManager = $entity_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* A specific createInstance method is necessary to pass the migration on.
|
||||
*/
|
||||
public function createInstance($plugin_id, array $configuration = [], MigrationInterface $migration = NULL) {
|
||||
if (substr($plugin_id, 0, 7) == 'entity:' && !$this->entityManager->getDefinition(substr($plugin_id, 7), FALSE)) {
|
||||
$plugin_id = 'null';
|
||||
}
|
||||
return parent::createInstance($plugin_id, $configuration, $migration);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,285 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\migrate\MigrateMessageInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Defines an interface for migrate ID mappings.
|
||||
*
|
||||
* Migrate ID mappings maintain a relation between source ID and destination ID
|
||||
* for audit and rollback purposes.
|
||||
*/
|
||||
interface MigrateIdMapInterface extends \Iterator, PluginInspectionInterface {
|
||||
|
||||
/**
|
||||
* Codes reflecting the current status of a map row.
|
||||
*/
|
||||
const STATUS_IMPORTED = 0;
|
||||
const STATUS_NEEDS_UPDATE = 1;
|
||||
const STATUS_IGNORED = 2;
|
||||
const STATUS_FAILED = 3;
|
||||
|
||||
/**
|
||||
* Codes reflecting how to handle the destination item on rollback.
|
||||
*/
|
||||
const ROLLBACK_DELETE = 0;
|
||||
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 an iterator over messages relate 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 \Iterator
|
||||
* Retrieves an iterator over the message rows.
|
||||
*/
|
||||
public function getMessageIterator(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
|
||||
* unmigrated 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 errored out.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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 identifier corresponding to a source key.
|
||||
*
|
||||
* Given a (possibly multi-field) source identifier value, return the
|
||||
* (possibly multi-field) destination identifier value it is mapped to.
|
||||
*
|
||||
* @param array $source_id_values
|
||||
* The source identifier keyed values of the record, e.g. ['nid' => 5].
|
||||
*
|
||||
* @return array
|
||||
* The destination identifier values of the record, or empty on failure.
|
||||
*
|
||||
* @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.x. Use
|
||||
* lookupDestinationIds() instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2725809
|
||||
*/
|
||||
public function lookupDestinationId(array $source_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 unkeyed, 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);
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
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\Annotation\MigrateSource
|
||||
* @see \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
* @see \Drupal\migrate\Annotation\MigrateProcessPlugin
|
||||
* @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 $annotation
|
||||
* (optional) The annotation class name. Defaults to
|
||||
* 'Drupal\Component\Annotation\PluginID'.
|
||||
*/
|
||||
public function __construct($type, \Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, $annotation = 'Drupal\Component\Annotation\PluginID') {
|
||||
parent::__construct("Plugin/migrate/$type", $namespaces, $module_handler, NULL, $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,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginManagerInterface;
|
||||
|
||||
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,62 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* An interface for migrate process plugins.
|
||||
*
|
||||
* A process plugin will typically implement the transform() method to perform
|
||||
* its work. However, it is possible instead for a process plugin to use any
|
||||
* number of methods, thus offering different alternatives ways of processing.
|
||||
* In this case, the transform() method should not be implemented, and the
|
||||
* plugin configuration must provide the name of the method to be called via the
|
||||
* "method" key. Each method must have the same signature as transform().
|
||||
* The base class \Drupal\migrate\ProcessPluginBase takes care of implementing
|
||||
* transform() and calling the configured method. See
|
||||
* \Drupal\migrate\Plugin\migrate\process\SkipOnEmpty and
|
||||
* d6_field_instance_widget_settings.yml for examples.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigratePluginManager
|
||||
* @see \Drupal\migrate\ProcessPluginBase
|
||||
* @see \Drupal\migrate\Annotation\MigrateProcessPlugin
|
||||
* @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 string|array
|
||||
* 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();
|
||||
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
<?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\Annotation\MigrateSource
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
interface MigrateSourceInterface extends \Countable, \Iterator, PluginInspectionInterface {
|
||||
|
||||
/**
|
||||
* 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,73 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\migrate\Plugin\Discovery\AnnotatedClassDiscoveryAutomatedProviders;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
|
||||
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\Annotation\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, 'Drupal\migrate\Annotation\MigrateSource');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getDiscovery() {
|
||||
if (!$this->discovery) {
|
||||
$discovery = new AnnotatedClassDiscoveryAutomatedProviders($this->subdir, $this->namespaces, $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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
700
2017/web/core/modules/migrate/src/Plugin/Migration.php
Normal file
700
2017/web/core/modules/migrate/src/Plugin/Migration.php
Normal file
|
@ -0,0 +1,700 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Defines the Migration plugin.
|
||||
*
|
||||
* The migration process plugin represents one single migration and acts like a
|
||||
* container for the information about a single migration such as the source,
|
||||
* process and destination plugins.
|
||||
*/
|
||||
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 plugin ID for the row.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $row;
|
||||
|
||||
/**
|
||||
* 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 string
|
||||
*/
|
||||
protected $idMap = [];
|
||||
|
||||
/**
|
||||
* The identifier map.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrateIdMapInterface
|
||||
*/
|
||||
protected $idMapPlugin;
|
||||
|
||||
/**
|
||||
* The source identifiers.
|
||||
*
|
||||
* An array of source identifiers: the keys are the name of the properties,
|
||||
* the values are dependent on the ID map plugin.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sourceIds = [];
|
||||
|
||||
/**
|
||||
* 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 = [];
|
||||
|
||||
/**
|
||||
* Specify value of source_row_status for current map row. Usually set by
|
||||
* MigrateFieldHandler implementations.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $sourceRowStatus = MigrateIdMapInterface::STATUS_IMPORTED;
|
||||
|
||||
/**
|
||||
* Track time of last import if TRUE.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $trackLastImported = FALSE;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* The migration_dependencies value is structured like this:
|
||||
* @code
|
||||
* array(
|
||||
* 'required' => array(
|
||||
* // An array of migration IDs that must be run before this migration.
|
||||
* ),
|
||||
* 'optional' => array(
|
||||
* // An array of migration IDs that, if they exist, must be run before
|
||||
* // this migration.
|
||||
* ),
|
||||
* );
|
||||
* @endcode
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $migration_dependencies = [];
|
||||
|
||||
/**
|
||||
* The migration's configuration dependencies.
|
||||
*
|
||||
* These store any dependencies on modules or other configuration (including
|
||||
* other migrations) that must be available before the migration can be
|
||||
* created.
|
||||
*
|
||||
* @see \Drupal\Core\Config\Entity\ConfigDependencyManager
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $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 $idmap_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 $idmap_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 = $idmap_plugin_manager;
|
||||
|
||||
foreach (NestedArray::mergeDeep($plugin_definition, $configuration) as $key => $value) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets any arbitrary property's value.
|
||||
*
|
||||
* @param string $property
|
||||
* The property to retrieve.
|
||||
*
|
||||
* @return mixed
|
||||
* The value for that property, or NULL if the property does not exist.
|
||||
*
|
||||
* @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.x. Use
|
||||
* more specific getters instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2873795
|
||||
*/
|
||||
public function get($property) {
|
||||
return isset($this->$property) ? $this->$property : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if (!isset($process)) {
|
||||
$process = $this->getProcess();
|
||||
}
|
||||
$index = serialize($process);
|
||||
if (!isset($this->processPlugins[$index])) {
|
||||
$this->processPlugins[$index] = [];
|
||||
foreach ($this->getProcessNormalized($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];
|
||||
}
|
||||
$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 = isset($configuration['plugin']) ? $configuration['plugin'] : 'sql';
|
||||
$this->idMapPlugin = $this->idMapPluginManager->createInstance($plugin, $configuration, $this);
|
||||
}
|
||||
return $this->idMapPlugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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\MigratePluginManager
|
||||
* The 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);
|
||||
}
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isTrackLastImported() {
|
||||
return $this->trackLastImported;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setTrackLastImported($track_last_imported) {
|
||||
$this->trackLastImported = (bool) $track_last_imported;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMigrationDependencies() {
|
||||
$this->migration_dependencies = ($this->migration_dependencies ?: []) + ['required' => [], 'optional' => []];
|
||||
$this->migration_dependencies['optional'] = array_unique(array_merge($this->migration_dependencies['optional'], $this->findMigrationDependencies($this->process)));
|
||||
return $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 (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] = isset($this->$key) ? $this->$key : $value;
|
||||
}
|
||||
return $definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDestinationConfiguration() {
|
||||
return $this->destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSourceConfiguration() {
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTrackLastImported() {
|
||||
return $this->trackLastImported;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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();
|
||||
}
|
||||
|
||||
}
|
332
2017/web/core/modules/migrate/src/Plugin/MigrationInterface.php
Normal file
332
2017/web/core/modules/migrate/src/Plugin/MigrationInterface.php
Normal file
|
@ -0,0 +1,332 @@
|
|||
<?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();
|
||||
|
||||
/**
|
||||
* Returns the initialized source plugin.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
* The source plugin.
|
||||
*/
|
||||
public function getSourcePlugin();
|
||||
|
||||
/**
|
||||
* Returns the process plugins.
|
||||
*
|
||||
* @param array $process
|
||||
* A process configuration array.
|
||||
*
|
||||
* @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();
|
||||
|
||||
/**
|
||||
* Signal that the migration should be interrupted with the specified result
|
||||
* code.
|
||||
*
|
||||
* @param int $result
|
||||
* One of the MigrationInterface::RESULT_* constants.
|
||||
*/
|
||||
public function interruptMigration($result);
|
||||
|
||||
/**
|
||||
* Get the normalized process pipeline configuration describing the process
|
||||
* plugins.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @see Drupal\migrate_drupal\Plugin\migrate\load\LoadEntity::processLinkField()
|
||||
*/
|
||||
public function mergeProcessOfProperty($property, array $process_of_property);
|
||||
|
||||
/**
|
||||
* Checks if the migration should track time of last import.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the migration is tracking last import time.
|
||||
*/
|
||||
public function isTrackLastImported();
|
||||
|
||||
/**
|
||||
* Set if the migration should track time of last import.
|
||||
*
|
||||
* @param bool $track_last_imported
|
||||
* Boolean value to indicate if the migration should track last import time.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setTrackLastImported($track_last_imported);
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* If true, track time of last import.
|
||||
*
|
||||
* @return bool
|
||||
* Flag to determine desire of tracking time of last import.
|
||||
*/
|
||||
public function getTrackLastImported();
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function isAuditable();
|
||||
|
||||
}
|
|
@ -0,0 +1,272 @@
|
|||
<?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', ['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 and
|
||||
* MODULENAME/migration_templates directories. Throws a deprecation notice if
|
||||
* the MODULENAME/migration_templates directory exists.
|
||||
*/
|
||||
protected function getDiscovery() {
|
||||
if (!isset($this->discovery)) {
|
||||
$directories = array_map(function ($directory) {
|
||||
// Check for use of the @deprecated /migration_templates directory.
|
||||
// @todo Remove use of /migration_templates in Drupal 9.0.0.
|
||||
if (is_dir($directory . '/migration_templates')) {
|
||||
@trigger_error('Use of the /migration_templates directory to store migration configuration files is deprecated in Drupal 8.1.0 and will be removed before Drupal 9.0.0. See https://www.drupal.org/node/2920988.', E_USER_DEPRECATED);
|
||||
}
|
||||
// But still accept configurations found in /migration_templates.
|
||||
return [$directory . '/migration_templates', $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;
|
||||
$plugin_ids = $this->expandPluginIds($migration_ids);
|
||||
|
||||
$instances = [];
|
||||
foreach ($plugin_ids as $plugin_id) {
|
||||
$instances[$plugin_id] = $factory->createInstance($plugin_id, isset($configuration[$plugin_id]) ? $configuration[$plugin_id] : []);
|
||||
}
|
||||
|
||||
foreach ($instances as $migration) {
|
||||
$migration->set('migration_dependencies', array_map([$this, 'expandPluginIds'], $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 $this->createInstances(array_keys($migrations));
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand derivative migration dependencies.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @return array
|
||||
* An array of expanded plugin ids.
|
||||
*/
|
||||
protected function expandPluginIds(array $migration_ids) {
|
||||
$plugin_ids = [];
|
||||
foreach ($migration_ids as $id) {
|
||||
$plugin_ids += preg_grep('/^' . preg_quote($id, '/') . PluginBase::DERIVATIVE_SEPARATOR . '/', array_keys($this->getDefinitions()));
|
||||
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 = isset($dynamic_ids[$dependency]) ? $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 = isset($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,54 @@
|
|||
<?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 = []);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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 $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() {
|
||||
$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,76 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
const MODE_NAME = '';
|
||||
|
||||
/**
|
||||
* {@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(MigrationInterface $migration = NULL) {
|
||||
// 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,220 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Component\Plugin\DependentPluginInterface;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\DependencyTrait;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
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: i18n_variable
|
||||
* 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 "i18n_variable"
|
||||
* source plugin.
|
||||
*
|
||||
* @see \Drupal\migrate_drupal\Plugin\migrate\source\d6\i18nVariable
|
||||
*
|
||||
* @MigrateDestination(
|
||||
* id = "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
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, ConfigFactoryInterface $config_factory, LanguageManagerInterface $language_manager) {
|
||||
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')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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);
|
||||
}
|
||||
}
|
||||
$this->config->save();
|
||||
$ids[] = $this->config->getName();
|
||||
if ($this->isTranslationDestination()) {
|
||||
$ids[] = $row->getDestinationProperty('langcode');
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields(MigrationInterface $migration = NULL) {
|
||||
// @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,136 @@
|
|||
<?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;
|
||||
|
||||
/**
|
||||
* 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\Annotation\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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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,238 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Component\Plugin\DependentPluginInterface;
|
||||
use Drupal\Core\Entity\DependencyTrait;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a generic destination to import 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_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
|
||||
* translations: true
|
||||
* @endcode
|
||||
*
|
||||
* This will save the processed, migrated row as a node with the relevant
|
||||
* langcode because the translations configuration is set to "true".
|
||||
*
|
||||
* @MigrateDestination(
|
||||
* id = "entity",
|
||||
* deriver = "Drupal\migrate\Plugin\Derivative\MigrateEntity"
|
||||
* )
|
||||
*/
|
||||
abstract class Entity extends DestinationBase implements ContainerFactoryPluginInterface, DependentPluginInterface {
|
||||
|
||||
use DependencyTrait;
|
||||
|
||||
/**
|
||||
* The entity storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* 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.manager')->getStorage($entity_type_id),
|
||||
array_keys($container->get('entity.manager')->getBundleInfo($entity_type_id))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
// Remove "entity:".
|
||||
return substr($plugin_id, 7);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = isset($this->configuration['default_bundle']) ? $this->configuration['default_bundle'] : '';
|
||||
$bundle_key = $this->getKey('bundle');
|
||||
return $row->getDestinationProperty($bundle_key) ?: $default_bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields(MigrationInterface $migration = NULL) {
|
||||
// TODO: Implement fields() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
$entity->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
$this->addDependency('module', $this->storage->getEntityType()->getProvider());
|
||||
return $this->dependencies;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
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(
|
||||
* id = "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,290 @@
|
|||
<?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_i18n_profile_field
|
||||
* constants:
|
||||
* entity_type: user
|
||||
* bundle: user
|
||||
* process:
|
||||
* langcode: language
|
||||
* entity_type: 'constants/entity_type'
|
||||
* bundle: 'constants/bundle'
|
||||
* field_name: name
|
||||
* ...
|
||||
* 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.
|
||||
*/
|
||||
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.manager')->getStorage($entity_type_id),
|
||||
array_keys($container->get('entity.manager')->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.
|
||||
*/
|
||||
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 ($row->hasDestinationProperty('langcode') && $this->languageManager instanceof ConfigurableLanguageManager) {
|
||||
$config = $entity->getConfigDependencyName();
|
||||
$langcode = $this->configFactory->get('langcode');
|
||||
if ($langcode != $row->getDestinationProperty('langcode')) {
|
||||
$translation = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if ($translation) {
|
||||
$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());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,369 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Field\FieldTypePluginManagerInterface;
|
||||
use Drupal\Core\TypedData\TranslatableInterface;
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
use Drupal\migrate\Audit\HighestIdInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides destination class for all content entities lacking a specific class.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - translations: (optional) Boolean, indicates if the entity is translatable,
|
||||
* defaults to FALSE.
|
||||
* - 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.
|
||||
*
|
||||
* 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
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\destination\EntityRevision
|
||||
*/
|
||||
class EntityContentBase extends Entity implements HighestIdInterface {
|
||||
|
||||
/**
|
||||
* Entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* Field type plugin manager.
|
||||
*
|
||||
* @var \Drupal\Core\Field\FieldTypePluginManagerInterface
|
||||
*/
|
||||
protected $fieldTypeManager;
|
||||
|
||||
/**
|
||||
* 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\EntityManagerInterface $entity_manager
|
||||
* The entity manager service.
|
||||
* @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
|
||||
* The field type plugin manager service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles);
|
||||
$this->entityManager = $entity_manager;
|
||||
$this->fieldTypeManager = $field_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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.manager')->getStorage($entity_type),
|
||||
array_keys($container->get('entity.manager')->getBundleInfo($entity_type)),
|
||||
$container->get('entity.manager'),
|
||||
$container->get('plugin.manager.field.field_type')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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');
|
||||
}
|
||||
|
||||
$ids = $this->save($entity, $old_destination_id_values);
|
||||
if ($this->isTranslationDestination()) {
|
||||
$ids[] = $entity->language()->getId();
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an entity with the new values from 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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates as much of the stub row as possible.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row of data.
|
||||
*/
|
||||
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));
|
||||
}
|
||||
|
||||
// Populate any required fields not already populated.
|
||||
$fields = $this->entityManager
|
||||
->getFieldDefinitions($this->storage->getEntityTypeId(), $bundle_key);
|
||||
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 {
|
||||
// Otherwise, ask the field type to generate a sample value.
|
||||
$field_type = $field_definition->getType();
|
||||
/** @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->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
parent::rollback($destination_identifier);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
$entity_type_id = static::getEntityTypeId($this->getPluginId());
|
||||
/** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $definitions */
|
||||
$definitions = $this->entityManager->getBaseFieldDefinitions($entity_type_id);
|
||||
$field_definition = $definitions[$key];
|
||||
|
||||
return [
|
||||
'type' => $field_definition->getType(),
|
||||
] + $field_definition->getSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHighestId() {
|
||||
$values = $this->storage->getQuery()
|
||||
->accessCheck(FALSE)
|
||||
->sort($this->getKey('id'), 'DESC')
|
||||
->range(0, 1)
|
||||
->execute();
|
||||
return (int) current($values);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
/**
|
||||
* 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(
|
||||
* id = "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,86 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
/**
|
||||
* 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(
|
||||
* id = "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,216 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Field\FieldTypePluginManagerInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\migrate\MigrateException;
|
||||
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 = "Drupal\migrate\Plugin\Derivative\MigrateEntityRevision"
|
||||
* )
|
||||
*/
|
||||
class EntityRevision extends EntityContentBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager) {
|
||||
$plugin_definition += [
|
||||
'label' => new TranslatableMarkup('@entity_type revisions', ['@entity_type' => $storage->getEntityType()->getSingularLabel()]),
|
||||
];
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_manager, $field_type_manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function getEntityTypeId($plugin_id) {
|
||||
// Remove entity_revision:
|
||||
return substr($plugin_id, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'));
|
||||
if (!empty($revision_id) && ($entity = $this->storage->loadRevision($revision_id))) {
|
||||
$entity->setNewRevision(FALSE);
|
||||
}
|
||||
else {
|
||||
$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);
|
||||
}
|
||||
// We need to update the entity, so that the destination row IDs are
|
||||
// correct.
|
||||
$entity = $this->updateEntity($entity, $row);
|
||||
$entity->isDefaultRevision(FALSE);
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function save(ContentEntityInterface $entity, array $old_destination_id_values = []) {
|
||||
$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;
|
||||
|
||||
/**
|
||||
* 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(
|
||||
* id = "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,38 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
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(MigrationInterface $migration = NULL) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = []) {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
/**
|
||||
* 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(
|
||||
* id = "component_entity_display"
|
||||
* )
|
||||
*/
|
||||
class PerComponentEntityDisplay extends ComponentEntityDisplayBase {
|
||||
|
||||
const MODE_NAME = 'view_mode';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEntity($entity_type, $bundle, $view_mode) {
|
||||
return entity_get_display($entity_type, $bundle, $view_mode);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
/**
|
||||
* 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(
|
||||
* id = "component_entity_form_display"
|
||||
* )
|
||||
*/
|
||||
class PerComponentEntityFormDisplay extends ComponentEntityDisplayBase {
|
||||
|
||||
const MODE_NAME = 'form_mode';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEntity($entity_type, $bundle, $form_mode) {
|
||||
return entity_get_form_display($entity_type, $bundle, $form_mode);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\id_map;
|
||||
|
||||
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 lookupDestinationId(array $source_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 getMessageIterator(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() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function current() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function key() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function next() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function valid() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
1006
2017/web/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php
Normal file
1006
2017/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\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
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* 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_key => $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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
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(). The callable takes the source value as the single mandatory
|
||||
* argument. No additional arguments can be passed to the callback.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - callable: The name of the callable method.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* destination_field:
|
||||
* plugin: callback
|
||||
* callable: 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'
|
||||
* - strtolower
|
||||
* source: source_field
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "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) {
|
||||
return call_user_func($this->configuration['callable'], $value);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
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 "wambooli" and the 'bar'
|
||||
* property is "pastafazoul", new_text_field will be "wamboolipastafazoul".
|
||||
*
|
||||
* 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 "wambooli" and the 'bar'
|
||||
* property is "pastafazoul", new_text_field will be "wambooli/pastafazoul".
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* 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 = isset($this->configuration['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,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
@trigger_error('The ' . __NAMESPACE__ . ' \DedupeEntityBase is deprecated in
|
||||
Drupal 8.4.x and will be removed before Drupal 9.0.0. Instead, use ' . __NAMESPACE__ . ' \MakeUniqueEntityFieldBase', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* This abstract base contains the dedupe logic.
|
||||
*
|
||||
* These plugins avoid duplication at the destination. For example, when
|
||||
* creating filter format names, the current value is checked against the
|
||||
* existing filter format names and if it exists, a numeric postfix is added
|
||||
* and incremented until a unique value is created.
|
||||
*
|
||||
* @link https://www.drupal.org/node/2345929 Online handbook documentation for dedupebase process plugin @endlink
|
||||
*
|
||||
* @deprecated in Drupal 8.4.x and will be removed in Drupal 9.0.x. Use
|
||||
* \Drupal\migrate\Plugin\migrate\process\MakeUniqueBase instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2873762
|
||||
*/
|
||||
abstract class DedupeBase extends MakeUniqueBase {
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
@trigger_error('The ' . __NAMESPACE__ . ' \DedupeEntity is deprecated in
|
||||
Drupal 8.4.x and will be removed before Drupal 9.0.0. Instead, use ' . __NAMESPACE__ . ' \MakeUniqueEntityField', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* Ensures value is not duplicated against an entity field.
|
||||
*
|
||||
* If the 'migrated' configuration value is true, an entity will only be
|
||||
* considered a duplicate if it was migrated by the current migration.
|
||||
*
|
||||
* @link https://www.drupal.org/node/2135325 Online handbook documentation for dedupe_entity process plugin @endlink
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "dedupe_entity"
|
||||
* )
|
||||
*
|
||||
* @deprecated in Drupal 8.4.x and will be removed in Drupal 9.0.x. Use
|
||||
* \Drupal\migrate\Plugin\migrate\process\MakeUniqueEntityField instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2873762
|
||||
*/
|
||||
class DedupeEntity extends MakeUniqueEntityField {}
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
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
|
||||
* -
|
||||
* 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
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "default_value"
|
||||
* )
|
||||
*/
|
||||
class DefaultValue extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (!empty($this->configuration['strict'])) {
|
||||
return isset($value) ? $value : $this->configuration['default_value'];
|
||||
}
|
||||
return $value ?: $this->configuration['default_value'];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Core\File\FileSystemInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use GuzzleHttp\Client;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Downloads a file from a HTTP(S) remote location into the local file system.
|
||||
*
|
||||
* The source value is an array of two values:
|
||||
* - source URL, e.g. 'http://www.example.com/img/foo.img'
|
||||
* - destination URI, e.g. 'public://images/foo.img'
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - file_exists: (optional) Replace behavior when the destination file already
|
||||
* exists:
|
||||
* - 'replace' - (default) Replace the existing file.
|
||||
* - 'rename' - Append _{incrementing number} until the filename is
|
||||
* unique.
|
||||
* - 'use existing' - Do nothing and return FALSE.
|
||||
* - guzzle_options: (optional)
|
||||
* @link http://docs.guzzlephp.org/en/latest/request-options.html Array of request options for Guzzle. @endlink
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* plugin: download
|
||||
* source:
|
||||
* - source_url
|
||||
* - destination_uri
|
||||
* @endcode
|
||||
*
|
||||
* This will download source_url to destination_uri.
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* plugin: download
|
||||
* source:
|
||||
* - source_url
|
||||
* - destination_uri
|
||||
* file_exists: rename
|
||||
* @endcode
|
||||
*
|
||||
* This will download source_url to destination_uri and ensure that the
|
||||
* destination URI is unique. If a file with the same name exists at the
|
||||
* destination, a numbered suffix like '_0' will be appended to make it unique.
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "download"
|
||||
* )
|
||||
*/
|
||||
class Download extends FileProcessBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The file system service.
|
||||
*
|
||||
* @var \Drupal\Core\File\FileSystemInterface
|
||||
*/
|
||||
protected $fileSystem;
|
||||
|
||||
/**
|
||||
* The Guzzle HTTP Client service.
|
||||
*
|
||||
* @var \GuzzleHttp\Client
|
||||
*/
|
||||
protected $httpClient;
|
||||
|
||||
/**
|
||||
* Constructs a download process plugin.
|
||||
*
|
||||
* @param array $configuration
|
||||
* The plugin configuration.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin definition.
|
||||
* @param \Drupal\Core\File\FileSystemInterface $file_system
|
||||
* The file system service.
|
||||
* @param \GuzzleHttp\Client $http_client
|
||||
* The HTTP client.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, array $plugin_definition, FileSystemInterface $file_system, Client $http_client) {
|
||||
$configuration += [
|
||||
'guzzle_options' => [],
|
||||
];
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->fileSystem = $file_system;
|
||||
$this->httpClient = $http_client;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('file_system'),
|
||||
$container->get('http_client')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
// If we're stubbing a file entity, return a uri of NULL so it will get
|
||||
// stubbed by the general process.
|
||||
if ($row->isStub()) {
|
||||
return NULL;
|
||||
}
|
||||
list($source, $destination) = $value;
|
||||
|
||||
// Modify the destination filename if necessary.
|
||||
$final_destination = file_destination($destination, $this->configuration['file_exists']);
|
||||
|
||||
// Reuse if file exists.
|
||||
if (!$final_destination) {
|
||||
return $destination;
|
||||
}
|
||||
|
||||
// Try opening the file first, to avoid calling file_prepare_directory()
|
||||
// unnecessarily. We're suppressing fopen() errors because we want to try
|
||||
// to prepare the directory before we give up and fail.
|
||||
$destination_stream = @fopen($final_destination, 'w');
|
||||
if (!$destination_stream) {
|
||||
// If fopen didn't work, make sure there's a writable directory in place.
|
||||
$dir = $this->fileSystem->dirname($final_destination);
|
||||
if (!file_prepare_directory($dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
|
||||
throw new MigrateException("Could not create or write to directory '$dir'");
|
||||
}
|
||||
// Let's try that fopen again.
|
||||
$destination_stream = @fopen($final_destination, 'w');
|
||||
if (!$destination_stream) {
|
||||
throw new MigrateException("Could not write to file '$final_destination'");
|
||||
}
|
||||
}
|
||||
|
||||
// Stream the request body directly to the final destination stream.
|
||||
$this->configuration['guzzle_options']['sink'] = $destination_stream;
|
||||
|
||||
try {
|
||||
// Make the request. Guzzle throws an exception for anything but 200.
|
||||
$this->httpClient->get($source, $this->configuration['guzzle_options']);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
throw new MigrateException("{$e->getMessage()} ($source)");
|
||||
}
|
||||
|
||||
return $final_destination;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* This plugin checks if a given entity exists.
|
||||
*
|
||||
* Example usage with configuration:
|
||||
* @code
|
||||
* field_tags:
|
||||
* plugin: entity_exists
|
||||
* source: tid
|
||||
* entity_type: taxonomy_term
|
||||
* @endcode
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "entity_exists"
|
||||
* )
|
||||
*/
|
||||
class EntityExists extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The entity storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* EntityExists constructor.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param $storage
|
||||
* The entity storage.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityStorageInterface $storage) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->storage = $storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager')->getStorage($configuration['entity_type'])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (is_array($value)) {
|
||||
$value = reset($value);
|
||||
}
|
||||
|
||||
$entity = $this->storage->load($value);
|
||||
if ($entity instanceof EntityInterface) {
|
||||
return $entity->id();
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Splits the source string into an array of strings, using a delimiter.
|
||||
*
|
||||
* This plugin creates an array of strings by splitting the source parameter on
|
||||
* boundaries formed by the delimiter.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - source: The source string.
|
||||
* - limit: (optional)
|
||||
* - If limit is set and positive, the returned array will contain a maximum
|
||||
* of limit elements with the last element containing the rest of string.
|
||||
* - If limit is set and negative, all components except the last -limit are
|
||||
* returned.
|
||||
* - If the limit parameter is zero, then this is treated as 1.
|
||||
* - delimiter: The boundary string.
|
||||
* - strict: (optional) When this boolean is TRUE, the source should be strictly
|
||||
* a string. If FALSE is passed, the source value is casted to a string before
|
||||
* being split. Also, in this case, the values casting to empty strings are
|
||||
* converted to empty arrays, instead of an array with a single empty string
|
||||
* item ['']. Defaults to TRUE.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* bar:
|
||||
* plugin: explode
|
||||
* source: foo
|
||||
* delimiter: /
|
||||
* @endcode
|
||||
*
|
||||
* If foo is "node/1", then bar will be ['node', '1']. The PHP equivalent of
|
||||
* this would be:
|
||||
*
|
||||
* @code
|
||||
* $bar = explode('/', $foo);
|
||||
* @endcode
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* bar:
|
||||
* plugin: explode
|
||||
* source: foo
|
||||
* limit: 1
|
||||
* delimiter: /
|
||||
* @endcode
|
||||
*
|
||||
* If foo is "node/1/edit", then bar will be ['node', '1/edit']. The PHP
|
||||
* equivalent of this would be:
|
||||
*
|
||||
* @code
|
||||
* $bar = explode('/', $foo, 1);
|
||||
* @endcode
|
||||
*
|
||||
* If the 'strict' configuration is set to FALSE, the input value is casted to a
|
||||
* string before being spilt:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* bar:
|
||||
* plugin: explode
|
||||
* source: foo
|
||||
* delimiter: /
|
||||
* strict: false
|
||||
* @endcode
|
||||
*
|
||||
* If foo is 123 (as integer), then bar will be ['123']. If foo is TRUE, then
|
||||
* bar will be ['1']. The PHP equivalent of this would be:
|
||||
*
|
||||
* @code
|
||||
* $bar = explode('/', (string) 123);
|
||||
* $bar = explode('/', (string) TRUE);
|
||||
* @endcode
|
||||
*
|
||||
* If the 'strict' configuration is set to FALSE, the source value casting to
|
||||
* an empty string are converted to an empty array. For example, with the last
|
||||
* configuration, if foo is '', NULL or FALSE, then bar will be [].
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "explode"
|
||||
* )
|
||||
*/
|
||||
class Explode extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (empty($this->configuration['delimiter'])) {
|
||||
throw new MigrateException('delimiter is empty');
|
||||
}
|
||||
|
||||
$strict = array_key_exists('strict', $this->configuration) ? $this->configuration['strict'] : TRUE;
|
||||
if ($strict && !is_string($value)) {
|
||||
throw new MigrateException(sprintf('%s is not a string', var_export($value, TRUE)));
|
||||
}
|
||||
elseif (!$strict) {
|
||||
// Check if the incoming value can cast to a string.
|
||||
$original = $value;
|
||||
if (!is_string($original) && ($original != ($value = @strval($value)))) {
|
||||
throw new MigrateException(sprintf('%s cannot be casted to a string', var_export($original, TRUE)));
|
||||
}
|
||||
// Empty strings should be exploded to empty arrays.
|
||||
if ($value === '') {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
$limit = isset($this->configuration['limit']) ? $this->configuration['limit'] : PHP_INT_MAX;
|
||||
|
||||
return explode($this->configuration['delimiter'], $value, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function multiple() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Extracts a value from an array.
|
||||
*
|
||||
* The extract process plugin is used to pull data from an input array, which
|
||||
* may have multiple levels. One use case is extracting data from field arrays
|
||||
* in previous versions of Drupal. For instance, in Drupal 7, a field array
|
||||
* would be indexed first by language, then by delta, then finally a key such as
|
||||
* 'value'.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - source: The input value - must be an array.
|
||||
* - index: The array of keys to access the value.
|
||||
* - default: (optional) A default value to assign to the destination if the
|
||||
* key does not exist.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* new_text_field:
|
||||
* plugin: extract
|
||||
* source: some_text_field
|
||||
* index:
|
||||
* - und
|
||||
* - 0
|
||||
* - value
|
||||
* @endcode
|
||||
*
|
||||
* The PHP equivalent of this would be:
|
||||
* @code
|
||||
* $destination['new_text_field'] = $source['some_text_field']['und'][0]['value'];
|
||||
* @endcode
|
||||
* If a default value is specified, it will be returned if the index does not
|
||||
* exist in the input array.
|
||||
*
|
||||
* @code
|
||||
* plugin: extract
|
||||
* source: some_text_field
|
||||
* default: 'Default title'
|
||||
* index:
|
||||
* - title
|
||||
* @endcode
|
||||
*
|
||||
* If $source['some_text_field']['title'] doesn't exist, then the plugin will
|
||||
* return "Default title".
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "extract",
|
||||
* handle_multiples = TRUE
|
||||
* )
|
||||
*/
|
||||
class Extract extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (!is_array($value)) {
|
||||
throw new MigrateException('Input should be an array.');
|
||||
}
|
||||
$new_value = NestedArray::getValue($value, $this->configuration['index'], $key_exists);
|
||||
if (!$key_exists) {
|
||||
if (isset($this->configuration['default'])) {
|
||||
$new_value = $this->configuration['default'];
|
||||
}
|
||||
else {
|
||||
throw new MigrateException('Array index missing, extraction failed.');
|
||||
}
|
||||
}
|
||||
return $new_value;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,245 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Core\File\FileSystemInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\StreamWrapper\LocalStream;
|
||||
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Plugin\MigrateProcessInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Copies or moves a local file from one place into another.
|
||||
*
|
||||
* The file can be moved, reused, or set to be automatically renamed if a
|
||||
* duplicate exists.
|
||||
*
|
||||
* The source value is an indexed array of two values:
|
||||
* - The source path or URI, e.g. '/path/to/foo.txt' or 'public://bar.txt'.
|
||||
* - The destination URI, e.g. 'public://foo.txt'.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - move: (optional) Boolean, if TRUE, move the file, otherwise copy the file.
|
||||
* Defaults to FALSE.
|
||||
* - file_exists: (optional) Replace behavior when the destination file already
|
||||
* exists:
|
||||
* - 'replace' - (default) Replace the existing file.
|
||||
* - 'rename' - Append _{incrementing number} until the filename is
|
||||
* unique.
|
||||
* - 'use existing' - Do nothing and return FALSE.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* path_to_file:
|
||||
* plugin: file_copy
|
||||
* source:
|
||||
* - /path/to/file.png
|
||||
* - public://new/path/to/file.png
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "file_copy"
|
||||
* )
|
||||
*/
|
||||
class FileCopy extends FileProcessBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The stream wrapper manager service.
|
||||
*
|
||||
* @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
|
||||
*/
|
||||
protected $streamWrapperManager;
|
||||
|
||||
/**
|
||||
* The file system service.
|
||||
*
|
||||
* @var \Drupal\Core\File\FileSystemInterface
|
||||
*/
|
||||
protected $fileSystem;
|
||||
|
||||
/**
|
||||
* An instance of the download process plugin.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*/
|
||||
protected $downloadPlugin;
|
||||
|
||||
/**
|
||||
* Constructs a file_copy process plugin.
|
||||
*
|
||||
* @param array $configuration
|
||||
* The plugin configuration.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin definition.
|
||||
* @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrappers
|
||||
* The stream wrapper manager service.
|
||||
* @param \Drupal\Core\File\FileSystemInterface $file_system
|
||||
* The file system service.
|
||||
* @param \Drupal\migrate\Plugin\MigrateProcessInterface $download_plugin
|
||||
* An instance of the download plugin for handling remote URIs.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, array $plugin_definition, StreamWrapperManagerInterface $stream_wrappers, FileSystemInterface $file_system, MigrateProcessInterface $download_plugin) {
|
||||
$configuration += [
|
||||
'move' => FALSE,
|
||||
];
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->streamWrapperManager = $stream_wrappers;
|
||||
$this->fileSystem = $file_system;
|
||||
$this->downloadPlugin = $download_plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('stream_wrapper_manager'),
|
||||
$container->get('file_system'),
|
||||
$container->get('plugin.manager.migrate.process')->createInstance('download', $configuration)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
// If we're stubbing a file entity, return a URI of NULL so it will get
|
||||
// stubbed by the general process.
|
||||
if ($row->isStub()) {
|
||||
return NULL;
|
||||
}
|
||||
list($source, $destination) = $value;
|
||||
|
||||
// If the source path or URI represents a remote resource, delegate to the
|
||||
// download plugin.
|
||||
if (!$this->isLocalUri($source)) {
|
||||
return $this->downloadPlugin->transform($value, $migrate_executable, $row, $destination_property);
|
||||
}
|
||||
|
||||
// Ensure the source file exists, if it's a local URI or path.
|
||||
if (!file_exists($source)) {
|
||||
throw new MigrateException("File '$source' does not exist");
|
||||
}
|
||||
|
||||
// If the start and end file is exactly the same, there is nothing to do.
|
||||
if ($this->isLocationUnchanged($source, $destination)) {
|
||||
return $destination;
|
||||
}
|
||||
|
||||
// Check if a writable directory exists, and if not try to create it.
|
||||
$dir = $this->getDirectory($destination);
|
||||
// If the directory exists and is writable, avoid file_prepare_directory()
|
||||
// call and write the file to destination.
|
||||
if (!is_dir($dir) || !is_writable($dir)) {
|
||||
if (!file_prepare_directory($dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
|
||||
throw new MigrateException("Could not create or write to directory '$dir'");
|
||||
}
|
||||
}
|
||||
|
||||
$final_destination = $this->writeFile($source, $destination, $this->configuration['file_exists']);
|
||||
if ($final_destination) {
|
||||
return $final_destination;
|
||||
}
|
||||
throw new MigrateException("File $source could not be copied to $destination");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to move or copy a file.
|
||||
*
|
||||
* @param string $source
|
||||
* The source path or URI.
|
||||
* @param string $destination
|
||||
* The destination path or URI.
|
||||
* @param int $replace
|
||||
* (optional) FILE_EXISTS_REPLACE (default) or FILE_EXISTS_RENAME.
|
||||
*
|
||||
* @return string|bool
|
||||
* File destination on success, FALSE on failure.
|
||||
*/
|
||||
protected function writeFile($source, $destination, $replace = FILE_EXISTS_REPLACE) {
|
||||
// Check if there is a destination available for copying. If there isn't,
|
||||
// it already exists at the destination and the replace flag tells us to not
|
||||
// replace it. In that case, return the original destination.
|
||||
if (!($final_destination = file_destination($destination, $replace))) {
|
||||
return $destination;
|
||||
}
|
||||
$function = 'file_unmanaged_' . ($this->configuration['move'] ? 'move' : 'copy');
|
||||
return $function($source, $destination, $replace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directory component of a URI or path.
|
||||
*
|
||||
* For URIs like public://foo.txt, the full physical path of public://
|
||||
* will be returned, since a scheme by itself will trip up certain file
|
||||
* API functions (such as file_prepare_directory()).
|
||||
*
|
||||
* @param string $uri
|
||||
* The URI or path.
|
||||
*
|
||||
* @return string|false
|
||||
* The directory component of the path or URI, or FALSE if it could not
|
||||
* be determined.
|
||||
*/
|
||||
protected function getDirectory($uri) {
|
||||
$dir = $this->fileSystem->dirname($uri);
|
||||
if (substr($dir, -3) == '://') {
|
||||
return $this->fileSystem->realpath($dir);
|
||||
}
|
||||
return $dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the source and destination URIs represent identical paths.
|
||||
*
|
||||
* @param string $source
|
||||
* The source URI.
|
||||
* @param string $destination
|
||||
* The destination URI.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the source and destination URIs refer to the same physical path,
|
||||
* otherwise FALSE.
|
||||
*/
|
||||
protected function isLocationUnchanged($source, $destination) {
|
||||
return $this->fileSystem->realpath($source) === $this->fileSystem->realpath($destination);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the given URI or path is considered local.
|
||||
*
|
||||
* A URI or path is considered local if it either has no scheme component,
|
||||
* or the scheme is implemented by a stream wrapper which extends
|
||||
* \Drupal\Core\StreamWrapper\LocalStream.
|
||||
*
|
||||
* @param string $uri
|
||||
* The URI or path to test.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isLocalUri($uri) {
|
||||
$scheme = $this->fileSystem->uriScheme($uri);
|
||||
|
||||
// The vfs scheme is vfsStream, which is used in testing. vfsStream is a
|
||||
// simulated file system that exists only in memory, but should be treated
|
||||
// as a local resource.
|
||||
if ($scheme == 'vfs') {
|
||||
$scheme = FALSE;
|
||||
}
|
||||
return $scheme === FALSE || $this->streamWrapperManager->getViaScheme($scheme) instanceof LocalStream;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
|
||||
/**
|
||||
* Provides functionality for file process plugins.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - file_exists: (optional) Replace behavior when the destination file already
|
||||
* exists:
|
||||
* - 'replace' - (default) Replace the existing file.
|
||||
* - 'rename' - Append _{incrementing number} until the filename is
|
||||
* unique.
|
||||
* - 'use existing' - Do nothing and return FALSE.
|
||||
*/
|
||||
abstract class FileProcessBase extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* Constructs a file process plugin.
|
||||
*
|
||||
* @param array $configuration
|
||||
* The plugin configuration.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin definition.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, array $plugin_definition) {
|
||||
if (array_key_exists('file_exists', $configuration)) {
|
||||
switch ($configuration['file_exists']) {
|
||||
case 'use existing':
|
||||
$configuration['file_exists'] = FILE_EXISTS_ERROR;
|
||||
break;
|
||||
case 'rename':
|
||||
$configuration['file_exists'] = FILE_EXISTS_RENAME;
|
||||
break;
|
||||
default:
|
||||
$configuration['file_exists'] = FILE_EXISTS_REPLACE;
|
||||
}
|
||||
}
|
||||
if (array_key_exists('reuse', $configuration)) {
|
||||
@trigger_error("Using the key 'reuse' is deprecated, use 'file_exists' => 'use existing' instead. See https://www.drupal.org/node/2981389.", E_USER_DEPRECATED);
|
||||
if (!empty($configuration['reuse'])) {
|
||||
$configuration['file_exists'] = FILE_EXISTS_ERROR;
|
||||
}
|
||||
}
|
||||
if (array_key_exists('rename', $configuration)) {
|
||||
@trigger_error("Using the key 'rename' is deprecated, use 'file_exists' => 'rename' instead. See https://www.drupal.org/node/2981389.", E_USER_DEPRECATED);
|
||||
if (!empty($configuration['rename'])) {
|
||||
$configuration['file_exists'] = FILE_EXISTS_RENAME;
|
||||
}
|
||||
}
|
||||
$configuration += ['file_exists' => FILE_EXISTS_REPLACE];
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Flattens the source value.
|
||||
*
|
||||
* The flatten process plugin converts a nested array into a flat array. For
|
||||
* example [[1, 2, [3, 4]], [5], 6] becomes [1, 2, 3, 4, 5, 6]. During some
|
||||
* types of processing (e.g. user permission splitting), what was once a
|
||||
* one-dimensional array gets transformed into a multidimensional array. This
|
||||
* plugin will flatten them back down to one-dimensional arrays again.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* tags:
|
||||
* -
|
||||
* plugin: default_value
|
||||
* source: foo
|
||||
* default_value: [bar, [qux, quux]]
|
||||
* -
|
||||
* plugin: flatten
|
||||
* @endcode
|
||||
*
|
||||
* In this example, the default_value process returns [bar, [qux, quux]] (given
|
||||
* a NULL value of foo). At this point, Migrate would try to import two
|
||||
* items: bar and [qux, quux]. The latter is not a valid one and won't be
|
||||
* imported. We need to pass the values through the flatten processor to obtain
|
||||
* a three items array [bar, qux, quux], suitable for import.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "flatten",
|
||||
* handle_multiples = TRUE
|
||||
* )
|
||||
*/
|
||||
class Flatten extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* Flatten nested array values to single array values.
|
||||
*
|
||||
* For example, [[1, 2, [3, 4]]] becomes [1, 2, 3, 4].
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
return iterator_to_array(new \RecursiveIteratorIterator(new \RecursiveArrayIterator($value)), FALSE);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Component\Datetime\DateTimePlus;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Converts date/datetime from one format to another.
|
||||
*
|
||||
* Available configuration keys
|
||||
* - from_format: The source format string as accepted by
|
||||
* @link http://php.net/manual/datetime.createfromformat.php \DateTime::createFromFormat. @endlink
|
||||
* - to_format: The destination format.
|
||||
* - timezone: (deprecated) String identifying the required time zone, see
|
||||
* DateTimePlus::__construct(). The timezone configuration key is deprecated
|
||||
* in Drupal 8.4.x and will be removed before Drupal 9.0.0, use from_timezone
|
||||
* and to_timezone instead.
|
||||
* - from_timezone: String identifying the required source time zone, see
|
||||
* DateTimePlus::__construct().
|
||||
* - to_timezone: String identifying the required destination time zone, see
|
||||
* DateTimePlus::__construct().
|
||||
* - settings: keyed array of settings, see DateTimePlus::__construct().
|
||||
*
|
||||
* Configuration keys from_timezone and to_timezone are both optional. Possible
|
||||
* input variants:
|
||||
* - Both from_timezone and to_timezone are empty. Date will not be converted
|
||||
* and be treated as date in default timezone.
|
||||
* - Only from_timezone is set. Date will be converted from timezone specified
|
||||
* in from_timezone key to the default timezone.
|
||||
* - Only to_timezone is set. Date will be converted from the default timezone
|
||||
* to the timezone specified in to_timezone key.
|
||||
* - Both from_timezone and to_timezone are set. Date will be converted from
|
||||
* timezone specified in from_timezone key to the timezone specified in
|
||||
* to_timezone key.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* Example usage for date only fields
|
||||
* (DateTimeItemInterface::DATE_STORAGE_FORMAT):
|
||||
* @code
|
||||
* process:
|
||||
* field_date:
|
||||
* plugin: format_date
|
||||
* from_format: 'm/d/Y'
|
||||
* to_format: 'Y-m-d'
|
||||
* source: event_date
|
||||
* @endcode
|
||||
*
|
||||
* If the source value was '01/05/1955' the transformed value would be
|
||||
* 1955-01-05.
|
||||
*
|
||||
* Example usage for datetime fields
|
||||
* (DateTimeItemInterface::DATETIME_STORAGE_FORMAT):
|
||||
* @code
|
||||
* process:
|
||||
* field_time:
|
||||
* plugin: format_date
|
||||
* from_format: 'm/d/Y H:i:s'
|
||||
* to_format: 'Y-m-d\TH:i:s'
|
||||
* source: event_time
|
||||
* @endcode
|
||||
*
|
||||
* If the source value was '01/05/1955 10:43:22' the transformed value would be
|
||||
* 1955-01-05T10:43:22.
|
||||
*
|
||||
* Example usage for datetime fields with a timezone and settings:
|
||||
* @code
|
||||
* process:
|
||||
* field_time:
|
||||
* plugin: format_date
|
||||
* from_format: 'Y-m-d\TH:i:sO'
|
||||
* to_format: 'Y-m-d\TH:i:s'
|
||||
* from_timezone: 'America/Managua'
|
||||
* to_timezone: 'UTC'
|
||||
* settings:
|
||||
* validate_format: false
|
||||
* source: event_time
|
||||
* @endcode
|
||||
*
|
||||
* If the source value was '2004-12-19T10:19:42-0600' the transformed value
|
||||
* would be 2004-12-19T10:19:42. Set validate_format to false if your source
|
||||
* value is '0000-00-00 00:00:00'.
|
||||
*
|
||||
* @see \DateTime::createFromFormat()
|
||||
* @see \Drupal\Component\Datetime\DateTimePlus::__construct()
|
||||
* @see \Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "format_date"
|
||||
* )
|
||||
*/
|
||||
class FormatDate extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (empty($value) && $value !== '0' && $value !== 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Validate the configuration.
|
||||
if (empty($this->configuration['from_format'])) {
|
||||
throw new MigrateException('Format date plugin is missing from_format configuration.');
|
||||
}
|
||||
if (empty($this->configuration['to_format'])) {
|
||||
throw new MigrateException('Format date plugin is missing to_format configuration.');
|
||||
}
|
||||
|
||||
$fromFormat = $this->configuration['from_format'];
|
||||
$toFormat = $this->configuration['to_format'];
|
||||
if (isset($this->configuration['timezone'])) {
|
||||
@trigger_error('Configuration key "timezone" is deprecated in 8.4.x and will be removed before Drupal 9.0.0, use "from_timezone" and "to_timezone" instead. See https://www.drupal.org/node/2885746', E_USER_DEPRECATED);
|
||||
$from_timezone = $this->configuration['timezone'];
|
||||
$to_timezone = isset($this->configuration['to_timezone']) ? $this->configuration['to_timezone'] : NULL;
|
||||
}
|
||||
else {
|
||||
$system_timezone = date_default_timezone_get();
|
||||
$default_timezone = !empty($system_timezone) ? $system_timezone : 'UTC';
|
||||
$from_timezone = isset($this->configuration['from_timezone']) ? $this->configuration['from_timezone'] : $default_timezone;
|
||||
$to_timezone = isset($this->configuration['to_timezone']) ? $this->configuration['to_timezone'] : $default_timezone;
|
||||
}
|
||||
$settings = isset($this->configuration['settings']) ? $this->configuration['settings'] : [];
|
||||
|
||||
// Attempts to transform the supplied date using the defined input format.
|
||||
// DateTimePlus::createFromFormat can throw exceptions, so we need to
|
||||
// explicitly check for problems.
|
||||
try {
|
||||
$transformed = DateTimePlus::createFromFormat($fromFormat, $value, $from_timezone, $settings)->format($toFormat, ['timezone' => $to_timezone]);
|
||||
}
|
||||
catch (\InvalidArgumentException $e) {
|
||||
throw new MigrateException(sprintf('Format date plugin could not transform "%s" using the format "%s". Error: %s', $value, $fromFormat, $e->getMessage()), $e->getCode(), $e);
|
||||
}
|
||||
catch (\UnexpectedValueException $e) {
|
||||
throw new MigrateException(sprintf('Format date plugin could not transform "%s" using the format "%s". Error: %s', $value, $fromFormat, $e->getMessage()), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $transformed;
|
||||
}
|
||||
|
||||
}
|
149
2017/web/core/modules/migrate/src/Plugin/migrate/process/Get.php
Normal file
149
2017/web/core/modules/migrate/src/Plugin/migrate/process/Get.php
Normal file
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Gets the source value.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - source: Source property.
|
||||
*
|
||||
* The get plugin returns the value of the property given by the "source"
|
||||
* configuration key.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* bar:
|
||||
* plugin: get
|
||||
* source: foo
|
||||
* @endcode
|
||||
*
|
||||
* This copies the source value of foo to the destination property "bar".
|
||||
*
|
||||
* Since get is the default process plugin, it can be shorthanded like this:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* bar: foo
|
||||
* @endcode
|
||||
*
|
||||
* Get also supports a list of source properties.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* bar:
|
||||
* plugin: get
|
||||
* source:
|
||||
* - foo1
|
||||
* - foo2
|
||||
* @endcode
|
||||
*
|
||||
* This copies the array of source values [foo1, foo2] to the destination
|
||||
* property "bar".
|
||||
*
|
||||
* If the list of source properties contains an empty element then the current
|
||||
* value will be used. This makes it impossible to reach a source property with
|
||||
* an empty string as its name.
|
||||
*
|
||||
* Get also supports copying destination values. These are indicated by a
|
||||
* starting @ sign. Values using @ must be wrapped in quotes.
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* foo:
|
||||
* plugin: machine_name
|
||||
* source: baz
|
||||
* bar:
|
||||
* plugin: get
|
||||
* source: '@foo'
|
||||
* @endcode
|
||||
*
|
||||
* This will simply copy the destination value of foo to the destination
|
||||
* property bar. foo configuration is included for illustration purposes.
|
||||
*
|
||||
* Because of this, if the source or destination property actually starts with a
|
||||
* "@", that character must be escaped with "@@". The referenced property
|
||||
* becomes, for example, "@@@foo".
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* '@foo':
|
||||
* plugin: machine_name
|
||||
* source: baz
|
||||
* bar:
|
||||
* plugin: get
|
||||
* source: '@@@foo'
|
||||
* @endcode
|
||||
*
|
||||
* This should occur extremely rarely.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "get"
|
||||
* )
|
||||
*/
|
||||
class Get extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* Flag indicating whether there are multiple values.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $multiple;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$source = $this->configuration['source'];
|
||||
$properties = is_string($source) ? [$source] : $source;
|
||||
$return = [];
|
||||
foreach ($properties as $property) {
|
||||
if ($property || (string) $property === '0') {
|
||||
$is_source = TRUE;
|
||||
if ($property[0] == '@') {
|
||||
$property = preg_replace_callback('/^(@?)((?:@@)*)([^@]|$)/', function ($matches) use (&$is_source) {
|
||||
// If there are an odd number of @ in the beginning, it's a
|
||||
// destination.
|
||||
$is_source = empty($matches[1]);
|
||||
// Remove the possible escaping and do not lose the terminating
|
||||
// non-@ either.
|
||||
return str_replace('@@', '@', $matches[2]) . $matches[3];
|
||||
}, $property);
|
||||
}
|
||||
if ($is_source) {
|
||||
$return[] = $row->getSourceProperty($property);
|
||||
}
|
||||
else {
|
||||
$return[] = $row->getDestinationProperty($property);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$return[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_string($source)) {
|
||||
$this->multiple = is_array($return[0]);
|
||||
return $return[0];
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function multiple() {
|
||||
return $this->multiple;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
@trigger_error('The ' . __NAMESPACE__ . '\Iterator is deprecated in Drupal 8.4.x and will be removed before Drupal 9.0.0. Instead, use ' . __NAMESPACE__ . '\SubProcess', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* Iterates and processes an associative array.
|
||||
*
|
||||
* @deprecated in Drupal 8.4.x and will be removed in Drupal 9.0.x. Use
|
||||
* \Drupal\migrate\Plugin\migrate\process\SubProcess instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2880427
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "iterator",
|
||||
* handle_multiples = TRUE
|
||||
* )
|
||||
*/
|
||||
class Iterator extends SubProcess {}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Logs values without changing them.
|
||||
*
|
||||
* The log plugin will log the values that are being processed by other plugins.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* process:
|
||||
* bar:
|
||||
* plugin: log
|
||||
* source: foo
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "log"
|
||||
* )
|
||||
*/
|
||||
class Log extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
// Log the value.
|
||||
$migrate_executable->saveMessage($value);
|
||||
|
||||
// Pass through the same value we received.
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Component\Transliteration\TransliterationInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Creates a machine name.
|
||||
*
|
||||
* The machine_name process plugin takes the source value and runs it through
|
||||
* the transliteration service. This makes the source value lowercase,
|
||||
* replaces anything that is not a number or a letter with an underscore,
|
||||
* and removes duplicate underscores.
|
||||
*
|
||||
* Letters will have language decorations and accents removed.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* bar:
|
||||
* plugin: machine_name
|
||||
* source: foo
|
||||
* @endcode
|
||||
*
|
||||
* If the value of foo in the source is 'áéí!' then the destination value of bar
|
||||
* will be 'aei_'.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "machine_name"
|
||||
* )
|
||||
*/
|
||||
class MachineName extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The transliteration service.
|
||||
*
|
||||
* @var \Drupal\Component\Transliteration\TransliterationInterface
|
||||
*/
|
||||
protected $transliteration;
|
||||
|
||||
/**
|
||||
* Constructs a MachineName plugin.
|
||||
*
|
||||
* @param array $configuration
|
||||
* The plugin configuration.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin definition.
|
||||
* @param \Drupal\Component\Transliteration\TransliterationInterface $transliteration
|
||||
* The transliteration service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, TransliterationInterface $transliteration) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->transliteration = $transliteration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('transliteration')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$new_value = $this->transliteration->transliterate($value, LanguageInterface::LANGCODE_DEFAULT, '_');
|
||||
$new_value = strtolower($new_value);
|
||||
$new_value = preg_replace('/[^a-z0-9_]+/', '_', $new_value);
|
||||
return preg_replace('/_+/', '_', $new_value);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate\MigrateException;
|
||||
|
||||
/**
|
||||
* This plugin ensures the source value is unique.
|
||||
*
|
||||
* The MakeUniqueBase process plugin is used to avoid duplication at the
|
||||
* destination. For example, when creating filter format names, the source
|
||||
* value is checked against the existing filter format names and if it exists,
|
||||
* a numeric postfix is added and incremented until a unique value is created.
|
||||
* An optional postfix string can be insert before the numeric postfix.
|
||||
*
|
||||
* Available configuration keys
|
||||
* - start: (optional) The position at which to start reading.
|
||||
* - length: (optional) The number of characters to read.
|
||||
* - postfix: (optional) A string to insert before the numeric postfix.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*/
|
||||
abstract class MakeUniqueBase extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* Creates a unique value based on the source value.
|
||||
*
|
||||
* @param string $value
|
||||
* The input string.
|
||||
* @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.
|
||||
* @param string $destination_property
|
||||
* The destination property currently worked on. This is only used together
|
||||
* with the $row above.
|
||||
*
|
||||
* @return string
|
||||
* The unique version of the input value.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$i = 1;
|
||||
$postfix = isset($this->configuration['postfix']) ? $this->configuration['postfix'] : '';
|
||||
$start = isset($this->configuration['start']) ? $this->configuration['start'] : 0;
|
||||
if (!is_int($start)) {
|
||||
throw new MigrateException('The start position configuration key should be an integer. Omit this key to capture from the beginning of the string.');
|
||||
}
|
||||
$length = isset($this->configuration['length']) ? $this->configuration['length'] : NULL;
|
||||
if (!is_null($length) && !is_int($length)) {
|
||||
throw new MigrateException('The character length configuration key should be an integer. Omit this key to capture the entire string.');
|
||||
}
|
||||
// Use optional start or length to return a portion of the unique value.
|
||||
$value = mb_substr($value, $start, $length);
|
||||
$new_value = $value;
|
||||
while ($this->exists($new_value)) {
|
||||
$new_value = $value . $postfix . $i++;
|
||||
}
|
||||
return $new_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a query checking the existence of some value.
|
||||
*
|
||||
* @param mixed $value
|
||||
* The value to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the value exists.
|
||||
*/
|
||||
abstract protected function exists($value);
|
||||
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Ensures the source value is made unique against an entity field.
|
||||
*
|
||||
* The make_unique process plugin is typically used to make the entity id
|
||||
* unique, ensuring that migrated entity data is preserved.
|
||||
*
|
||||
* The make_unique process plugin has two required configuration keys,
|
||||
* entity_type and field. It's typically used with an entity destination, making
|
||||
* sure that after saving the entity, the field value is unique. For example,
|
||||
* if the value is foo and there is already an entity where the field value is
|
||||
* foo, then the plugin will return foo1.
|
||||
*
|
||||
* The optional configuration key postfix which will be added between the number
|
||||
* and the original value, for example, foo_1 for postfix: _. Note that the
|
||||
* value of postfix is ignored if the value is not changed, if it was already
|
||||
* unique.
|
||||
*
|
||||
* The optional configuration key migrated, if true, indicates that an entity
|
||||
* will only be considered a duplicate if it was migrated by the current
|
||||
* migration.
|
||||
*
|
||||
* Available configuration keys
|
||||
* - entity_type: The entity type.
|
||||
* - field: The entity field for the given value.
|
||||
* - migrated: (optional) A boolean to indicate that making the field unique
|
||||
* only occurs for migrated entities.
|
||||
* - start: (optional) The position at which to start reading.
|
||||
* - length: (optional) The number of characters to read.
|
||||
* - postfix: (optional) A string to insert before the numeric postfix.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* format:
|
||||
* -
|
||||
* plugin: machine_name
|
||||
* source: name
|
||||
* -
|
||||
* plugin: make_unique_entity_field
|
||||
* entity_type: filter_format
|
||||
* field: format
|
||||
*
|
||||
* @endcode
|
||||
*
|
||||
* This will create a format machine name out the human readable name and make
|
||||
* sure it's unique.
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* format:
|
||||
* -
|
||||
* plugin: machine_name
|
||||
* source: name
|
||||
* -
|
||||
* plugin: make_unique_entity_field
|
||||
* entity_type: filter_format
|
||||
* field: format
|
||||
* postfix: _
|
||||
* migrated: true
|
||||
*
|
||||
* @endcode
|
||||
*
|
||||
* This will create a format machine name out the human readable name and make
|
||||
* sure it's unique if the entity was migrated. The postfix character is
|
||||
* inserted between the added number and the original value.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\process\MakeUniqueBase
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "make_unique_entity_field"
|
||||
* )
|
||||
*/
|
||||
class MakeUniqueEntityField extends MakeUniqueBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The current migration.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationInterface
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityTypeManagerInterface $entity_type_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->migration = $migration;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('entity_type.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function exists($value) {
|
||||
// Plugins are cached so for every run we need a new query object.
|
||||
$query = $this
|
||||
->entityTypeManager
|
||||
->getStorage($this->configuration['entity_type'])
|
||||
->getQuery()
|
||||
->condition($this->configuration['field'], $value);
|
||||
if (!empty($this->configuration['migrated'])) {
|
||||
// Check if each entity is in the ID map.
|
||||
$idMap = $this->migration->getIdMap();
|
||||
foreach ($query->execute() as $id) {
|
||||
$dest_id_values[$this->configuration['field']] = $id;
|
||||
if ($idMap->lookupSourceId($dest_id_values)) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
else {
|
||||
// Just check if any such entity exists.
|
||||
return $query->count()->execute();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Menu\MenuLinkManagerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\MigrateSkipRowException;
|
||||
use Drupal\migrate\Plugin\MigrateProcessInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* This plugin figures out menu link parent plugin IDs.
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "menu_link_parent"
|
||||
* )
|
||||
*/
|
||||
class MenuLinkParent extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Menu\MenuLinkManagerInterface
|
||||
*/
|
||||
protected $menuLinkManager;
|
||||
|
||||
/**
|
||||
* @var \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*/
|
||||
protected $migrationPlugin;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $menuLinkStorage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrateProcessInterface $migration_plugin, MenuLinkManagerInterface $menu_link_manager, EntityStorageInterface $menu_link_storage) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->migrationPlugin = $migration_plugin;
|
||||
$this->menuLinkManager = $menu_link_manager;
|
||||
$this->menuLinkStorage = $menu_link_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
|
||||
$migration_configuration['migration'][] = $migration->id();
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('plugin.manager.migrate.process')->createInstance('migration', $migration_configuration, $migration),
|
||||
$container->get('plugin.manager.menu.link'),
|
||||
$container->get('entity.manager')->getStorage('menu_link_content')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Find the parent link GUID.
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$parent_id = array_shift($value);
|
||||
if (!$parent_id) {
|
||||
// Top level item.
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
$already_migrated_id = $this
|
||||
->migrationPlugin
|
||||
->transform($parent_id, $migrate_executable, $row, $destination_property);
|
||||
if ($already_migrated_id && ($link = $this->menuLinkStorage->load($already_migrated_id))) {
|
||||
return $link->getPluginId();
|
||||
}
|
||||
}
|
||||
catch (MigrateSkipRowException $e) {
|
||||
|
||||
}
|
||||
if (isset($value[1])) {
|
||||
list($menu_name, $parent_link_path) = $value;
|
||||
$url = Url::fromUserInput("/$parent_link_path");
|
||||
if ($url->isRouted()) {
|
||||
$links = $this->menuLinkManager->loadLinksByRoute($url->getRouteName(), $url->getRouteParameters(), $menu_name);
|
||||
if (count($links) == 1) {
|
||||
/** @var \Drupal\Core\Menu\MenuLinkInterface $link */
|
||||
$link = reset($links);
|
||||
return $link->getPluginId();
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new MigrateSkipRowException(sprintf("No parent link found for plid '%d' in menu '%s'.", $parent_id, $value[0]));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
@trigger_error('The ' . __NAMESPACE__ . '\Migration is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use ' . __NAMESPACE__ . '\MigrationLookup', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* Calculates the value of a property based on a previous migration.
|
||||
*
|
||||
* @link https://www.drupal.org/node/2149801 Online handbook documentation for migration process plugin @endlink
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "migration"
|
||||
* )
|
||||
*
|
||||
* @deprecated in Drupal 8.3.x and will be removed in Drupal 9.0.x.
|
||||
* Use \Drupal\migrate\Plugin\migrate\process\MigrationLookup instead.
|
||||
*/
|
||||
class Migration extends MigrationLookup {}
|
|
@ -0,0 +1,271 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\MigrateSkipProcessException;
|
||||
use Drupal\migrate\Plugin\MigratePluginManagerInterface;
|
||||
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Looks up the value of a property based on a previous migration.
|
||||
*
|
||||
* It is important to maintain relationships among content coming from the
|
||||
* source site. For example, on the source site, a given user account may
|
||||
* have an ID of 123, but the Drupal user account created from it may have
|
||||
* a uid of 456. The migration process maintains the relationships between
|
||||
* source and destination identifiers in map tables, and this information
|
||||
* is leveraged by the migration_lookup process plugin.
|
||||
*
|
||||
* Available configuration keys
|
||||
* - migration: A single migration ID, or an array of migration IDs.
|
||||
* - source_ids: (optional) An array keyed by migration IDs with values that are
|
||||
* a list of source properties.
|
||||
* - stub_id: (optional) Identifies the migration which will be used to create
|
||||
* any stub entities.
|
||||
* - no_stub: (optional) Prevents the creation of a stub entity when no
|
||||
* relationship is found in the migration map.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* Consider a node migration, where you want to maintain authorship. Let's
|
||||
* assume that users are previously migrated in a migration named 'users'. The
|
||||
* 'users' migration saved the mapping between the source and destination IDs in
|
||||
* a map table. The node migration example below maps the node 'uid' property so
|
||||
* that we first take the source 'author' value and then do a lookup for the
|
||||
* corresponding Drupal user ID from the map table.
|
||||
* @code
|
||||
* process:
|
||||
* uid:
|
||||
* plugin: migration_lookup
|
||||
* migration: users
|
||||
* source: author
|
||||
* @endcode
|
||||
*
|
||||
* The value of 'migration' can be a list of migration IDs. When using multiple
|
||||
* migrations it is possible each use different source identifiers. In this
|
||||
* case one can use source_ids which is an array keyed by the migration IDs
|
||||
* and the value is a list of source properties. See example below.
|
||||
* @code
|
||||
* process:
|
||||
* uid:
|
||||
* plugin: migration_lookup
|
||||
* migration:
|
||||
* - users
|
||||
* - members
|
||||
* source_ids:
|
||||
* users:
|
||||
* - author
|
||||
* members:
|
||||
* - id
|
||||
* @endcode
|
||||
*
|
||||
* If the migration_lookup plugin does not find the source ID in the migration
|
||||
* map it will create a stub entity for the relationship to use. This stub is
|
||||
* generated by the migration provided. In the case of multiple migrations the
|
||||
* first value of the migration list will be used, but you can select the
|
||||
* migration you wish to use by using the stub_id configuration key. The example
|
||||
* below uses 'members' migration to create stub entities.
|
||||
* @code
|
||||
* process:
|
||||
* uid:
|
||||
* plugin: migration_lookup
|
||||
* migration:
|
||||
* - users
|
||||
* - members
|
||||
* stub_id: members
|
||||
* @endcode
|
||||
*
|
||||
* To prevent the creation of a stub entity when no relationship is found in the
|
||||
* migration map, 'no_stub' configuration can be used as shown below.
|
||||
* @code
|
||||
* process:
|
||||
* uid:
|
||||
* plugin: migration_lookup
|
||||
* migration: users
|
||||
* no_stub: true
|
||||
* source: author
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "migration_lookup"
|
||||
* )
|
||||
*/
|
||||
class MigrationLookup extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The process plugin manager.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigratePluginManager
|
||||
*/
|
||||
protected $processPluginManager;
|
||||
|
||||
/**
|
||||
* The migration plugin manager.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
|
||||
*/
|
||||
protected $migrationPluginManager;
|
||||
|
||||
/**
|
||||
* The migration to be executed.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationInterface
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, MigrationPluginManagerInterface $migration_plugin_manager, MigratePluginManagerInterface $process_plugin_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->migrationPluginManager = $migration_plugin_manager;
|
||||
$this->migration = $migration;
|
||||
$this->processPluginManager = $process_plugin_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$migration,
|
||||
$container->get('plugin.manager.migration'),
|
||||
$container->get('plugin.manager.migrate.process')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$migration_ids = $this->configuration['migration'];
|
||||
if (!is_array($migration_ids)) {
|
||||
$migration_ids = [$migration_ids];
|
||||
}
|
||||
$self = FALSE;
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface[] $migrations */
|
||||
$destination_ids = NULL;
|
||||
$source_id_values = [];
|
||||
$migrations = $this->migrationPluginManager->createInstances($migration_ids);
|
||||
foreach ($migrations as $migration_id => $migration) {
|
||||
if ($migration_id == $this->migration->id()) {
|
||||
$self = TRUE;
|
||||
}
|
||||
if (isset($this->configuration['source_ids'][$migration_id])) {
|
||||
$configuration = ['source' => $this->configuration['source_ids'][$migration_id]];
|
||||
$value = $this->processPluginManager
|
||||
->createInstance('get', $configuration, $this->migration)
|
||||
->transform(NULL, $migrate_executable, $row, $destination_property);
|
||||
}
|
||||
if (!is_array($value)) {
|
||||
$value = [$value];
|
||||
}
|
||||
$this->skipOnEmpty($value);
|
||||
$source_id_values[$migration_id] = $value;
|
||||
// Break out of the loop as soon as a destination ID is found.
|
||||
if ($destination_ids = $migration->getIdMap()->lookupDestinationId($source_id_values[$migration_id])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$destination_ids && !empty($this->configuration['no_stub'])) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!$destination_ids && ($self || isset($this->configuration['stub_id']) || count($migrations) == 1)) {
|
||||
// If the lookup didn't succeed, figure out which migration will do the
|
||||
// stubbing.
|
||||
if ($self) {
|
||||
$migration = $this->migration;
|
||||
}
|
||||
elseif (isset($this->configuration['stub_id'])) {
|
||||
$migration = $migrations[$this->configuration['stub_id']];
|
||||
}
|
||||
else {
|
||||
$migration = reset($migrations);
|
||||
}
|
||||
$destination_plugin = $migration->getDestinationPlugin(TRUE);
|
||||
// Only keep the process necessary to produce the destination ID.
|
||||
$process = $migration->getProcess();
|
||||
|
||||
// We already have the source ID values but need to key them for the Row
|
||||
// constructor.
|
||||
$source_ids = $migration->getSourcePlugin()->getIds();
|
||||
$values = [];
|
||||
foreach (array_keys($source_ids) as $index => $source_id) {
|
||||
$values[$source_id] = $source_id_values[$migration->id()][$index];
|
||||
}
|
||||
|
||||
$stub_row = $this->createStubRow($values + $migration->getSourceConfiguration(), $source_ids);
|
||||
|
||||
// Do a normal migration with the stub row.
|
||||
$migrate_executable->processRow($stub_row, $process);
|
||||
$destination_ids = [];
|
||||
$id_map = $migration->getIdMap();
|
||||
try {
|
||||
$destination_ids = $destination_plugin->import($stub_row);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$id_map->saveMessage($stub_row->getSourceIdValues(), $e->getMessage());
|
||||
}
|
||||
|
||||
if ($destination_ids) {
|
||||
$id_map->saveIdMapping($stub_row, $destination_ids, MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
|
||||
}
|
||||
}
|
||||
if ($destination_ids) {
|
||||
if (count($destination_ids) == 1) {
|
||||
return reset($destination_ids);
|
||||
}
|
||||
else {
|
||||
return $destination_ids;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips the migration process entirely if the value is FALSE.
|
||||
*
|
||||
* @param array $value
|
||||
* The incoming value to transform.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateSkipProcessException
|
||||
*/
|
||||
protected function skipOnEmpty(array $value) {
|
||||
if (!array_filter($value)) {
|
||||
throw new MigrateSkipProcessException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a stub row source for later import as stub data.
|
||||
*
|
||||
* This simple wrapper of the Row constructor allows sub-classing plugins to
|
||||
* have more control over the row.
|
||||
*
|
||||
* @param array $values
|
||||
* An array of values to add as properties on the object.
|
||||
* @param array $source_ids
|
||||
* An array containing the IDs of the source using the keys as the field
|
||||
* names.
|
||||
*
|
||||
* @return \Drupal\migrate\Row
|
||||
* The stub row.
|
||||
*/
|
||||
protected function createStubRow(array $values, array $source_ids) {
|
||||
return new Row($values, $source_ids, TRUE);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\Core\Path\PathValidatorInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Sets the destination route information based on the source link_path.
|
||||
*
|
||||
* The source value is an array of two values:
|
||||
* - link_path: The path or URL of the route.
|
||||
* - options: An array of URL options, e.g. query string, attributes, etc.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* new_route_field:
|
||||
* plugin: route
|
||||
* source:
|
||||
* - 'https://www.drupal.org'
|
||||
* -
|
||||
* attributes:
|
||||
* title: Drupal
|
||||
* @endcode
|
||||
*
|
||||
* This will set new_route_field to be a route with the URL
|
||||
* "https://www.drupal.org" and title attribute "Drupal".
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* another_route_field:
|
||||
* plugin: route
|
||||
* source:
|
||||
* - 'user/login'
|
||||
* -
|
||||
* query:
|
||||
* destination: 'node/1'
|
||||
* @endcode
|
||||
*
|
||||
* This will set another_route_field to be a route to the user login page
|
||||
* (user/login) with a query string of "destination=node/1".
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "route"
|
||||
* )
|
||||
*/
|
||||
class Route extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The path validator service.
|
||||
*
|
||||
* @var \Drupal\Core\Path\PathValidatorInterface
|
||||
*/
|
||||
protected $pathValidator;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, PathValidatorInterface $path_validator) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->migration = $migration;
|
||||
$this->pathValidator = $path_validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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('path.validator')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Set the destination route information based on the source link_path.
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (is_string($value)) {
|
||||
$link_path = $value;
|
||||
$options = [];
|
||||
}
|
||||
else {
|
||||
list($link_path, $options) = $value;
|
||||
}
|
||||
|
||||
$extracted = $this->pathValidator->getUrlIfValidWithoutAccessCheck($link_path);
|
||||
$route = [];
|
||||
|
||||
if ($extracted) {
|
||||
if ($extracted->isExternal()) {
|
||||
$route['route_name'] = NULL;
|
||||
$route['route_parameters'] = [];
|
||||
$route['options'] = $options;
|
||||
$route['url'] = $extracted->getUri();
|
||||
}
|
||||
else {
|
||||
$route['route_name'] = $extracted->getRouteName();
|
||||
$route['route_parameters'] = $extracted->getRouteParameters();
|
||||
$route['options'] = $extracted->getOptions();
|
||||
|
||||
if (isset($options['query'])) {
|
||||
// If the querystring is stored as a string (as in D6), convert it
|
||||
// into an array.
|
||||
if (is_string($options['query'])) {
|
||||
parse_str($options['query'], $old_query);
|
||||
}
|
||||
else {
|
||||
$old_query = $options['query'];
|
||||
}
|
||||
$options['query'] = $route['options']['query'] + $old_query;
|
||||
unset($route['options']['query']);
|
||||
}
|
||||
$route['options'] = $route['options'] + $options;
|
||||
$route['url'] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\MigrateSkipProcessException;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate\MigrateSkipRowException;
|
||||
|
||||
/**
|
||||
* Skips processing the current row when the input value is empty.
|
||||
*
|
||||
* The skip_on_empty process plugin checks to see if the current input value
|
||||
* is empty (empty string, NULL, FALSE, 0, '0', or an empty array). If so, the
|
||||
* further processing of the property or the entire row (depending on the chosen
|
||||
* method) is skipped and will not be migrated.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - method: (optional) What to do if the input value is empty. Possible values:
|
||||
* - row: Skips the entire row when an empty value is encountered.
|
||||
* - process: Prevents further processing of the input property when the value
|
||||
* is empty.
|
||||
* - message: (optional) A message to be logged in the {migrate_message_*} table
|
||||
* for this row. Messages are only logged for the 'row' method. If not set,
|
||||
* nothing is logged in the message table.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* field_type_exists:
|
||||
* plugin: skip_on_empty
|
||||
* method: row
|
||||
* source: field_name
|
||||
* message: 'Field field_name is missing'
|
||||
* @endcode
|
||||
* If 'field_name' is empty, the entire row is skipped and the message 'Field
|
||||
* field_name is missing' is logged in the message table.
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* parent:
|
||||
* -
|
||||
* plugin: skip_on_empty
|
||||
* method: process
|
||||
* source: parent
|
||||
* -
|
||||
* plugin: migration_lookup
|
||||
* migration: d6_taxonomy_term
|
||||
* @endcode
|
||||
* If 'parent' is empty, any further processing of the property is skipped and
|
||||
* the next process plugin (migration_lookup) will not be run. Combining
|
||||
* skip_on_empty and migration_lookup is a typical process pipeline combination
|
||||
* for hierarchical entities where the root entity does not have a parent.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "skip_on_empty"
|
||||
* )
|
||||
*/
|
||||
class SkipOnEmpty extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* Skips the current row when value is not set.
|
||||
*
|
||||
* @param mixed $value
|
||||
* The input value.
|
||||
* @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.
|
||||
* @param string $destination_property
|
||||
* The destination property currently worked on. This is only used together
|
||||
* with the $row above.
|
||||
*
|
||||
* @return mixed
|
||||
* The input value, $value, if it is not empty.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateSkipRowException
|
||||
* Thrown if the source property is not set and the row should be skipped,
|
||||
* records with STATUS_IGNORED status in the map.
|
||||
*/
|
||||
public function row($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (!$value) {
|
||||
$message = !empty($this->configuration['message']) ? $this->configuration['message'] : '';
|
||||
throw new MigrateSkipRowException($message);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops processing the current property when value is not set.
|
||||
*
|
||||
* @param mixed $value
|
||||
* The input value.
|
||||
* @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.
|
||||
* @param string $destination_property
|
||||
* The destination property currently worked on. This is only used together
|
||||
* with the $row above.
|
||||
*
|
||||
* @return mixed
|
||||
* The input value, $value, if it is not empty.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateSkipProcessException
|
||||
* Thrown if the source property is not set and rest of the process should
|
||||
* be skipped.
|
||||
*/
|
||||
public function process($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (!$value) {
|
||||
throw new MigrateSkipProcessException();
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate\MigrateSkipRowException;
|
||||
|
||||
/**
|
||||
* Skips processing the current row when a source value is not set.
|
||||
*
|
||||
* The skip_row_if_not_set process plugin checks whether a value is set. If the
|
||||
* value is set, it is returned. Otherwise, a MigrateSkipRowException
|
||||
* is thrown.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - index: The source property to check for.
|
||||
* - message: (optional) A message to be logged in the {migrate_message_*} table
|
||||
* for this row. If not set, nothing is logged in the message table.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* settings:
|
||||
* # Check if the "contact" key exists in the "data" array.
|
||||
* plugin: skip_row_if_not_set
|
||||
* index: contact
|
||||
* source: data
|
||||
* message: "Missed the 'data' key"
|
||||
* @endcode
|
||||
*
|
||||
* This will return $data['contact'] if it exists. Otherwise, the row will be
|
||||
* skipped and the message "Missed the 'data' key" will be logged in the
|
||||
* message table.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "skip_row_if_not_set",
|
||||
* handle_multiples = TRUE
|
||||
* )
|
||||
*/
|
||||
class SkipRowIfNotSet extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (!isset($value[$this->configuration['index']])) {
|
||||
$message = !empty($this->configuration['message']) ? $this->configuration['message'] : '';
|
||||
throw new MigrateSkipRowException($message);
|
||||
}
|
||||
return $value[$this->configuration['index']];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Component\Utility\Variable;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate\MigrateSkipRowException;
|
||||
|
||||
/**
|
||||
* Changes the source value based on a static lookup map.
|
||||
*
|
||||
* Maps the input value to another value using an associative array specified in
|
||||
* the configuration.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - source: The input value - either a scalar or an array.
|
||||
* - map: An array (of 1 or more dimensions) that defines the mapping between
|
||||
* source values and destination values.
|
||||
* - bypass: (optional) Whether the plugin should proceed when the source is not
|
||||
* found in the map array, defaults to FALSE.
|
||||
* - TRUE: Return the unmodified input value, or another default value, if one
|
||||
* is specified.
|
||||
* - FALSE: Throw a MigrateSkipRowException.
|
||||
* - default_value: (optional) The value to return if the source is not found in
|
||||
* the map array.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* If the value of the source property 'foo' is 'from' then the value of the
|
||||
* destination property bar will be 'to'. Similarly 'this' becomes 'that'.
|
||||
* @code
|
||||
* process:
|
||||
* bar:
|
||||
* plugin: static_map
|
||||
* source: foo
|
||||
* map:
|
||||
* from: to
|
||||
* this: that
|
||||
* @endcode
|
||||
*
|
||||
* The static_map process plugin supports a list of source properties. This is
|
||||
* useful in module-delta to machine name conversions. In the example below,
|
||||
* value 'filter_url' is returned if the source property 'module' is 'filter'
|
||||
* and the source property 'delta' is '2'.
|
||||
* @code
|
||||
* process:
|
||||
* id:
|
||||
* plugin: static_map
|
||||
* source:
|
||||
* - module
|
||||
* - delta
|
||||
* map:
|
||||
* filter:
|
||||
* 0: filter_html_escape
|
||||
* 1: filter_autop
|
||||
* 2: filter_url
|
||||
* 3: filter_htmlcorrector
|
||||
* 4: filter_html_escape
|
||||
* php:
|
||||
* 0: php_code
|
||||
* @endcode
|
||||
*
|
||||
* When static_map is used to just rename a few values and leave the others
|
||||
* unchanged, a 'bypass: true' option can be used. See the example below. If the
|
||||
* value of the source property 'foo' is 'from', 'to' will be returned. If the
|
||||
* value of the source property 'foo' is 'another' (a value that is not in the
|
||||
* map), 'another' will be returned unchanged.
|
||||
* @code
|
||||
* process:
|
||||
* bar:
|
||||
* plugin: static_map
|
||||
* source: foo
|
||||
* map:
|
||||
* from: to
|
||||
* this: that
|
||||
* bypass: TRUE
|
||||
* @endcode
|
||||
*
|
||||
* A default value can be defined for all values that are not included in the
|
||||
* map. See the example below. If the value of the source property 'foo' is
|
||||
* 'yet_another' (a value that is not in the map), 'bar' will be returned.
|
||||
* @code
|
||||
* process:
|
||||
* bar:
|
||||
* plugin: static_map
|
||||
* source: foo
|
||||
* map:
|
||||
* from: to
|
||||
* this: that
|
||||
* default_value: bar
|
||||
* @endcode
|
||||
*
|
||||
* If your source data has boolean values as strings, you need to use single
|
||||
* quotes in the map. See the example below.
|
||||
* @code
|
||||
* process:
|
||||
* bar:
|
||||
* plugin: static_map
|
||||
* source: foo
|
||||
* map:
|
||||
* 'TRUE': to
|
||||
* @endcode
|
||||
*
|
||||
* Mapping from a string which contains a period is not supported. A custom
|
||||
* process plugin can be written to handle this kind of a transformation.
|
||||
* Another option which may be feasible in certain use cases is to first pass
|
||||
* the value through the machine_name process plugin.
|
||||
*
|
||||
* @see https://www.drupal.org/project/drupal/issues/2827897
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "static_map"
|
||||
* )
|
||||
*/
|
||||
class StaticMap extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$new_value = $value;
|
||||
if (is_array($value)) {
|
||||
if (!$value) {
|
||||
throw new MigrateException('Can not lookup without a value.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$new_value = [$value];
|
||||
}
|
||||
$new_value = NestedArray::getValue($this->configuration['map'], $new_value, $key_exists);
|
||||
if (!$key_exists) {
|
||||
if (array_key_exists('default_value', $this->configuration)) {
|
||||
if (!empty($this->configuration['bypass'])) {
|
||||
throw new MigrateException('Setting both default_value and bypass is invalid.');
|
||||
}
|
||||
return $this->configuration['default_value'];
|
||||
}
|
||||
if (empty($this->configuration['bypass'])) {
|
||||
throw new MigrateSkipRowException(sprintf("No static mapping found for '%s' and no default value provided for destination '%s'.", Variable::export($value), $destination_property));
|
||||
}
|
||||
else {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
return $new_value;
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue