updated core from 8.4 to 8.5 : bug with login_destination

This commit is contained in:
Bachir Soussi Chiadmi
2018-03-13 14:01:21 +01:00
parent 0d78da249b
commit e668535a4e
3486 changed files with 89604 additions and 33283 deletions
+81 -49
View File
@@ -10,78 +10,81 @@ use Drupal\migrate\Plugin\MigrateSourceInterface;
use Drupal\migrate\Row;
/**
* @defgroup migration Migration API
* @defgroup migration Migrate API
* @{
* Overview of the Migration API, which migrates data into Drupal.
* Overview of the Migrate API, which migrates data into Drupal.
*
* @section overview Overview of migration
* @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 migration 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 "load" in ETL
* means to load data into storage, while traditionally Drupal uses "load" to
* mean load data from storage into memory.
* (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, typically a database but it can be a CSV, JSON or XML file. The
* row is sent to the process phase where it is transformed as needed by the
* destination, or marked to be skipped. Processing can also determine that a
* stub needs to be created, for example, if a term has a parent term that does
* not yet exist. After processing the transformed row is passed to the
* destination phase where it is loaded (saved) into the Drupal 8 site.
* 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.
*
* The ETL process is configured by the migration plugin. The different phases:
* source, process, and destination are also plugins, and are managed by the
* Migration plugin. So there are four types of plugins in the migration
* process: migration, source, process and destination.
* 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 Migration plugins
* @section sec_migrations Migrate API migration plugins
* Migration plugin definitions are stored in a module's 'migrations' directory.
* For backwards compatibility we also scan the 'migration_templates' directory.
* Examples of migration plugin definitions can be found in
* 'core/modules/action/migration_templates'. The plugin class is
* \Drupal\migrate\Plugin\Migration, with interface
* 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.
*
* @section sec_source Source plugins
* Migration source plugins implement
* @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. Migration source plugins are managed by the
* \Drupal\migrate\Plugin\MigrateSourcePluginManager class. Source plugin
* providers are determined by their and their parents namespaces.
* 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.
*
* @section sec_process Process plugins
* Migration process plugins implement
* @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. Migration process plugins are managed by the
* \Drupal\migrate\Plugin\MigratePluginManager class. The Migrate module
* provides process plugins for common operations (setting default values,
* mapping values, etc.).
* \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.
*
* @section sec_destination Destination plugins
* Migration destination plugins implement
* @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. Migration destination plugins
* 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. The Migrate module provides destination plugins for Drupal core
* objects (configuration and entity).
* class.
*
* @section sec_more_info More information
* @link https://www.drupal.org/node/2127611 Migration API documentation. @endlink
* @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
*
* @see update_api
* @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
* @}
*/
@@ -99,6 +102,13 @@ use Drupal\migrate\Row;
*
* 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) {
@@ -110,6 +120,28 @@ function hook_migrate_prepare_row(Row $row, MigrateSourceInterface $source, Migr
}
}
/**
* 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.
*
+4 -4
View File
@@ -1,12 +1,12 @@
name: Migrate
type: module
description: 'Handles migrations'
package: Core (Experimental)
package: Migration
# version: VERSION
# core: 8.x
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
# Information added by Drupal.org packaging script on 2018-03-07
version: '8.5.0'
core: '8.x'
project: 'drupal'
datestamp: 1515021228
datestamp: 1520457825
@@ -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);
}
}
@@ -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;
}
}
@@ -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();
}
@@ -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;
}
}
@@ -5,7 +5,7 @@ namespace Drupal\migrate\Exception;
use Exception;
/**
* Defines an
* Defines an exception thrown when a migration does not meet the requirements.
*
* @see \Drupal\migrate\Plugin\RequirementsInterface
*/
@@ -96,16 +96,16 @@ class MigrateExecutable implements MigrateExecutableInterface {
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
* The migration to run.
* @param \Drupal\migrate\MigrateMessageInterface $message
* The migrate message service.
* (optional) The migrate message service.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher.
* (optional) The event dispatcher.
*
* @throws \Drupal\migrate\MigrateException
*/
public function __construct(MigrationInterface $migration, MigrateMessageInterface $message, EventDispatcherInterface $event_dispatcher = NULL) {
public function __construct(MigrationInterface $migration, MigrateMessageInterface $message = NULL, EventDispatcherInterface $event_dispatcher = NULL) {
$this->migration = $migration;
$this->message = $message;
$this->migration->getIdMap()->setMessage($message);
$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'));
@@ -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);
}
}
@@ -154,6 +154,17 @@ class Migration extends PluginBase implements MigrationInterface, RequirementsIn
*/
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.
*
@@ -677,4 +688,11 @@ class Migration extends PluginBase implements MigrationInterface, RequirementsIn
return $this->migration_tags;
}
/**
* {@inheritdoc}
*/
public function isAuditable() {
return (bool) $this->audit;
}
}
@@ -25,6 +25,9 @@ trait MigrationDeriverTrait {
'destination' => [
'plugin' => 'null',
],
'idMap' => [
'plugin' => 'null',
],
];
return \Drupal::service('plugin.manager.migration')->createStubMigration($definition)->getSourcePlugin();
}
@@ -322,4 +322,11 @@ interface MigrationInterface extends PluginInspectionInterface, DerivativeInspec
*/
public function getMigrationTags();
/**
* Indicates if the migration is auditable.
*
* @return bool
*/
public function isAuditable();
}
@@ -60,11 +60,22 @@ class MigrationPluginManager extends DefaultPluginManager implements MigrationPl
}
/**
* {@inheritdoc}
* 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());
@@ -116,13 +127,7 @@ class MigrationPluginManager extends DefaultPluginManager implements MigrationPl
}
/**
* 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.
* {@inheritdoc}
*/
public function createInstancesByTag($tag) {
$migrations = array_filter($this->getDefinitions(), function ($migration) use ($tag) {
@@ -40,4 +40,15 @@ interface MigrationPluginManagerInterface extends PluginManagerInterface {
*/
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);
}
@@ -6,7 +6,14 @@ use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Row;
/**
* Defines the base abstract class for component entity display.
* 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 {
@@ -12,7 +12,12 @@ use Drupal\migrate\Plugin\RequirementsInterface;
/**
* Base class for migrate destination classes.
*
* @see \Drupal\migrate\Plugin\MigrateDestinationInterface
* Migrate destination plugins perfom 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
@@ -11,7 +11,48 @@ use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides entity destination plugin.
* 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",
@@ -53,6 +94,10 @@ abstract class Entity extends DestinationBase implements ContainerFactoryPluginI
* 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;
@@ -5,7 +5,42 @@ namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\migrate\Row;
/**
* Provides entity base field override plugin.
* 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"
@@ -9,6 +9,7 @@ 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;
@@ -16,9 +17,68 @@ use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* The destination class for all content entities lacking a specific class.
* 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 {
class EntityContentBase extends Entity implements HighestIdInterface {
/**
* Entity manager.
@@ -111,12 +171,9 @@ class EntityContentBase extends Entity {
}
/**
* Get whether this destination is for translations.
*
* @return bool
* Whether this destination is for translations.
* {@inheritdoc}
*/
protected function isTranslationDestination() {
public function isTranslationDestination() {
return !empty($this->configuration['translations']);
}
@@ -218,7 +275,7 @@ class EntityContentBase extends Entity {
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;
$values = $default_value;
}
else {
// Otherwise, ask the field type to generate a sample value.
@@ -294,4 +351,16 @@ class EntityContentBase extends Entity {
] + $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);
}
}
@@ -3,12 +3,107 @@
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"
@@ -16,6 +111,16 @@ use Drupal\migrate\Row;
*/
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}
*/
@@ -78,4 +183,19 @@ class EntityRevision extends EntityContentBase {
throw new MigrateException('This entity type does not support revisions.');
}
/**
* {@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,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;
}
}
@@ -7,6 +7,7 @@ use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\PluginBase;
use Drupal\migrate\MigrateMessage;
use Drupal\migrate\Audit\HighestIdInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Event\MigrateIdMapMessageEvent;
use Drupal\migrate\MigrateException;
@@ -27,7 +28,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
*
* @PluginID("sql")
*/
class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryPluginInterface {
class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryPluginInterface, HighestIdInterface {
/**
* Column name of hashed source id values.
@@ -152,6 +153,8 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
* The configuration for the plugin.
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
* The migration to do.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EventDispatcherInterface $event_dispatcher) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
@@ -693,7 +696,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
* {@inheritdoc}
*/
public function processedCount() {
return $this->getDatabase()->select($this->mapTableName())
return (int) $this->getDatabase()->select($this->mapTableName())
->countQuery()
->execute()
->fetchField();
@@ -703,7 +706,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
* {@inheritdoc}
*/
public function importedCount() {
return $this->getDatabase()->select($this->mapTableName())
return (int) $this->getDatabase()->select($this->mapTableName())
->condition('source_row_status', [MigrateIdMapInterface::STATUS_IMPORTED, MigrateIdMapInterface::STATUS_NEEDS_UPDATE], 'IN')
->countQuery()
->execute()
@@ -747,7 +750,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
if (isset($status)) {
$query->condition('source_row_status', $status);
}
return $query->countQuery()->execute()->fetchField();
return (int) $query->countQuery()->execute()->fetchField();
}
/**
@@ -925,4 +928,69 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
return $this->currentRow !== FALSE;
}
/**
* Returns the migration plugin manager.
*
* @todo Inject as a dependency in https://www.drupal.org/node/2919158.
*
* @return \Drupal\migrate\Plugin\MigrationPluginManagerInterface
* The migration plugin manager.
*/
protected function getMigrationPluginManager() {
return \Drupal::service('plugin.manager.migration');
}
/**
* {@inheritdoc}
*/
public function getHighestId() {
array_filter(
$this->migration->getDestinationPlugin()->getIds(),
function (array $id) {
if ($id['type'] !== 'integer') {
throw new \LogicException('Cannot determine the highest migrated ID without an integer ID column');
}
}
);
// List of mapping tables to look in for the highest ID.
$map_tables = [
$this->migration->id() => $this->mapTableName(),
];
// If there's a bundle, it means we have a derived migration and we need to
// find all the mapping tables from the related derived migrations.
if ($base_id = substr($this->migration->id(), 0, strpos($this->migration->id(), static::DERIVATIVE_SEPARATOR))) {
$migration_manager = $this->getMigrationPluginManager();
$migrations = $migration_manager->getDefinitions();
foreach ($migrations as $migration_id => $migration) {
if ($migration['id'] === $base_id) {
// Get this derived migration's mapping table and add it to the list
// of mapping tables to look in for the highest ID.
$stub = $migration_manager->createInstance($migration_id);
$map_tables[$migration_id] = $stub->getIdMap()->mapTableName();
}
}
}
// Get the highest id from the list of map tables.
$ids = [0];
foreach ($map_tables as $map_table) {
if (!$this->getDatabase()->schema()->tableExists($map_table)) {
break;
}
$query = $this->getDatabase()->select($map_table, 'map')
->fields('map', $this->destinationIdFields())
->range(0, 1);
foreach (array_values($this->destinationIdFields()) as $order_field) {
$query->orderBy($order_field, 'DESC');
}
$ids[] = $query->execute()->fetchField();
}
// Return the highest of all the mapped IDs.
return (int) max($ids);
}
}
@@ -15,13 +15,32 @@ use Drupal\migrate\Row;
* - 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: String identifying the required time zone, see
* - 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 (DATETIME_DATE_STORAGE_FORMAT):
* Example usage for date only fields
* (DateTimeItemInterface::DATE_STORAGE_FORMAT):
* @code
* process:
* field_date:
@@ -34,7 +53,8 @@ use Drupal\migrate\Row;
* If the source value was '01/05/1955' the transformed value would be
* 1955-01-05.
*
* Example usage for datetime fields (DATETIME_DATETIME_STORAGE_FORMAT):
* Example usage for datetime fields
* (DateTimeItemInterface::DATETIME_STORAGE_FORMAT):
* @code
* process:
* field_time:
@@ -54,7 +74,8 @@ use Drupal\migrate\Row;
* plugin: format_date
* from_format: 'Y-m-d\TH:i:sO'
* to_format: 'Y-m-d\TH:i:s'
* timezone: 'America/Managua'
* from_timezone: 'America/Managua'
* to_timezone: 'UTC'
* settings:
* validate_format: false
* source: event_time
@@ -65,6 +86,7 @@ use Drupal\migrate\Row;
*
* @see \DateTime::createFromFormat()
* @see \Drupal\Component\Datetime\DateTimePlus::__construct()
* @see \Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
*
* @MigrateProcessPlugin(
@@ -91,14 +113,24 @@ class FormatDate extends ProcessPluginBase {
$fromFormat = $this->configuration['from_format'];
$toFormat = $this->configuration['to_format'];
$timezone = isset($this->configuration['timezone']) ? $this->configuration['timezone'] : NULL;
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, $timezone, $settings)->format($toFormat);
$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);
@@ -33,7 +33,7 @@ use Drupal\migrate\Row;
* bar: foo
* @endcode
*
* get also supports a list of source properties.
* Get also supports a list of source properties.
*
* Example:
*
@@ -53,7 +53,7 @@ use Drupal\migrate\Row;
* 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
* Get also supports copying destination values. These are indicated by a
* starting @ sign. Values using @ must be wrapped in quotes.
*
* @code
@@ -69,20 +69,18 @@ use Drupal\migrate\Row;
* 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 your source or destination property actually starts with
* a @ you need to double those starting characters up. This means that if a
* destination property happens to start with a @ and you want to refer it,
* you'll need to start with three @ characters -- one to indicate the
* destination and two for escaping the real @.
* 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'
* '@foo':
* plugin: machine_name
* source: baz
* bar:
* plugin: get
* source: '@@@foo'
* @endcode
*
* This should occur extremely rarely.
@@ -2,8 +2,7 @@
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);
@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.
@@ -2,8 +2,7 @@
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);
@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.
@@ -34,10 +34,12 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*
* Examples:
*
* Consider a node migration, where you want to maintain authorship. If you have
* migrated the user accounts in a migration named "users", you would specify
* the following:
*
* 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:
@@ -46,15 +48,10 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* source: author
* @endcode
*
* This takes the value of the author property in the source data, and looks it
* up in the map table associated with the users migration, returning the
* resulting user ID and assigning it to the destination uid property.
*
* 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.
*
* and the value is a list of source properties. See example below.
* @code
* process:
* uid:
@@ -73,8 +70,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* 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:
*
* 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:
@@ -85,12 +82,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* stub_id: members
* @endcode
*
* In the above example, the value of stub_id selects the members migration to
* create any stub entities.
*
* To prevent the creation of a stub entity when no relationship is found in the
* migration map, use no_stub:
*
* migration map, 'no_stub' configuration can be used as shown below.
* @code
* process:
* uid:
@@ -161,10 +154,6 @@ class MigrationLookup extends ProcessPluginBase implements ContainerFactoryPlugi
if (!is_array($migration_ids)) {
$migration_ids = [$migration_ids];
}
if (!is_array($value)) {
$value = [$value];
}
$this->skipOnEmpty($value);
$self = FALSE;
/** @var \Drupal\migrate\Plugin\MigrationInterface[] $migrations */
$destination_ids = NULL;
@@ -176,13 +165,15 @@ class MigrationLookup extends ProcessPluginBase implements ContainerFactoryPlugi
}
if (isset($this->configuration['source_ids'][$migration_id])) {
$configuration = ['source' => $this->configuration['source_ids'][$migration_id]];
$source_id_values[$migration_id] = $this->processPluginManager
$value = $this->processPluginManager
->createInstance('get', $configuration, $this->migration)
->transform(NULL, $migrate_executable, $row, $destination_property);
}
else {
$source_id_values[$migration_id] = $value;
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;
@@ -22,8 +22,8 @@ use Drupal\migrate\MigrateSkipRowException;
* - 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' skip level. If not
* set, nothing is logged in the message table.
* for this row. Messages are only logged for the 'row' method. If not set,
* nothing is logged in the message table.
*
* Examples:
*
@@ -33,11 +33,10 @@ use Drupal\migrate\MigrateSkipRowException;
* plugin: skip_on_empty
* method: row
* source: field_name
* message: 'Field field_name is missed'
* message: 'Field field_name is missing'
* @endcode
*
* If field_name is empty, skips the entire row and the message 'Field
* field_name is missed' is logged in the message table.
* 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:
@@ -47,12 +46,13 @@ use Drupal\migrate\MigrateSkipRowException;
* method: process
* source: parent
* -
* plugin: migration
* plugin: migration_lookup
* migration: d6_taxonomy_term
* @endcode
*
* If parent is empty, any further processing of the property is skipped - thus,
* the next plugin (migration) will not be run.
* 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
*
@@ -17,10 +17,10 @@ use Drupal\migrate\MigrateSkipRowException;
*
* Available configuration keys:
* - source: The input value - either a scalar or an array.
* - map: An array (of 1 or more dimensions) that identifies the mapping between
* - 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.
* 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.
@@ -29,6 +29,8 @@ use Drupal\migrate\MigrateSkipRowException;
*
* 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:
@@ -39,68 +41,75 @@ use Drupal\migrate\MigrateSkipRowException;
* this: that
* @endcode
*
* If the value of the source property foo was "from" then the value of the
* destination property bar will be "to". Similarly "this" becomes "that".
* static_map can do a lot more than this: it supports a list of source
* properties. This is super useful in module-delta to machine name conversions.
*
* 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
* 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
*
* If the value of the source properties module and delta are "filter" and "2"
* respectively, then the returned value will be "filter_url". By default, if a
* value is not found in the map, an exception is thrown.
*
* When static_map is used to just rename a few things and leave the others, a
* "bypass: true" option can be added. In this case, the source value is used
* unchanged, e.g.:
*
* 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
* map:
* from: to
* this: that
* bypass: TRUE
* @endcode
*
* If the value of the source property "foo" is "from" then the returned value
* will be "to", but if the value of "foo" is "another" (a value that is not in
* the map) then the source value is used unchanged so the returned value will
* be "from" because "bypass" is set to TRUE.
*
* 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
* map:
* from: to
* this: that
* default_value: bar
* @endcode
*
* If the value of the source property "foo" is "yet_another" (a value that is
* not in the map) then the default_value is used so the returned value will
* be "bar".
* 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(
@@ -18,8 +18,10 @@ use Drupal\migrate\Row;
* - key: runs the process pipeline for the key to determine a new dynamic
* name.
*
* Examples:
* Example 1:
*
* This example demonstrates how migration_lookup process plugin can be applied
* on the following source data.
* @code
* source: Array
* (
@@ -41,10 +43,8 @@ use Drupal\migrate\Row;
* )
* ...
* @endcode
*
* The sub_process process plugin will take these arrays one at a time and run
* its own process over each one:
*
* its own process for each of them:
* @code
* process:
* upload:
@@ -58,16 +58,17 @@ use Drupal\migrate\Row;
* display: list
* description: description
* @endcode
*
* In this case, each item in the upload array will be processed by the
* sub_process process plugin. The target_id will be found by looking up the
* destination value from a previous migration. The display and description
* fields will simply be mapped.
* destination value from a previous migration using the migration_lookup
* process plugin. The display and description fields will be mapped directly.
*
* In the next example, normally the array returned from sub_process will have
* its original keys. If you need to change the key, it is possible for the
* returned array to be keyed by one of the transformed values in the sub-array.
* Example 2.
*
* Drupal 6 filter formats contain a list of filters belonging to that format
* identified by a numeric delta. A delta of 1 indicates automatic linebreaks,
* delta of 2 indicates the URL filter and so on. This example demonstrates how
* static_map process plugin can be applied on the following source data.
* @code
* source: Array
* (
@@ -91,7 +92,52 @@ use Drupal\migrate\Row;
* )
* )
* ...
* @endcode
* The sub_process will take these arrays one at a time and run its own process
* for each of them:
* @code
* process:
* filters:
* plugin: sub_process
* source: filters
* 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
* The example above means that we take each array element ([0], [1], etc.) from
* the source filters field and apply the static_map plugin on it. Let's have a
* closer look at the first array at index 0:
* @code
* Array
* (
* [module] => filter
* [delta] => 2
* [weight] => 0
* )
* @endcode
* The static_map process plugin results to value 'filter_url' for this input
* based on the 'module' and 'delta' map.
*
* Example 3.
*
* Normally the array returned from sub_process will have its original keys. If
* you need to change the key, it is possible for the returned array to be keyed
* by one of the transformed values in the sub-array. For the same source data
* used in the previous example, the migration below would result to keys
* 'filter_2' and 'filter_0'.
* @code
* process:
* filters:
* plugin: sub_process
@@ -106,9 +152,8 @@ use Drupal\migrate\Row;
* delimiter: _
* @endcode
*
* In the above example, the keys of the returned array would be filter_2 and
* filter_0
*
* @see \Drupal\migrate\Plugin\migrate\process\MigrationLookup
* @see \Drupal\migrate\Plugin\migrate\process\StaticMap
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
*
* @MigrateProcessPlugin(
@@ -12,19 +12,19 @@ use Drupal\Component\Utility\Unicode;
* Returns a substring of the input value.
*
* The substr process plugin returns the portion of the input value specified by
* the start and length parameters. This is a wrapper around the PHP substr()
* function.
* the start and length parameters. This is a wrapper around
* \Drupal\Component\Utility\Unicode::substr().
*
* Available configuration keys:
* - start: (optional) The returned string will start this many characters after
* the beginning of the string. Defaults to NULL.
* the beginning of the string, defaults to 0.
* - length: (optional) The maximum number of characters in the returned
* string. Defaults to NULL.
* string, defaults to NULL.
*
* If start is NULL and length is an integer, the start position is the
* If start is 0 and length is an integer, the start position is the
* beginning of the string. If start is an integer and length is NULL, the
* substring starting from the start position until the end of the string will
* be returned. If both start and length are NULL the entire string is returned.
* be returned. If start is 0 and length is NULL the entire string is returned.
*
* Example:
*
@@ -33,19 +33,33 @@ use Drupal\Component\Utility\Unicode;
* new_text_field:
* plugin: substr
* source: some_text_field
* start: 6
* length: 10
* start: 6
* length: 10
* @endcode
*
* If some_text_field was 'Marie Skłodowska Curie' then
* $destination['new_text_field'] would be 'Skłodowska'.
*
* The PHP equivalent of this is:
*
* @code
* $destination['new_text_field'] = substr($source['some_text_field'], 6, 10);
* @endcode
*
* The substr plugin requires that the source value is not empty. If empty
* values are expected, combine skip_on_empty process plugin to the pipeline:
* @code
* process:
* new_text_field:
* -
* plugin: skip_on_empty
* method: process
* source: some_text_field
* -
* plugin: substr
* source: some_text_field
* start: 6
* length: 10
* @endcode
*
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
*
* @MigrateProcessPlugin(
@@ -38,7 +38,8 @@ use Drupal\migrate\Plugin\MigrationInterface;
* @see \Drupal\migrate\Plugin\MigrateSourceInterface
*
* @MigrateSource(
* id = "embedded_data"
* id = "embedded_data",
* source_module = "migrate"
* )
*/
class EmbeddedDataSource extends SourcePluginBase {
@@ -108,7 +109,7 @@ class EmbeddedDataSource extends SourcePluginBase {
/**
* {@inheritdoc}
*/
public function count() {
public function count($refresh = FALSE) {
return count($this->dataRows);
}
@@ -58,7 +58,7 @@ class EmptySource extends SourcePluginBase {
/**
* {@inheritdoc}
*/
public function count() {
public function count($refresh = FALSE) {
return 1;
}
@@ -216,8 +216,8 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
/**
* Initializes the iterator with the source data.
*
* @return array
* An array of the data for this source.
* @return \Iterator
* Returns an iteratable object of data for this source.
*/
abstract protected function initializeIterator();
@@ -346,7 +346,9 @@ abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPlugi
if (($this->batchSize > 0)) {
$this->query->range($this->batch * $this->batchSize, $this->batchSize);
}
return new \IteratorIterator($this->query->execute());
$statement = $this->query->execute();
$statement->setFetchMode(\PDO::FETCH_ASSOC);
return new \IteratorIterator($statement);
}
/**
@@ -379,7 +381,7 @@ abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPlugi
* {@inheritdoc}
*/
public function count($refresh = FALSE) {
return $this->query()->countQuery()->execute()->fetchField();
return (int) $this->query()->countQuery()->execute()->fetchField();
}
/**
+1 -1
View File
@@ -104,7 +104,7 @@ class Row {
$this->isStub = $is_stub;
foreach (array_keys($source_ids) as $id) {
if (!$this->hasSourceProperty($id)) {
throw new \InvalidArgumentException("$id has no value");
throw new \InvalidArgumentException("$id is defined as a source ID but has no value.");
}
}
}
@@ -5,8 +5,8 @@ package: Testing
# version: VERSION
# core: 8.x
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
# Information added by Drupal.org packaging script on 2018-03-07
version: '8.5.0'
core: '8.x'
project: 'drupal'
datestamp: 1515021228
datestamp: 1520457825
@@ -4,8 +4,8 @@ package: Testing
# version: VERSION
# core: 8.x
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
# Information added by Drupal.org packaging script on 2018-03-07
version: '8.5.0'
core: '8.x'
project: 'drupal'
datestamp: 1515021228
datestamp: 1520457825
@@ -7,8 +7,8 @@ dependencies:
- node
- migrate
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
# Information added by Drupal.org packaging script on 2018-03-07
version: '8.5.0'
core: '8.x'
project: 'drupal'
datestamp: 1515021228
datestamp: 1520457825
@@ -6,8 +6,8 @@ package: Testing
dependencies:
- migrate
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
# Information added by Drupal.org packaging script on 2018-03-07
version: '8.5.0'
core: '8.x'
project: 'drupal'
datestamp: 1515021228
datestamp: 1520457825
@@ -5,8 +5,8 @@ package: Testing
# version: VERSION
# core: 8.x
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
# Information added by Drupal.org packaging script on 2018-03-07
version: '8.5.0'
core: '8.x'
project: 'drupal'
datestamp: 1515021228
datestamp: 1520457825
@@ -6,8 +6,8 @@ package: Testing
dependencies:
- migrate
# Information added by Drupal.org packaging script on 2018-01-03
version: '8.4.4'
# Information added by Drupal.org packaging script on 2018-03-07
version: '8.5.0'
core: '8.x'
project: 'drupal'
datestamp: 1515021228
datestamp: 1520457825
@@ -0,0 +1,13 @@
name: 'Migration directory test'
type: module
package: Testing
# version: VERSION
# core: 8.x
dependencies:
- migrate
# Information added by Drupal.org packaging script on 2018-03-07
version: '8.5.0'
core: '8.x'
project: 'drupal'
datestamp: 1520457825
@@ -0,0 +1,8 @@
id: migration_templates_test
label: Migration templates test
source:
plugin: embedded_data
process:
id: id
destination:
plugin: null
@@ -3,7 +3,6 @@
namespace Drupal\Tests\migrate\Functional\process;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate\MigrateMessage;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\Tests\BrowserTestBase;
@@ -51,7 +50,7 @@ class DownloadFunctionalTest extends BrowserTestBase {
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$executable = new MigrateExecutable($migration, new MigrateMessage());
$executable = new MigrateExecutable($migration);
$result = $executable->import();
// Check that the migration has completed.
@@ -2,10 +2,10 @@
namespace Drupal\Tests\migrate\Kernel;
use Drupal\entity_test\Entity\EntityTestMul;
use Drupal\KernelTests\KernelTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate\MigrateMessage;
use Drupal\migrate\Plugin\migrate\destination\EntityContentBase;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
use Drupal\migrate\Plugin\MigrationInterface;
@@ -45,6 +45,11 @@ class MigrateEntityContentBaseTest extends KernelTestBase {
*/
protected function setUp() {
parent::setUp();
// Enable two required fields with default values: a single-value field and
// a multi-value field.
\Drupal::state()->set('entity_test.required_default_field', TRUE);
\Drupal::state()->set('entity_test.required_multi_default_field', TRUE);
$this->installEntitySchema('entity_test_mul');
ConfigurableLanguage::createFromLangcode('en')->save();
@@ -191,7 +196,7 @@ class MigrateEntityContentBaseTest extends KernelTestBase {
];
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$executable = new MigrateExecutable($migration, new MigrateMessage());
$executable = new MigrateExecutable($migration);
$result = $executable->import();
$this->assertEquals(MigrationInterface::RESULT_COMPLETED, $result);
@@ -239,7 +244,7 @@ class MigrateEntityContentBaseTest extends KernelTestBase {
$migration = \Drupal::service('plugin.manager.migration')
->createStubMigration($definition);
$executable = new MigrateExecutable($migration, new MigrateMessage());
$executable = new MigrateExecutable($migration);
$executable->import();
/** @var \Drupal\migrate_entity_test\Entity\StringIdEntityTest $entity */
@@ -256,7 +261,7 @@ class MigrateEntityContentBaseTest extends KernelTestBase {
$migration = \Drupal::service('plugin.manager.migration')
->createStubMigration($definition);
$executable = new MigrateExecutable($migration, new MigrateMessage());
$executable = new MigrateExecutable($migration);
$executable->import();
/** @var \Drupal\migrate_entity_test\Entity\StringIdEntityTest $entity */
@@ -266,4 +271,34 @@ class MigrateEntityContentBaseTest extends KernelTestBase {
$this->assertNull($entity->version->value);
}
/**
* Tests stub rows.
*/
public function testStubRows() {
// Create a destination.
$this->createDestination([]);
// Import a stub row.
$row = new Row([], [], TRUE);
$row->setDestinationProperty('type', 'test');
$ids = $this->destination->import($row);
$this->assertCount(1, $ids);
// Make sure the entity was saved.
$entity = EntityTestMul::load(reset($ids));
$this->assertInstanceOf(EntityTestMul::class, $entity);
// Make sure the default value was applied to the required fields.
$single_field_name = 'required_default_field';
$single_default_value = $entity->getFieldDefinition($single_field_name)->getDefaultValueLiteral();
$this->assertSame($single_default_value, $entity->get($single_field_name)->getValue());
$multi_field_name = 'required_multi_default_field';
$multi_default_value = $entity->getFieldDefinition($multi_field_name)->getDefaultValueLiteral();
$count = 3;
$this->assertCount($count, $multi_default_value);
for ($i = 0; $i < $count; ++$i) {
$this->assertSame($multi_default_value[$i], $entity->get($multi_field_name)->get($i)->getValue());
}
}
}
@@ -7,7 +7,6 @@ use Drupal\migrate\Event\MigrateMapDeleteEvent;
use Drupal\migrate\Event\MigrateMapSaveEvent;
use Drupal\migrate\Event\MigratePostRowSaveEvent;
use Drupal\migrate\Event\MigratePreRowSaveEvent;
use Drupal\migrate\MigrateMessage;
use Drupal\migrate\Event\MigrateEvents;
use Drupal\migrate\MigrateExecutable;
use Drupal\KernelTests\KernelTestBase;
@@ -76,7 +75,7 @@ class MigrateEventsTest extends KernelTestBase {
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$executable = new MigrateExecutable($migration, new MigrateMessage());
$executable = new MigrateExecutable($migration);
// As the import runs, events will be dispatched, recording the received
// information in state.
$executable->import();
@@ -3,7 +3,6 @@
namespace Drupal\Tests\migrate\Kernel;
use Drupal\migrate\Event\MigratePostRowSaveEvent;
use Drupal\migrate\MigrateMessage;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Event\MigrateEvents;
use Drupal\migrate\MigrateExecutable;
@@ -56,7 +55,7 @@ class MigrateInterruptionTest extends KernelTestBase {
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$executable = new MigrateExecutable($migration, new MigrateMessage());
$executable = new MigrateExecutable($migration);
// When the import runs, the first row imported will trigger an
// interruption.
$result = $executable->import();
@@ -3,7 +3,6 @@
namespace Drupal\Tests\migrate\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate\MigrateMessage;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
@@ -50,7 +49,7 @@ class MigrateSkipRowTest extends KernelTestBase {
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$executable = new MigrateExecutable($migration, new MigrateMessage());
$executable = new MigrateExecutable($migration);
$result = $executable->import();
$this->assertEqual($result, MigrationInterface::RESULT_COMPLETED);
@@ -85,7 +84,7 @@ class MigrateSkipRowTest extends KernelTestBase {
];
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$executable = new MigrateExecutable($migration, new MigrateMessage());
$executable = new MigrateExecutable($migration);
$result = $executable->import();
$this->assertEquals($result, MigrationInterface::RESULT_COMPLETED);
@@ -0,0 +1,32 @@
<?php
namespace Drupal\Tests\migrate\Kernel\Plugin;
use Drupal\Tests\migrate_drupal\Kernel\MigrateDrupalTestBase;
/**
* Tests that migrations exist in the migration_templates directory.
*
* @group migrate
* @group legacy
*/
class MigrationDirectoryTest extends MigrateDrupalTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['migration_directory_test'];
/**
* Tests that migrations in the migration_templates directory are created.
*
* @expectedDeprecationMessage 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.
*/
public function testMigrationDirectory() {
/** @var \Drupal\migrate\Plugin\MigrationPluginManager $plugin_manager */
$plugin_manager = $this->container->get('plugin.manager.migration');
// Tests that a migration in directory 'migration_templates' is discovered.
$this->assertTrue($plugin_manager->hasDefinition('migration_templates_test'));
}
}
@@ -2,8 +2,9 @@
namespace Drupal\Tests\migrate\Kernel\Plugin;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\KernelTests\FileSystemModuleDiscoveryDataProviderTrait;
use Drupal\migrate\Plugin\Exception\BadPluginDefinitionException;
use Drupal\migrate_drupal\Plugin\MigrateFieldPluginManager;
use Drupal\Tests\migrate_drupal\Kernel\MigrateDrupalTestBase;
/**
@@ -16,49 +17,215 @@ class MigrationProvidersExistTest extends MigrateDrupalTestBase {
use FileSystemModuleDiscoveryDataProviderTrait;
/**
* {@inheritdoc}
* Tests that a missing source_module property raises an exception.
*/
public static $modules = ['migration_provider_test'];
public function testSourceProvider() {
$this->enableModules(['migration_provider_test']);
$this->setExpectedException(BadPluginDefinitionException::class, 'The no_source_module plugin must define the source_module property.');
$this->container->get('plugin.manager.migration')->getDefinition('migration_provider_no_annotation');
}
/**
* Tests that modules exist for all source and destination plugins.
* Tests that modules exist for all source plugins.
*/
public function testProvidersExist() {
$this->enableAllModules();
/** @var \Drupal\migrate\Plugin\MigrationPluginManager $plugin_manager */
$plugin_manager = $this->container->get('plugin.manager.migration');
// Instantiate all migrations.
$migrations = array_keys($plugin_manager->getDefinitions());
$migrations = $plugin_manager->createInstances($migrations);
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
foreach ($migrations as $migration) {
$this->assertInternalType('string', $migration->getSourcePlugin()->getSourceModule());
}
}
/**
* Enable all available modules.
*/
protected function enableAllModules() {
// Install all available modules.
$module_handler = $this->container->get('module_handler');
$modules = $this->coreModuleListDataProvider();
$modules_enabled = $module_handler->getModuleList();
$modules_to_enable = array_keys(array_diff_key($modules, $modules_enabled));
$this->enableModules($modules_to_enable);
}
/** @var \Drupal\migrate\Plugin\MigrationPluginManager $plugin_manager */
$plugin_manager = $this->container->get('plugin.manager.migration');
// Get all the migrations
$migrations = $plugin_manager->createInstances(array_keys($plugin_manager->getDefinitions()));
// Ensure the test module was enabled.
$this->assertTrue(array_key_exists('migration_provider_test', $migrations));
$this->assertTrue(array_key_exists('migration_provider_no_annotation', $migrations));
/** @var \Drupal\migrate\Plugin\Migration $migration */
foreach ($migrations as $migration) {
$source_module = $migration->getSourcePlugin()->getSourceModule();
$destination_module = $migration->getDestinationPlugin()->getDestinationModule();
$migration_id = $migration->getPluginId();
if ($migration_id == 'migration_provider_test') {
$this->assertFalse($source_module, new FormattableMarkup('Source module not found for @migration_id.', ['@migration_id' => $migration_id]));
$this->assertFalse($destination_module, new FormattableMarkup('Destination module not found for @migration_id.', ['@migration_id' => $migration_id]));
}
elseif ($migration_id == 'migration_provider_no_annotation') {
$this->assertFalse($source_module, new FormattableMarkup('Source module not found for @migration_id.', ['@migration_id' => $migration_id]));
$this->assertTrue($destination_module, new FormattableMarkup('Destination module found for @migration_id.', ['@migration_id' => $migration_id]));
}
else {
$this->assertTrue($source_module, new FormattableMarkup('Source module found for @migration_id.', ['@migration_id' => $migration_id]));
$this->assertTrue($destination_module, new FormattableMarkup('Destination module found for @migration_id.', ['@migration_id' => $migration_id]));
}
// Destination module can't be migrate or migrate_drupal or migrate_drupal_ui
$invalid_destinations = ['migrate', 'migrate_drupal', 'migrate_drupal_ui'];
$this->assertNotContains($destination_module, $invalid_destinations, new FormattableMarkup('Invalid destination for @migration_id.', ['@migration_id' => $migration_id]));
/**
* Tests that modules exist for all field plugins.
*/
public function testFieldProvidersExist() {
$expected_mappings = [
'userreference' => [
'source_module' => 'userreference',
'destination_module' => 'core',
],
'nodereference' => [
'source_module' => 'nodereference',
'destination_module' => 'core',
],
'optionwidgets' => [
'source_module' => 'optionwidgets',
'destination_module' => 'options',
],
'list' => [
'source_module' => 'list',
'destination_module' => 'options',
],
'options' => [
'source_module' => 'options',
'destination_module' => 'options',
],
'filefield' => [
'source_module' => 'filefield',
'destination_module' => 'file',
],
'imagefield' => [
'source_module' => 'imagefield',
'destination_module' => 'image',
],
'file' => [
'source_module' => 'file',
'destination_module' => 'file',
],
'image' => [
'source_module' => 'image',
'destination_module' => 'image',
],
'phone' => [
'source_module' => 'phone',
'destination_module' => 'telephone',
],
'link' => [
'source_module' => 'link',
'destination_module' => 'link',
],
'link_field' => [
'source_module' => 'link',
'destination_module' => 'link',
],
'd6_text' => [
'source_module' => 'text',
'destination_module' => 'text',
],
'd7_text' => [
'source_module' => 'text',
'destination_module' => 'text',
],
'taxonomy_term_reference' => [
'source_module' => 'taxonomy',
'destination_module' => 'core',
],
'date' => [
'source_module' => 'date',
'destination_module' => 'datetime',
],
'datetime' => [
'source_module' => 'date',
'destination_module' => 'datetime',
],
'email' => [
'source_module' => 'email',
'destination_module' => 'core',
],
'number_default' => [
'source_module' => 'number',
'destination_module' => 'core',
],
'entityreference' => [
'source_module' => 'entityreference',
'destination_module' => 'core',
],
];
$this->enableAllModules();
$definitions = $this->container->get('plugin.manager.migrate.field')->getDefinitions();
foreach ($definitions as $key => $definition) {
$this->assertArrayHasKey($key, $expected_mappings);
$this->assertEquals($expected_mappings[$key]['source_module'], $definition['source_module']);
$this->assertEquals($expected_mappings[$key]['destination_module'], $definition['destination_module']);
}
}
/**
* Test a missing required definition.
*
* @param array $definitions
* A field plugin definition.
* @param string $missing_property
* The name of the property missing from the definition.
*
* @dataProvider fieldPluginDefinitionsProvider
*/
public function testFieldProviderMissingRequiredProperty(array $definitions, $missing_property) {
$discovery = $this->getMockBuilder(MigrateFieldPluginManager::class)
->disableOriginalConstructor()
->setMethods(['getDefinitions'])
->getMock();
$discovery->method('getDefinitions')
->willReturn($definitions);
$plugin_manager = $this->getMockBuilder(MigrateFieldPluginManager::class)
->disableOriginalConstructor()
->setMethods(['getDiscovery'])
->getMock();
$plugin_manager->method('getDiscovery')
->willReturn($discovery);
$this->setExpectedException(BadPluginDefinitionException::class, "The missing_{$missing_property} plugin must define the $missing_property property.");
$plugin_manager->getDefinitions();
}
/**
* Data provider for field plugin definitions.
*
* @return array
* Array of plugin definitions.
*/
public function fieldPluginDefinitionsProvider() {
return [
'missing_core_scenario' => [
'definitions' => [
'missing_core' => [
'source_module' => 'migrate',
'destination_module' => 'migrate',
'id' => 'missing_core',
'class' => 'foo',
'provider' => 'foo',
],
],
'missing_property' => 'core',
],
'missing_source_scenario' => [
'definitions' => [
'missing_source_module' => [
'core' => [6, 7],
'destination_module' => 'migrate',
'id' => 'missing_source_module',
'class' => 'foo',
'provider' => 'foo',
],
],
'missing_property' => 'source_module',
],
'missing_destination_scenario' => [
'definitions' => [
'missing_destination_module' => [
'core' => [6, 7],
'source_module' => 'migrate',
'id' => 'missing_destination_module',
'class' => 'foo',
'provider' => 'foo',
],
],
'missing_property' => 'destination_module',
],
];
}
}
@@ -9,6 +9,7 @@ namespace Drupal\Tests\migrate\Kernel;
use Drupal\Core\Database\Query\ConditionInterface;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Database\StatementInterface;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\Core\Database\Database;
use Drupal\migrate\Plugin\migrate\source\SqlBase;
@@ -46,8 +47,8 @@ class SqlBaseTest extends MigrateTestBase {
// Verify that falling back to the default 'migrate' connection (defined in
// the base class) works.
$this->assertSame($sql_base->getDatabase()->getTarget(), 'default');
$this->assertSame($sql_base->getDatabase()->getKey(), 'migrate');
$this->assertSame('default', $sql_base->getDatabase()->getTarget());
$this->assertSame('migrate', $sql_base->getDatabase()->getKey());
// Verify the fallback state key overrides the 'migrate' connection.
$target = 'test_fallback_target';
@@ -149,10 +150,10 @@ class SqlBaseTest extends MigrateTestBase {
$source->getHighWaterStorage()->set($this->migration->id(), $high_water);
}
$query_result = new \ArrayIterator($query_result);
$query = $this->getMock(SelectInterface::class);
$query->method('execute')->willReturn($query_result);
$statement = $this->createMock(StatementInterface::class);
$statement->expects($this->atLeastOnce())->method('setFetchMode')->with(\PDO::FETCH_ASSOC);
$query = $this->createMock(SelectInterface::class);
$query->method('execute')->willReturn($statement);
$query->expects($this->atLeastOnce())->method('orderBy')->with('order', 'ASC');
$condition_group = $this->getMock(ConditionInterface::class);
@@ -9,7 +9,6 @@ use Drupal\migrate\Plugin\migrate\process\Download;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Response;
/**
* Tests the download process plugin.
@@ -100,14 +99,8 @@ class DownloadTest extends FileTestBase {
* The local URI of the downloaded file.
*/
protected function doTransform($destination_uri, $configuration = []) {
// The HTTP client will return a file with contents 'It worked!'
$body = fopen('data://text/plain;base64,SXQgd29ya2VkIQ==', 'r');
// Prepare a mock HTTP client.
$this->container->set('http_client', $this->getMock(Client::class));
$this->container->get('http_client')
->method('get')
->willReturn(new Response(200, [], $body));
// Instantiate the plugin statically so it can pull dependencies out of
// the container.
@@ -4,7 +4,6 @@ namespace Drupal\Tests\migrate\Kernel\process;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate\MigrateMessage;
use Drupal\migrate\Plugin\MigrationInterface;
/**
@@ -66,7 +65,7 @@ class ExtractTest extends KernelTestBase {
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$executable = new MigrateExecutable($migration, new MigrateMessage());
$executable = new MigrateExecutable($migration);
$result = $executable->import();
// Migration needs to succeed before further assertions are made.
@@ -4,7 +4,6 @@ namespace Drupal\Tests\migrate\Kernel\process;
use Drupal\KernelTests\KernelTestBase;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate\MigrateMessage;
use Drupal\migrate\Plugin\MigrationInterface;
/**
@@ -94,7 +93,7 @@ class HandleMultiplesTest extends KernelTestBase {
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition);
$executable = new MigrateExecutable($migration, new MigrateMessage());
$executable = new MigrateExecutable($migration);
$result = $executable->import();
// Migration needs to succeed before further assertions are made.
@@ -7,7 +7,7 @@ use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\migrate\Exception\RequirementsException
* @group migration
* @group migrate
*/
class RequirementsExceptionTest extends UnitTestCase {
@@ -211,10 +211,10 @@ class MigrateSqlIdMapTest extends MigrateTestCase {
}
// Truncate and check that 4 messages were deleted.
$this->assertEquals($id_map->messageCount(), 4);
$this->assertSame($id_map->messageCount(), 4);
$id_map->clearMessages();
$count = $id_map->messageCount();
$this->assertEquals($count, 0);
$this->assertSame($count, 0);
}
/**
@@ -284,7 +284,7 @@ class MigrateSqlIdMapTest extends MigrateTestCase {
// Test count message multiple times starting from 0.
foreach ($expected_results as $key => $expected_result) {
$count = $id_map->messageCount();
$this->assertEquals($expected_result, $count);
$this->assertSame($expected_result, $count);
$id_map->saveMessage(['source_id_property' => $key], $message);
}
}
@@ -684,21 +684,21 @@ class MigrateSqlIdMapTest extends MigrateTestCase {
$row = new Row($source, ['source_id_property' => []]);
$destination = ['destination_id_property' => 'destination_value_failed'];
$id_map->saveIdMapping($row, $destination, MigrateIdMapInterface::STATUS_FAILED);
$this->assertSame(0, (int) $id_map->importedCount());
$this->assertSame(0, $id_map->importedCount());
// Add an imported row and assert single count.
$source = ['source_id_property' => 'source_value_imported'];
$row = new Row($source, ['source_id_property' => []]);
$destination = ['destination_id_property' => 'destination_value_imported'];
$id_map->saveIdMapping($row, $destination, MigrateIdMapInterface::STATUS_IMPORTED);
$this->assertSame(1, (int) $id_map->importedCount());
$this->assertSame(1, $id_map->importedCount());
// Add a row needing update and assert multiple imported rows.
$source = ['source_id_property' => 'source_value_update'];
$row = new Row($source, ['source_id_property' => []]);
$destination = ['destination_id_property' => 'destination_value_update'];
$id_map->saveIdMapping($row, $destination, MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
$this->assertSame(2, (int) $id_map->importedCount());
$this->assertSame(2, $id_map->importedCount());
}
/**
@@ -712,7 +712,7 @@ class MigrateSqlIdMapTest extends MigrateTestCase {
public function testProcessedCount() {
$id_map = $this->getIdMap();
// Assert zero rows have been processed before adding rows.
$this->assertSame(0, (int) $id_map->processedCount());
$this->assertSame(0, $id_map->processedCount());
$row_statuses = [
MigrateIdMapInterface::STATUS_IMPORTED,
MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
@@ -727,11 +727,11 @@ class MigrateSqlIdMapTest extends MigrateTestCase {
$id_map->saveIdMapping($row, $destination, $status);
if ($status == MigrateIdMapInterface::STATUS_IMPORTED) {
// Assert a single row has been processed.
$this->assertSame(1, (int) $id_map->processedCount());
$this->assertSame(1, $id_map->processedCount());
}
}
// Assert multiple rows have been processed.
$this->assertSame(count($row_statuses), (int) $id_map->processedCount());
$this->assertSame(count($row_statuses), $id_map->processedCount());
}
/**
@@ -779,7 +779,7 @@ class MigrateSqlIdMapTest extends MigrateTestCase {
$this->saveMap($row);
}
$id_map = $this->getIdMap();
$this->assertSame($num_update_rows, (int) $id_map->updateCount());
$this->assertSame($num_update_rows, $id_map->updateCount());
}
/**
@@ -827,7 +827,7 @@ class MigrateSqlIdMapTest extends MigrateTestCase {
$this->saveMap($row);
}
$this->assertSame($num_error_rows, (int) $this->getIdMap()->errorCount());
$this->assertSame($num_error_rows, $this->getIdMap()->errorCount());
}
/**
@@ -78,11 +78,6 @@ abstract class MigrateTestCase extends UnitTestCase {
$configuration = &$this->migrationConfiguration;
$migration->method('getHighWaterProperty')
->willReturnCallback(function () use ($configuration) {
return isset($configuration['high_water_property']) ? $configuration['high_water_property'] : '';
});
$migration->method('set')
->willReturnCallback(function ($argument, $value) use (&$configuration) {
$configuration[$argument] = $value;
@@ -19,7 +19,7 @@ use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\migrate\Plugin\Migration
*
* @group Migration
* @group migrate
*/
class MigrationTest extends UnitTestCase {
@@ -8,9 +8,9 @@
namespace Drupal\Tests\migrate\Unit\Plugin\migrate\destination;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\ContentEntityType;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\migrate\MigrateException;
@@ -38,6 +38,11 @@ class EntityContentBaseTest extends UnitTestCase {
*/
protected $storage;
/**
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $entityType;
/**
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
@@ -51,6 +56,11 @@ class EntityContentBaseTest extends UnitTestCase {
$this->migration = $this->prophesize(MigrationInterface::class);
$this->storage = $this->prophesize(EntityStorageInterface::class);
$this->entityType = $this->prophesize(EntityTypeInterface::class);
$this->entityType->getPluralLabel()->willReturn('wonkiness');
$this->storage->getEntityType()->willReturn($this->entityType->reveal());
$this->entityManager = $this->prophesize(EntityManagerInterface::class);
}
@@ -104,14 +114,11 @@ class EntityContentBaseTest extends UnitTestCase {
*/
public function testUntranslatable() {
// An entity type without a language.
$entity_type = $this->prophesize(ContentEntityType::class);
$entity_type->getKey('langcode')->willReturn('');
$entity_type->getKey('id')->willReturn('id');
$this->entityType->getKey('langcode')->willReturn('');
$this->entityType->getKey('id')->willReturn('id');
$this->entityManager->getBaseFieldDefinitions('foo')
->willReturn(['id' => BaseFieldDefinitionTest::create('integer')]);
$this->storage->getEntityType()->willReturn($entity_type->reveal());
$destination = new EntityTestDestination(
['translations' => TRUE],
'',
@@ -9,6 +9,7 @@ namespace Drupal\Tests\migrate\Unit\destination;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Plugin\migrate\destination\EntityRevision as RealEntityRevision;
use Drupal\migrate\Row;
@@ -48,6 +49,12 @@ class EntityRevisionTest extends UnitTestCase {
// Setup mocks to be used when creating a revision destination.
$this->migration = $this->prophesize(MigrationInterface::class);
$this->storage = $this->prophesize('\Drupal\Core\Entity\EntityStorageInterface');
$entity_type = $this->prophesize(EntityTypeInterface::class);
$entity_type->getSingularLabel()->willReturn('crazy');
$entity_type->getPluralLabel()->willReturn('craziness');
$this->storage->getEntityType()->willReturn($entity_type->reveal());
$this->entityManager = $this->prophesize('\Drupal\Core\Entity\EntityManagerInterface');
$this->fieldTypeManager = $this->prophesize('\Drupal\Core\Field\FieldTypePluginManagerInterface');
}
@@ -44,8 +44,8 @@ class PerComponentEntityDisplayTest extends MigrateTestCase {
->method('save')
->with();
$plugin = new TestPerComponentEntityDisplay($entity);
$this->assertSame($plugin->import($row), ['entity_type_test', 'bundle_test', 'view_mode_test', 'field_name_test']);
$this->assertSame($plugin->getTestValues(), ['entity_type_test', 'bundle_test', 'view_mode_test']);
$this->assertSame(['entity_type_test', 'bundle_test', 'view_mode_test', 'field_name_test'], $plugin->import($row));
$this->assertSame(['entity_type_test', 'bundle_test', 'view_mode_test'], $plugin->getTestValues());
}
}
@@ -44,8 +44,8 @@ class PerComponentEntityFormDisplayTest extends MigrateTestCase {
->method('save')
->with();
$plugin = new TestPerComponentEntityFormDisplay($entity);
$this->assertSame($plugin->import($row), ['entity_type_test', 'bundle_test', 'form_mode_test', 'field_name_test']);
$this->assertSame($plugin->getTestValues(), ['entity_type_test', 'bundle_test', 'form_mode_test']);
$this->assertSame(['entity_type_test', 'bundle_test', 'form_mode_test', 'field_name_test'], $plugin->import($row));
$this->assertSame(['entity_type_test', 'bundle_test', 'form_mode_test'], $plugin->getTestValues());
}
}
@@ -30,7 +30,7 @@ class CallbackTest extends MigrateProcessTestCase {
public function testCallbackWithFunction() {
$this->plugin->setCallable('strtolower');
$value = $this->plugin->transform('FooBar', $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, 'foobar');
$this->assertSame('foobar', $value);
}
/**
@@ -39,7 +39,7 @@ class CallbackTest extends MigrateProcessTestCase {
public function testCallbackWithClassMethod() {
$this->plugin->setCallable(['\Drupal\Component\Utility\Unicode', 'strtolower']);
$value = $this->plugin->transform('FooBar', $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, 'foobar');
$this->assertSame('foobar', $value);
}
}
@@ -30,7 +30,7 @@ class ConcatTest extends MigrateProcessTestCase {
*/
public function testConcatWithoutDelimiter() {
$value = $this->plugin->transform(['foo', 'bar'], $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, 'foobar');
$this->assertSame('foobar', $value);
}
/**
@@ -47,7 +47,7 @@ class ConcatTest extends MigrateProcessTestCase {
public function testConcatWithDelimiter() {
$this->plugin->setDelimiter('_');
$value = $this->plugin->transform(['foo', 'bar'], $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, 'foo_bar');
$this->assertSame('foo_bar', $value);
}
}
@@ -29,7 +29,7 @@ class ExplodeTest extends MigrateProcessTestCase {
*/
public function testTransform() {
$value = $this->plugin->transform('foo,bar,tik', $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, ['foo', 'bar', 'tik']);
$this->assertSame(['foo', 'bar', 'tik'], $value);
}
/**
@@ -38,7 +38,7 @@ class ExplodeTest extends MigrateProcessTestCase {
public function testTransformLimit() {
$plugin = new Explode(['delimiter' => '_', 'limit' => 2], 'map', []);
$value = $plugin->transform('foo_bar_tik', $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, ['foo', 'bar_tik']);
$this->assertSame(['foo', 'bar_tik'], $value);
}
/**
@@ -49,7 +49,7 @@ class ExplodeTest extends MigrateProcessTestCase {
$concat = new Concat([], 'map', []);
$concatenated = $concat->transform($exploded, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($concatenated, 'foobartik');
$this->assertSame('foobartik', $concatenated);
}
/**
@@ -25,7 +25,7 @@ class ExtractTest extends MigrateProcessTestCase {
*/
public function testExtract() {
$value = $this->plugin->transform(['foo' => 'bar'], $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, 'bar');
$this->assertSame('bar', $value);
}
/**
@@ -50,7 +50,7 @@ class ExtractTest extends MigrateProcessTestCase {
public function testExtractFailDefault() {
$plugin = new Extract(['index' => ['foo'], 'default' => 'test'], 'map', []);
$value = $plugin->transform(['bar' => 'foo'], $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, 'test', '');
$this->assertSame('test', $value, '');
}
}
@@ -17,7 +17,7 @@ class FlattenTest extends MigrateProcessTestCase {
public function testFlatten() {
$plugin = new Flatten([], 'flatten', []);
$flattened = $plugin->transform([1, 2, [3, 4, [5]], [], [7, 8]], $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($flattened, [1, 2, 3, 4, 5, 7, 8]);
$this->assertSame([1, 2, 3, 4, 5, 7, 8], $flattened);
}
}
@@ -56,6 +56,40 @@ class FormatDateTest extends MigrateProcessTestCase {
$this->plugin->transform('January 5, 1955', $this->migrateExecutable, $this->row, 'field_date');
}
/**
* Tests that "timezone" configuration key triggers deprecation error.
*
* @covers ::transform
*
* @dataProvider providerTestDeprecatedTimezoneConfigurationKey
*
* @group legacy
* @expectedDeprecation 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
*/
public function testDeprecatedTimezoneConfigurationKey($configuration, $value, $expected) {
$this->plugin = new FormatDate($configuration, 'test_format_date', []);
$actual = $this->plugin->transform($value, $this->migrateExecutable, $this->row, 'field_date');
$this->assertEquals($expected, $actual);
}
/**
* Data provider for testDeprecatedTimezoneConfigurationKey.
*/
public function providerTestDeprecatedTimezoneConfigurationKey() {
return [
[
'configuration' => [
'from_format' => 'Y-m-d\TH:i:sO',
'to_format' => 'c e',
'timezone' => 'America/Managua',
],
'value' => '2004-12-19T10:19:42-0600',
'expected' => '2004-12-19T10:19:42-06:00 -06:00'
],
];
}
/**
* Tests transformation.
*
@@ -96,10 +130,10 @@ class FormatDateTest extends MigrateProcessTestCase {
'datetime_datetime' => [
'configuration' => [
'from_format' => 'm/d/Y H:i:s',
'to_format' => 'Y-m-d\TH:i:s',
'to_format' => 'Y-m-d\TH:i:s e',
],
'value' => '01/05/1955 10:43:22',
'expected' => '1955-01-05T10:43:22',
'expected' => '1955-01-05T10:43:22 Australia/Sydney',
],
'empty_values' => [
'configuration' => [
@@ -109,14 +143,37 @@ class FormatDateTest extends MigrateProcessTestCase {
'value' => '',
'expected' => '',
],
'timezone' => [
'timezone_from_to' => [
'configuration' => [
'from_format' => 'Y-m-d\TH:i:sO',
'to_format' => 'Y-m-d\TH:i:s',
'timezone' => 'America/Managua',
'from_format' => 'Y-m-d H:i:s',
'to_format' => 'Y-m-d H:i:s e',
'from_timezone' => 'America/Managua',
'to_timezone' => 'UTC',
],
'value' => '2004-12-19T10:19:42-0600',
'expected' => '2004-12-19T10:19:42',
'value' => '2004-12-19 10:19:42',
'expected' => '2004-12-19 16:19:42 UTC',
],
'timezone_from' => [
'configuration' => [
'from_format' => 'Y-m-d h:i:s',
'to_format' => 'Y-m-d h:i:s e',
'from_timezone' => 'America/Managua',
],
'value' => '2004-11-19 10:25:33',
// Unit tests use Australia/Sydney timezone, so date value will be
// converted from America/Managua to Australia/Sydney timezone.
'expected' => '2004-11-20 03:25:33 Australia/Sydney',
],
'timezone_to' => [
'configuration' => [
'from_format' => 'Y-m-d H:i:s',
'to_format' => 'Y-m-d H:i:s e',
'to_timezone' => 'America/Managua',
],
'value' => '2004-12-19 10:19:42',
// Unit tests use Australia/Sydney timezone, so date value will be
// converted from Australia/Sydney to America/Managua timezone.
'expected' => '2004-12-18 17:19:42 America/Managua',
],
];
}
@@ -34,7 +34,7 @@ class GetTest extends MigrateProcessTestCase {
->will($this->returnValue('source_value'));
$this->plugin->setSource('test');
$value = $this->plugin->transform(NULL, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, 'source_value');
$this->assertSame('source_value', $value);
}
/**
@@ -52,7 +52,7 @@ class GetTest extends MigrateProcessTestCase {
return $map[$argument];
}));
$value = $this->plugin->transform(NULL, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, ['source_value1', 'source_value2']);
$this->assertSame(['source_value1', 'source_value2'], $value);
}
/**
@@ -65,7 +65,7 @@ class GetTest extends MigrateProcessTestCase {
->will($this->returnValue('source_value'));
$this->plugin->setSource('@@test');
$value = $this->plugin->transform(NULL, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, 'source_value');
$this->assertSame('source_value', $value);
}
/**
@@ -85,7 +85,7 @@ class GetTest extends MigrateProcessTestCase {
return $map[$argument];
}));
$value = $this->plugin->transform(NULL, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, ['source_value1', 'source_value2', 'source_value3', 'source_value4']);
$this->assertSame(['source_value1', 'source_value2', 'source_value3', 'source_value4'], $value);
}
/**
@@ -75,10 +75,10 @@ class IteratorTest extends MigrateTestCase {
// values ended up in the proper destinations, and that the value of the
// key (@id) is the same as the destination ID (42).
$new_value = $plugin->transform($current_value, $migrate_executable, $row, 'test');
$this->assertSame(count($new_value), 1);
$this->assertSame(count($new_value[42]), 2);
$this->assertSame($new_value[42]['foo'], 'test');
$this->assertSame($new_value[42]['id'], 42);
$this->assertSame(1, count($new_value));
$this->assertSame(2, count($new_value[42]));
$this->assertSame('test', $new_value[42]['foo']);
$this->assertSame(42, $new_value[42]['id']);
}
}
@@ -11,6 +11,7 @@ use Drupal\migrate\Plugin\MigrateIdMapInterface;
use Drupal\migrate\Plugin\MigratePluginManager;
use Drupal\migrate\Plugin\MigrateSourceInterface;
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
use Drupal\migrate\Row;
use Prophecy\Argument;
/**
@@ -100,6 +101,8 @@ class MigrationLookupTest extends MigrateProcessTestCase {
'migration' => 'foobaz',
];
$migration_plugin->id()->willReturn(uniqid());
$migration_plugin_manager->createInstances(['foobaz'])
->willReturn(['foobaz' => $migration_plugin->reveal()]);
$migration = new MigrationLookup($configuration, 'migration_lookup', [], $migration_plugin->reveal(), $migration_plugin_manager->reveal(), $process_plugin_manager->reveal());
$this->setExpectedException(MigrateSkipProcessException::class);
$migration->transform(0, $this->migrateExecutable, $this->row, 'foo');
@@ -238,4 +241,61 @@ class MigrationLookupTest extends MigrateProcessTestCase {
$migration->transform(1, $this->migrateExecutable, $this->row, '');
}
/**
* Tests processing multiple source IDs.
*/
public function testMultipleSourceIds() {
$migration_plugin = $this->prophesize(MigrationInterface::class);
$migration_plugin_manager = $this->prophesize(MigrationPluginManagerInterface::class);
$process_plugin_manager = $this->prophesize(MigratePluginManager::class);
$foobaz_migration = $this->prophesize(MigrationInterface::class);
$get_migration = $this->prophesize(MigrationLookup::class);
$id_map = $this->prophesize(MigrateIdMapInterface::class);
$destination_plugin = $this->prophesize(MigrateDestinationInterface::class);
$source_plugin = $this->prophesize(MigrateSourceInterface::class);
$migration_plugin_manager->createInstances(['foobaz'])
->willReturn(['foobaz' => $foobaz_migration->reveal()]);
$process_plugin_manager->createInstance('get', ['source' => ['string_id', 'integer_id']], $migration_plugin->reveal())
->willReturn($get_migration->reveal());
$foobaz_migration->getIdMap()->willReturn($id_map->reveal());
$foobaz_migration->getDestinationPlugin(TRUE)->willReturn($destination_plugin->reveal());
$foobaz_migration->getProcess()->willReturn([]);
$foobaz_migration->getSourcePlugin()->willReturn($source_plugin->reveal());
$foobaz_migration->id()->willReturn('foobaz');
$foobaz_migration->getSourceConfiguration()->willReturn([]);
$get_migration->transform(NULL, $this->migrateExecutable, $this->row, 'foo')
->willReturn(['example_string', 99]);
$source_plugin_ids = [
'string_id' => [
'type' => 'string',
'max_length' => 128,
'is_ascii' => TRUE,
'alias' => 'wpt',
],
'integer_id' => [
'type' => 'integer',
'unsigned' => FALSE,
'alias' => 'wpt',
],
];
$stub_row = new Row(['string_id' => 'example_string', 'integer_id' => 99], $source_plugin_ids, TRUE);
$destination_plugin->import($stub_row)->willReturn([2]);
$source_plugin->getIds()->willReturn($source_plugin_ids);
$configuration = [
'migration' => 'foobaz',
'source_ids' => ['foobaz' => ['string_id', 'integer_id']],
];
$migration = new MigrationLookup($configuration, 'migration', [], $migration_plugin->reveal(), $migration_plugin_manager->reveal(), $process_plugin_manager->reveal());
$result = $migration->transform(NULL, $this->migrateExecutable, $this->row, 'foo');
$this->assertEquals(2, $result);
}
}
@@ -124,6 +124,8 @@ class MigrationTest extends MigrateProcessTestCase {
'migration' => 'foobaz',
];
$this->migration_plugin->id()->willReturn(uniqid());
$this->migration_plugin_manager->createInstances(['foobaz'])
->willReturn(['foobaz' => $this->migration_plugin->reveal()]);
$migration = new Migration($configuration, 'migration', [], $this->migration_plugin->reveal(), $this->migration_plugin_manager->reveal(), $this->process_plugin_manager->reveal());
$this->setExpectedException(MigrateSkipProcessException::class);
$migration->transform(0, $this->migrateExecutable, $this->row, 'foo');
@@ -31,7 +31,7 @@ class SkipOnEmptyTest extends MigrateProcessTestCase {
$configuration['method'] = 'process';
$value = (new SkipOnEmpty($configuration, 'skip_on_empty', []))
->transform(' ', $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, ' ');
$this->assertSame(' ', $value);
}
/**
@@ -51,7 +51,7 @@ class SkipOnEmptyTest extends MigrateProcessTestCase {
$configuration['method'] = 'row';
$value = (new SkipOnEmpty($configuration, 'skip_on_empty', []))
->transform(' ', $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, ' ');
$this->assertSame(' ', $value);
}
/**
@@ -27,7 +27,7 @@ class StaticMapTest extends MigrateProcessTestCase {
*/
public function testMapWithSourceString() {
$value = $this->plugin->transform('foo', $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, ['bar' => 'baz']);
$this->assertSame(['bar' => 'baz'], $value);
}
/**
@@ -35,7 +35,7 @@ class StaticMapTest extends MigrateProcessTestCase {
*/
public function testMapWithSourceList() {
$value = $this->plugin->transform(['foo', 'bar'], $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, 'baz');
$this->assertSame('baz', $value);
}
/**
@@ -62,7 +62,7 @@ class StaticMapTest extends MigrateProcessTestCase {
$configuration['default_value'] = 'test';
$this->plugin = new StaticMap($configuration, 'map', []);
$value = $this->plugin->transform(['bar'], $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, 'test');
$this->assertSame('test', $value);
}
/**
@@ -73,10 +73,10 @@ class SubProcessTest extends MigrateTestCase {
// values ended up in the proper destinations, and that the value of the
// key (@id) is the same as the destination ID (42).
$new_value = $plugin->transform($current_value, $migrate_executable, $row, 'test');
$this->assertSame(count($new_value), 1);
$this->assertSame(count($new_value[42]), 2);
$this->assertSame($new_value[42]['foo'], 'test');
$this->assertSame($new_value[42]['id'], 42);
$this->assertSame(1, count($new_value));
$this->assertSame(2, count($new_value[42]));
$this->assertSame('test', $new_value[42]['foo']);
$this->assertSame(42, $new_value[42]['id']);
}
}
@@ -4,7 +4,6 @@ namespace Drupal\Tests\migrate\Unit\process;
use Drupal\migrate\Plugin\migrate\process\UrlEncode;
use Drupal\migrate\MigrateExecutable;
use Drupal\migrate\MigrateMessage;
use Drupal\migrate\Row;
use Drupal\Tests\migrate\Unit\MigrateTestCase;
@@ -56,7 +55,7 @@ class UrlEncodeTest extends MigrateTestCase {
* Encoded URL.
*/
protected function doTransform($value) {
$executable = new MigrateExecutable($this->getMigration(), new MigrateMessage());
$executable = new MigrateExecutable($this->getMigration());
$row = new Row();
return (new UrlEncode([], 'urlencode', []))