Update Composer, update everything
This commit is contained in:
parent
ea3e94409f
commit
dda5c284b6
19527 changed files with 1135420 additions and 351004 deletions
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
name: Migrate
|
||||
type: module
|
||||
description: 'Handles migrations'
|
||||
package: Core (Experimental)
|
||||
package: Migration
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
|
|
|
@ -13,7 +13,7 @@ use Drupal\Component\Annotation\Plugin;
|
|||
* \Drupal\migrate\Plugin\migrate\destination\UrlAlias
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateDestinationInterface
|
||||
* @see \Drupal\migrate\Plugin\destination\DestinationBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\destination\DestinationBase
|
||||
* @see \Drupal\migrate\Plugin\MigrateDestinationPluginManager
|
||||
* @see \Drupal\migrate\Annotation\MigrateSource
|
||||
* @see \Drupal\migrate\Annotation\MigrateProcessPlugin
|
||||
|
@ -43,4 +43,15 @@ class MigrateDestination extends Plugin {
|
|||
*/
|
||||
public $requirements_met = TRUE;
|
||||
|
||||
/**
|
||||
* Identifies the system handling the data the destination plugin will write.
|
||||
*
|
||||
* The destination plugin itself determines how the value is used. For
|
||||
* example, Migrate Drupal's destination plugins expect destination_module to
|
||||
* be the name of a module that must be installed on the destination.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $destination_module;
|
||||
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ class MigrateProcessPlugin extends Plugin {
|
|||
* Whether the plugin handles multiples itself.
|
||||
*
|
||||
* Typically these plugins will expect an array as input and iterate over it
|
||||
* themselves, changing the whole array. For example the 'iterator' and the
|
||||
* themselves, changing the whole array. For example the 'sub_process' and the
|
||||
* 'flatten' plugins. If the plugin only need to change a single value it
|
||||
* can skip setting this attribute and let
|
||||
* \Drupal\migrate\MigrateExecutable::processRow() handle the iteration.
|
||||
|
|
|
@ -43,16 +43,15 @@ class MigrateSource extends Plugin implements MultipleProviderAnnotationInterfac
|
|||
/**
|
||||
* Identifies the system providing the data the source plugin will read.
|
||||
*
|
||||
* This can be any type, and the source plugin itself determines how the value
|
||||
* is used. For example, Migrate Drupal's source plugins expect
|
||||
* source_provider to be the name of a module that must be installed and
|
||||
* enabled in the source database.
|
||||
* The source plugin itself determines how the value is used. For example,
|
||||
* Migrate Drupal's source plugins expect source_module to be the name of a
|
||||
* module that must be installed and enabled in the source database.
|
||||
*
|
||||
* @see \Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase::checkRequirements
|
||||
*
|
||||
* @var mixed
|
||||
* @var string
|
||||
*/
|
||||
public $source_provider;
|
||||
public $source_module;
|
||||
|
||||
/**
|
||||
* Specifies the minimum version of the source provider.
|
||||
|
@ -60,7 +59,7 @@ class MigrateSource extends Plugin implements MultipleProviderAnnotationInterfac
|
|||
* This can be any type, and the source plugin itself determines how it is
|
||||
* used. For example, Migrate Drupal's source plugins expect this to be an
|
||||
* integer representing the minimum installed database schema version of the
|
||||
* module specified by source_provider.
|
||||
* module specified by source_module.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
|
|
27
web/core/modules/migrate/src/Audit/AuditException.php
Normal file
27
web/core/modules/migrate/src/Audit/AuditException.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Audit;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
|
||||
/**
|
||||
* Defines an exception to throw if an error occurs during a migration audit.
|
||||
*/
|
||||
class AuditException extends \RuntimeException {
|
||||
|
||||
/**
|
||||
* AuditException constructor.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration that caused the exception.
|
||||
* @param string $message
|
||||
* The reason the audit failed.
|
||||
* @param \Exception $previous
|
||||
* (optional) The previous exception.
|
||||
*/
|
||||
public function __construct(MigrationInterface $migration, $message, \Exception $previous = NULL) {
|
||||
$message = sprintf('Cannot audit migration %s: %s', $migration->id(), $message);
|
||||
parent::__construct($message, 0, $previous);
|
||||
}
|
||||
|
||||
}
|
146
web/core/modules/migrate/src/Audit/AuditResult.php
Normal file
146
web/core/modules/migrate/src/Audit/AuditResult.php
Normal file
|
@ -0,0 +1,146 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Audit;
|
||||
|
||||
use Drupal\Component\Render\MarkupInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
|
||||
/**
|
||||
* Encapsulates the result of a migration audit.
|
||||
*/
|
||||
class AuditResult implements MarkupInterface, \Countable {
|
||||
|
||||
/**
|
||||
* The audited migration.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationInterface
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* The result of the audit (TRUE if passed, FALSE otherwise).
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $status;
|
||||
|
||||
/**
|
||||
* The reasons why the migration passed or failed the audit.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $reasons = [];
|
||||
|
||||
/**
|
||||
* AuditResult constructor.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The audited migration.
|
||||
* @param bool $status
|
||||
* The result of the audit (TRUE if passed, FALSE otherwise).
|
||||
* @param string[] $reasons
|
||||
* (optional) The reasons why the migration passed or failed the audit.
|
||||
*/
|
||||
public function __construct(MigrationInterface $migration, $status, array $reasons = []) {
|
||||
if (!is_bool($status)) {
|
||||
throw new \InvalidArgumentException('Audit results must have a boolean status.');
|
||||
}
|
||||
$this->migration = $migration;
|
||||
$this->status = $status;
|
||||
array_walk($reasons, [$this, 'addReason']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the audited migration.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrationInterface
|
||||
* The audited migration.
|
||||
*/
|
||||
public function getMigration() {
|
||||
return $this->migration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the boolean result of the audit.
|
||||
*
|
||||
* @return bool
|
||||
* The result of the audit. TRUE if the migration passed the audit, FALSE
|
||||
* otherwise.
|
||||
*/
|
||||
public function passed() {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a reason why the migration passed or failed the audit.
|
||||
*
|
||||
* @param string|object $reason
|
||||
* The reason to add. Can be a string or a string-castable object.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addReason($reason) {
|
||||
array_push($this->reasons, (string) $reason);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a passing audit result for a migration.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The audited migration.
|
||||
* @param string[] $reasons
|
||||
* (optional) The reasons why the migration passed the audit.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function pass(MigrationInterface $migration, array $reasons = []) {
|
||||
return new static($migration, TRUE, $reasons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a failing audit result for a migration.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The audited migration.
|
||||
* @param array $reasons
|
||||
* (optional) The reasons why the migration failed the audit.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function fail(MigrationInterface $migration, array $reasons = []) {
|
||||
return new static($migration, FALSE, $reasons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Countable::count() for Twig template compatibility.
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
* @see \Drupal\Component\Render\MarkupInterface
|
||||
*/
|
||||
public function count() {
|
||||
return count($this->reasons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reasons the migration passed or failed, as a string.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @see \Drupal\Component\Render\MarkupInterface
|
||||
*/
|
||||
public function __toString() {
|
||||
return implode("\n", $this->reasons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reasons the migration passed or failed, for JSON serialization.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function jsonSerialize() {
|
||||
return $this->reasons;
|
||||
}
|
||||
|
||||
}
|
42
web/core/modules/migrate/src/Audit/AuditorInterface.php
Normal file
42
web/core/modules/migrate/src/Audit/AuditorInterface.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Audit;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for migration auditors.
|
||||
*
|
||||
* A migration auditor is a class which can examine a migration to determine if
|
||||
* it will cause conflicts with data already existing in the destination system.
|
||||
* What kind of auditing it does, and how it does it, is up to the implementing
|
||||
* class.
|
||||
*/
|
||||
interface AuditorInterface {
|
||||
|
||||
/**
|
||||
* Audits a migration.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration to audit.
|
||||
*
|
||||
* @throws \Drupal\migrate\Audit\AuditException
|
||||
* If the audit fails.
|
||||
*
|
||||
* @return \Drupal\migrate\Audit\AuditResult
|
||||
* The result of the audit.
|
||||
*/
|
||||
public function audit(MigrationInterface $migration);
|
||||
|
||||
/**
|
||||
* Audits a set of migrations.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface[] $migrations
|
||||
* The migrations to audit.
|
||||
*
|
||||
* @return \Drupal\migrate\Audit\AuditResult[]
|
||||
* The audit results, keyed by migration ID.
|
||||
*/
|
||||
public function auditMultiple(array $migrations);
|
||||
|
||||
}
|
26
web/core/modules/migrate/src/Audit/HighestIdInterface.php
Normal file
26
web/core/modules/migrate/src/Audit/HighestIdInterface.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Audit;
|
||||
|
||||
/**
|
||||
* Defines an interface for destination and ID maps which track a highest ID.
|
||||
*
|
||||
* When implemented by destination plugins, getHighestId() should return the
|
||||
* highest ID of the destination entity type that exists in the system. So, for
|
||||
* example, the entity:node plugin should return the highest node ID that
|
||||
* exists, regardless of whether it was created by a migration.
|
||||
*
|
||||
* When implemented by an ID map, getHighestId() should return the highest
|
||||
* migrated ID of the destination entity type.
|
||||
*/
|
||||
interface HighestIdInterface {
|
||||
|
||||
/**
|
||||
* Returns the highest ID tracked by the implementing plugin.
|
||||
*
|
||||
* @return int
|
||||
* The highest ID.
|
||||
*/
|
||||
public function getHighestId();
|
||||
|
||||
}
|
58
web/core/modules/migrate/src/Audit/IdAuditor.php
Normal file
58
web/core/modules/migrate/src/Audit/IdAuditor.php
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Audit;
|
||||
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
|
||||
/**
|
||||
* Audits migrations that create content entities in the destination system.
|
||||
*/
|
||||
class IdAuditor implements AuditorInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function audit(MigrationInterface $migration) {
|
||||
// If the migration does not opt into auditing, it passes.
|
||||
if (!$migration->isAuditable()) {
|
||||
return AuditResult::pass($migration);
|
||||
}
|
||||
|
||||
$interface = HighestIdInterface::class;
|
||||
|
||||
$destination = $migration->getDestinationPlugin();
|
||||
if (!$destination instanceof HighestIdInterface) {
|
||||
throw new AuditException($migration, "Destination does not implement $interface");
|
||||
}
|
||||
|
||||
$id_map = $migration->getIdMap();
|
||||
if (!$id_map instanceof HighestIdInterface) {
|
||||
throw new AuditException($migration, "ID map does not implement $interface");
|
||||
}
|
||||
|
||||
if ($destination->getHighestId() > $id_map->getHighestId()) {
|
||||
return AuditResult::fail($migration, [
|
||||
$this->t('The destination system contains data which was not created by a migration.'),
|
||||
]);
|
||||
}
|
||||
return AuditResult::pass($migration);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function auditMultiple(array $migrations) {
|
||||
$conflicts = [];
|
||||
|
||||
foreach ($migrations as $migration) {
|
||||
$migration_id = $migration->getPluginId();
|
||||
$conflicts[$migration_id] = $this->audit($migration);
|
||||
}
|
||||
ksort($conflicts);
|
||||
return $conflicts;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
|
||||
interface MigrateBuildDependencyInterface {
|
||||
|
||||
/**
|
||||
|
|
|
@ -96,16 +96,16 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
|||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration to run.
|
||||
* @param \Drupal\migrate\MigrateMessageInterface $message
|
||||
* The message to record.
|
||||
* (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'));
|
||||
|
@ -221,7 +221,9 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
|||
if ($save) {
|
||||
try {
|
||||
$this->getEventDispatcher()->dispatch(MigrateEvents::PRE_ROW_SAVE, new MigratePreRowSaveEvent($this->migration, $this->message, $row));
|
||||
$destination_id_values = $destination->import($row, $id_map->lookupDestinationId($this->sourceIdValues));
|
||||
$destination_ids = $id_map->lookupDestinationIds($this->sourceIdValues);
|
||||
$destination_id_values = $destination_ids ? reset($destination_ids) : [];
|
||||
$destination_id_values = $destination->import($row, $destination_id_values);
|
||||
$this->getEventDispatcher()->dispatch(MigrateEvents::POST_ROW_SAVE, new MigratePostRowSaveEvent($this->migration, $this->message, $row, $destination_id_values));
|
||||
if ($destination_id_values) {
|
||||
// We do not save an idMap entry for config.
|
||||
|
@ -386,9 +388,14 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
|||
$multiple = $plugin->multiple();
|
||||
}
|
||||
}
|
||||
// No plugins or no value means do not set.
|
||||
if ($plugins && !is_null($value)) {
|
||||
$row->setDestinationProperty($destination, $value);
|
||||
// Ensure all values, including nulls, are migrated.
|
||||
if ($plugins) {
|
||||
if (isset($value)) {
|
||||
$row->setDestinationProperty($destination, $value);
|
||||
}
|
||||
else {
|
||||
$row->setEmptyDestinationProperty($destination);
|
||||
}
|
||||
}
|
||||
// Reset the value.
|
||||
$value = NULL;
|
||||
|
@ -534,6 +541,9 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
|||
|
||||
// @TODO: explore resetting the container.
|
||||
|
||||
// Run garbage collector to further reduce memory.
|
||||
gc_collect_cycles();
|
||||
|
||||
return memory_get_usage();
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ interface MigrateExecutableInterface {
|
|||
* Usually setting this is not necessary as $process typically starts with
|
||||
* a 'get'. This is useful only when the $process contains a single
|
||||
* destination and needs to access a value outside of the source. See
|
||||
* \Drupal\migrate\Plugin\migrate\process\Iterator::transformKey for an
|
||||
* \Drupal\migrate\Plugin\migrate\process\SubProcess::transformKey for an
|
||||
* example.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
|
||||
interface MigrateMessageInterface {
|
||||
|
||||
/**
|
||||
|
|
|
@ -48,7 +48,6 @@ class AnnotatedClassDiscoveryAutomatedProviders extends AnnotatedClassDiscovery
|
|||
$this->finder = new ClassFinder();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -11,7 +11,7 @@ use Drupal\migrate\Row;
|
|||
* Destinations are responsible for persisting source data into the destination
|
||||
* Drupal.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\destination\DestinationBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\destination\DestinationBase
|
||||
* @see \Drupal\migrate\Plugin\MigrateDestinationPluginManager
|
||||
* @see \Drupal\migrate\Annotation\MigrateDestination
|
||||
* @see plugin_api
|
||||
|
@ -133,4 +133,12 @@ interface MigrateDestinationInterface extends PluginInspectionInterface {
|
|||
*/
|
||||
public function rollbackAction();
|
||||
|
||||
/**
|
||||
* Gets the destination module handling the destination data.
|
||||
*
|
||||
* @return string|null
|
||||
* The destination module or NULL if not found.
|
||||
*/
|
||||
public function getDestinationModule();
|
||||
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ use Drupal\Core\Extension\ModuleHandlerInterface;
|
|||
* Plugin manager for migrate destination plugins.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateDestinationInterface
|
||||
* @see \Drupal\migrate\Plugin\destination\DestinationBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\destination\DestinationBase
|
||||
* @see \Drupal\migrate\Annotation\MigrateDestination
|
||||
* @see plugin_api
|
||||
*
|
||||
|
|
|
@ -103,7 +103,6 @@ interface MigrateIdMapInterface extends \Iterator, PluginInspectionInterface {
|
|||
*/
|
||||
public function importedCount();
|
||||
|
||||
|
||||
/**
|
||||
* Returns a count of items which are marked as needing update.
|
||||
*
|
||||
|
@ -197,7 +196,7 @@ interface MigrateIdMapInterface extends \Iterator, PluginInspectionInterface {
|
|||
* The source identifier keyed values of the record, e.g. ['nid' => 5], or
|
||||
* an empty array on failure.
|
||||
*/
|
||||
public function lookupSourceID(array $destination_id_values);
|
||||
public function lookupSourceId(array $destination_id_values);
|
||||
|
||||
/**
|
||||
* Looks up the destination identifier corresponding to a source key.
|
||||
|
@ -213,6 +212,8 @@ interface MigrateIdMapInterface extends \Iterator, PluginInspectionInterface {
|
|||
*
|
||||
* @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.x. Use
|
||||
* lookupDestinationIds() instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2725809
|
||||
*/
|
||||
public function lookupDestinationId(array $source_id_values);
|
||||
|
||||
|
@ -227,7 +228,7 @@ interface MigrateIdMapInterface extends \Iterator, PluginInspectionInterface {
|
|||
* If unkeyed, the first count($source_id_values) keys will be assumed.
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays of destination identifier values.
|
||||
* An array of arrays of destination identifier values.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
* Thrown when $source_id_values contains unknown keys, or is the wrong
|
||||
|
@ -266,10 +267,10 @@ interface MigrateIdMapInterface extends \Iterator, PluginInspectionInterface {
|
|||
public function getQualifiedMapTableName();
|
||||
|
||||
/**
|
||||
* Sets the migrate message.
|
||||
* Sets the migrate message service.
|
||||
*
|
||||
* @param \Drupal\migrate\MigrateMessageInterface $message
|
||||
* The message to display.
|
||||
* The migrate message service.
|
||||
*/
|
||||
public function setMessage(MigrateMessageInterface $message);
|
||||
|
||||
|
|
|
@ -9,11 +9,16 @@ use Drupal\migrate\Row;
|
|||
/**
|
||||
* An interface for migrate process plugins.
|
||||
*
|
||||
* A process plugin can use any number of methods instead of (but not in
|
||||
* addition to) transform with the same arguments and then the plugin
|
||||
* configuration needs to provide the name of the method to be called via the
|
||||
* "method" key. See \Drupal\migrate\Plugin\migrate\process\SkipOnEmpty and
|
||||
* migrate.migration.d6_field_instance_widget_settings.yml for examples.
|
||||
* A process plugin will typically implement the transform() method to perform
|
||||
* its work. However, it is possible instead for a process plugin to use any
|
||||
* number of methods, thus offering different alternatives ways of processing.
|
||||
* In this case, the transform() method should not be implemented, and the
|
||||
* plugin configuration must provide the name of the method to be called via the
|
||||
* "method" key. Each method must have the same signature as transform().
|
||||
* The base class \Drupal\migrate\ProcessPluginBase takes care of implementing
|
||||
* transform() and calling the configured method. See
|
||||
* \Drupal\migrate\Plugin\migrate\process\SkipOnEmpty and
|
||||
* d6_field_instance_widget_settings.yml for examples.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigratePluginManager
|
||||
* @see \Drupal\migrate\ProcessPluginBase
|
||||
|
|
|
@ -53,9 +53,12 @@ interface MigrateSourceInterface extends \Countable, \Iterator, PluginInspection
|
|||
* An associative array of field definitions keyed by field ID. Values are
|
||||
* associative arrays with a structure that contains the field type ('type'
|
||||
* key). The other keys are the field storage settings as they are returned
|
||||
* by FieldStorageDefinitionInterface::getSettings(). As an example, for a
|
||||
* composite source primary key that is defined by an integer and a
|
||||
* string, the returned value might look like:
|
||||
* by FieldStorageDefinitionInterface::getSettings().
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* A composite source primary key that is defined by an integer and a string
|
||||
* might look like this:
|
||||
* @code
|
||||
* return [
|
||||
* 'id' => [
|
||||
|
@ -70,6 +73,7 @@ interface MigrateSourceInterface extends \Countable, \Iterator, PluginInspection
|
|||
* ],
|
||||
* ];
|
||||
* @endcode
|
||||
*
|
||||
* If 'type' points to a field plugin with multiple columns and needs to
|
||||
* refer to a column different than 'value', the key of that column will be
|
||||
* appended as a suffix to the plugin name, separated by dot ('.'). Example:
|
||||
|
@ -80,9 +84,13 @@ interface MigrateSourceInterface extends \Countable, \Iterator, PluginInspection
|
|||
* ],
|
||||
* ];
|
||||
* @endcode
|
||||
* Additional custom keys/values, that are not part of field storage
|
||||
* definition, can be passed in definitions. The most common setting, passed
|
||||
* along the ID definition, is 'alias' used by SqlBase source plugin:
|
||||
*
|
||||
* Additional custom keys/values that are not part of field storage
|
||||
* definition can be added as shown below. The most common setting
|
||||
* passed along to the ID definition is table 'alias', used by the SqlBase
|
||||
* source plugin in order to distinguish between ambiguous column names -
|
||||
* for example, when a SQL source query joins two tables with the same
|
||||
* column names.
|
||||
* @code
|
||||
* return [
|
||||
* 'nid' => [
|
||||
|
@ -100,4 +108,12 @@ interface MigrateSourceInterface extends \Countable, \Iterator, PluginInspection
|
|||
*/
|
||||
public function getIds();
|
||||
|
||||
/**
|
||||
* Gets the source module providing the source data.
|
||||
*
|
||||
* @return string|null
|
||||
* The source module or NULL if not found.
|
||||
*/
|
||||
public function getSourceModule();
|
||||
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ use Drupal\migrate\Plugin\Discovery\ProviderFilterDecorator;
|
|||
* Plugin manager for migrate source plugins.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
* @see \Drupal\migrate\Plugin\source\SourcePluginBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
* @see \Drupal\migrate\Annotation\MigrateSource
|
||||
* @see plugin_api
|
||||
*
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
@ -205,7 +216,7 @@ class Migration extends PluginBase implements MigrationInterface, RequirementsIn
|
|||
protected $sourcePluginManager;
|
||||
|
||||
/**
|
||||
* Thep process plugin manager.
|
||||
* The process plugin manager.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigratePluginManager
|
||||
*/
|
||||
|
@ -312,6 +323,8 @@ class Migration extends PluginBase implements MigrationInterface, RequirementsIn
|
|||
*
|
||||
* @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.x. Use
|
||||
* more specific getters instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2873795
|
||||
*/
|
||||
public function get($property) {
|
||||
return isset($this->$property) ? $this->$property : NULL;
|
||||
|
@ -397,7 +410,7 @@ class Migration extends PluginBase implements MigrationInterface, RequirementsIn
|
|||
*/
|
||||
public function getDestinationPlugin($stub_being_requested = FALSE) {
|
||||
if ($stub_being_requested && !empty($this->destination['no_stub'])) {
|
||||
throw new MigrateSkipRowException();
|
||||
throw new MigrateSkipRowException('Stub requested but not made because no_stub configuration is set.');
|
||||
}
|
||||
if (!isset($this->destinationPlugin)) {
|
||||
$this->destinationPlugin = $this->destinationPluginManager->createInstance($this->destination['plugin'], $this->destination, $this);
|
||||
|
@ -541,7 +554,6 @@ class Migration extends PluginBase implements MigrationInterface, RequirementsIn
|
|||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -570,7 +582,7 @@ class Migration extends PluginBase implements MigrationInterface, RequirementsIn
|
|||
*/
|
||||
public function mergeProcessOfProperty($property, array $process_of_property) {
|
||||
// If we already have a process value then merge the incoming process array
|
||||
//otherwise simply set it.
|
||||
// otherwise simply set it.
|
||||
$current_process = $this->getProcess();
|
||||
if (isset($current_process[$property])) {
|
||||
$this->process = NestedArray::mergeDeepArray([$current_process, $this->getProcessNormalized([$property => $process_of_property])], TRUE);
|
||||
|
@ -607,19 +619,22 @@ class Migration extends PluginBase implements MigrationInterface, RequirementsIn
|
|||
}
|
||||
|
||||
/**
|
||||
* Find migration dependencies from the migration and the iterator plugins.
|
||||
* Find migration dependencies from migration_lookup and sub_process plugins.
|
||||
*
|
||||
* @param array $process
|
||||
* A process configuration array.
|
||||
*
|
||||
* @param $process
|
||||
* @return array
|
||||
* The migration dependencies.
|
||||
*/
|
||||
protected function findMigrationDependencies($process) {
|
||||
$return = [];
|
||||
foreach ($this->getProcessNormalized($process) as $process_pipeline) {
|
||||
foreach ($process_pipeline as $plugin_configuration) {
|
||||
if ($plugin_configuration['plugin'] == 'migration') {
|
||||
if (in_array($plugin_configuration['plugin'], ['migration', 'migration_lookup'], TRUE)) {
|
||||
$return = array_merge($return, (array) $plugin_configuration['migration']);
|
||||
}
|
||||
if ($plugin_configuration['plugin'] == 'iterator') {
|
||||
if (in_array($plugin_configuration['plugin'], ['iterator', 'sub_process'], TRUE)) {
|
||||
$return = array_merge($return, $this->findMigrationDependencies($plugin_configuration['process']));
|
||||
}
|
||||
}
|
||||
|
@ -675,4 +690,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();
|
||||
}
|
||||
|
|
|
@ -153,7 +153,7 @@ interface MigrationInterface extends PluginInspectionInterface, DerivativeInspec
|
|||
/**
|
||||
* Set the current migration status.
|
||||
*
|
||||
* @param int $result
|
||||
* @param int $status
|
||||
* One of the STATUS_* constants.
|
||||
*/
|
||||
public function setStatus($status);
|
||||
|
@ -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) {
|
||||
$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());
|
||||
|
||||
|
@ -72,7 +83,7 @@ class MigrationPluginManager extends DefaultPluginManager implements MigrationPl
|
|||
// This gets rid of migrations which try to use a non-existent source
|
||||
// plugin. The common case for this is if the source plugin has, or
|
||||
// specifies, a non-existent provider.
|
||||
$only_with_source_discovery = new NoSourcePluginDecorator($yaml_discovery);
|
||||
$only_with_source_discovery = new NoSourcePluginDecorator($yaml_discovery);
|
||||
// This gets rid of migrations with explicit providers set if one of the
|
||||
// providers do not exist before we try to use a potentially non-existing
|
||||
// deriver. This is a rare case.
|
||||
|
@ -116,16 +127,10 @@ 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) {
|
||||
$migrations = array_filter($this->getDefinitions(), function ($migration) use ($tag) {
|
||||
return !empty($migration['migration_tags']) && in_array($tag, $migration['migration_tags']);
|
||||
});
|
||||
return $this->createInstances(array_keys($migrations));
|
||||
|
@ -155,7 +160,6 @@ class MigrationPluginManager extends DefaultPluginManager implements MigrationPl
|
|||
return $plugin_ids;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
@ -64,6 +71,6 @@ abstract class ComponentEntityDisplayBase extends DestinationBase {
|
|||
* @return \Drupal\Core\Entity\Display\EntityDisplayInterface
|
||||
* The entity display object.
|
||||
*/
|
||||
protected abstract function getEntity($entity_type, $bundle, $mode);
|
||||
abstract protected function getEntity($entity_type, $bundle, $mode);
|
||||
|
||||
}
|
||||
|
|
|
@ -195,4 +195,26 @@ class Config extends DestinationBase implements ContainerFactoryPluginInterface,
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDestinationModule() {
|
||||
if (!empty($this->configuration['destination_module'])) {
|
||||
return $this->configuration['destination_module'];
|
||||
}
|
||||
if (!empty($this->pluginDefinition['destination_module'])) {
|
||||
return $this->pluginDefinition['destination_module'];
|
||||
}
|
||||
// Config translations require the config_translation module so set the
|
||||
// migration provider to 'config_translation'. The corresponding non
|
||||
// translated configuration is expected to be handled in a separate
|
||||
// migration.
|
||||
if (isset($this->configuration['translations'])) {
|
||||
return 'config_translation';
|
||||
}
|
||||
// Get the module handling this configuration object from the config_name,
|
||||
// which is of the form <module_name>.<configuration object name>
|
||||
return !empty($this->configuration['config_name']) ? explode('.', $this->configuration['config_name'], 2)[0] : NULL;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,7 +12,12 @@ use Drupal\migrate\Plugin\RequirementsInterface;
|
|||
/**
|
||||
* Base class for migrate destination classes.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateDestinationInterface
|
||||
* Migrate destination plugins perform the import operation of the migration.
|
||||
* Destination plugins extend this abstract base class. A destination plugin
|
||||
* must implement at least fields(), getIds() and import() methods. Destination
|
||||
* plugins can also support rollback operations. For more
|
||||
* information, refer to \Drupal\migrate\Plugin\MigrateDestinationInterface.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateDestinationPluginManager
|
||||
* @see \Drupal\migrate\Annotation\MigrateDestination
|
||||
* @see plugin_api
|
||||
|
@ -110,4 +115,22 @@ abstract class DestinationBase extends PluginBase implements MigrateDestinationI
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDestinationModule() {
|
||||
if (!empty($this->configuration['destination_module'])) {
|
||||
return $this->configuration['destination_module'];
|
||||
}
|
||||
if (!empty($this->pluginDefinition['destination_module'])) {
|
||||
return $this->pluginDefinition['destination_module'];
|
||||
}
|
||||
if (is_string($this->migration->provider)) {
|
||||
return $this->migration->provider;
|
||||
}
|
||||
else {
|
||||
return reset($this->migration->provider);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
@ -45,14 +86,18 @@ abstract class Entity extends DestinationBase implements ContainerFactoryPluginI
|
|||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param MigrationInterface $migration
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration.
|
||||
* @param EntityStorageInterface $storage
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
|
||||
* The storage for this entity type.
|
||||
* @param array $bundles
|
||||
* The list of bundles this entity type has.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles) {
|
||||
$plugin_definition += [
|
||||
'label' => $storage->getEntityType()->getPluralLabel(),
|
||||
];
|
||||
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
|
||||
$this->storage = $storage;
|
||||
$this->bundles = $bundles;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -72,7 +72,7 @@ class EntityConfigBase extends Entity {
|
|||
/**
|
||||
* The configuration factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface;
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
|
@ -268,7 +268,7 @@ class EntityConfigBase extends Entity {
|
|||
// The entity id does not include the langcode.
|
||||
$id_values = [];
|
||||
foreach ($destination_identifier as $key => $value) {
|
||||
if ($this->isTranslationDestination() && $key == 'langcode') {
|
||||
if ($this->isTranslationDestination() && $key === 'langcode') {
|
||||
continue;
|
||||
}
|
||||
$id_values[] = $value;
|
||||
|
|
|
@ -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.
|
||||
|
@ -88,7 +148,7 @@ class EntityContentBase extends Entity {
|
|||
}
|
||||
|
||||
$ids = $this->save($entity, $old_destination_id_values);
|
||||
if (!empty($this->configuration['translations'])) {
|
||||
if ($this->isTranslationDestination()) {
|
||||
$ids[] = $entity->language()->getId();
|
||||
}
|
||||
return $ids;
|
||||
|
@ -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']);
|
||||
}
|
||||
|
||||
|
@ -124,12 +181,15 @@ class EntityContentBase extends Entity {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids = [];
|
||||
|
||||
$id_key = $this->getKey('id');
|
||||
$ids[$id_key] = $this->getDefinitionFromEntity($id_key);
|
||||
|
||||
if ($this->isTranslationDestination()) {
|
||||
if (!$langcode_key = $this->getKey('langcode')) {
|
||||
throw new MigrateException('This entity type does not support translation.');
|
||||
$langcode_key = $this->getKey('langcode');
|
||||
if (!$langcode_key) {
|
||||
throw new MigrateException(sprintf('The "%s" entity type does not support translations.', $this->storage->getEntityTypeId()));
|
||||
}
|
||||
$ids[$langcode_key] = $this->getDefinitionFromEntity($langcode_key);
|
||||
}
|
||||
|
@ -145,10 +205,11 @@ class EntityContentBase extends Entity {
|
|||
* @param \Drupal\migrate\Row $row
|
||||
* The row object to update from.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
* An updated entity, or NULL if it's the same as the one passed in.
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
* An updated entity from row values.
|
||||
*/
|
||||
protected function updateEntity(EntityInterface $entity, Row $row) {
|
||||
$empty_destinations = $row->getEmptyDestinationProperties();
|
||||
// By default, an update will be preserved.
|
||||
$rollback_action = MigrateIdMapInterface::ROLLBACK_PRESERVE;
|
||||
|
||||
|
@ -171,6 +232,7 @@ class EntityContentBase extends Entity {
|
|||
// clone the row with an empty set of destination values, and re-add only
|
||||
// the specified properties.
|
||||
if (isset($this->configuration['overwrite_properties'])) {
|
||||
$empty_destinations = array_intersect($empty_destinations, $this->configuration['overwrite_properties']);
|
||||
$clone = $row->cloneWithoutDestination();
|
||||
foreach ($this->configuration['overwrite_properties'] as $property) {
|
||||
$clone->setDestinationProperty($property, $row->getDestinationProperty($property));
|
||||
|
@ -184,6 +246,9 @@ class EntityContentBase extends Entity {
|
|||
$field->setValue($values);
|
||||
}
|
||||
}
|
||||
foreach ($empty_destinations as $field_name) {
|
||||
$entity->$field_name = NULL;
|
||||
}
|
||||
|
||||
$this->setRollbackAction($row->getIdMap(), $rollback_action);
|
||||
|
||||
|
@ -213,7 +278,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.
|
||||
|
@ -289,4 +354,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,7 +3,43 @@
|
|||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
/**
|
||||
* Provides entity field instance plugin.
|
||||
* Provides destination plugin for field_config configuration entities.
|
||||
*
|
||||
* The Field API defines two primary data structures, FieldStorage and Field.
|
||||
* A FieldStorage defines a particular type of data that can be attached to
|
||||
* entities as a Field instance.
|
||||
*
|
||||
* The example below adds an instance of 'field_text_example' to 'article'
|
||||
* bundle (node content type). The example uses the EmptySource source plugin
|
||||
* and constant source values for the sake of simplicity. For an example on how
|
||||
* the FieldStorage 'field_text_example' can be migrated, refer to
|
||||
* \Drupal\migrate\Plugin\migrate\destination\EntityFieldStorageConfig.
|
||||
* @code
|
||||
* id: field_instance_example
|
||||
* label: Field instance example
|
||||
* source:
|
||||
* plugin: empty
|
||||
* constants:
|
||||
* entity_type: node
|
||||
* field_name: field_text_example
|
||||
* bundle: article
|
||||
* label: Text field example
|
||||
* translatable: true
|
||||
* process:
|
||||
* entity_type: constants/entity_type
|
||||
* field_name: constants/field_name
|
||||
* bundle: constants/bundle
|
||||
* label: constants/label
|
||||
* translatable: constants/translatable
|
||||
* destination:
|
||||
* plugin: entity:field_config
|
||||
* migration_dependencies:
|
||||
* required:
|
||||
* - field_storage_example
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\field\Entity\FieldConfig
|
||||
* @see \Drupal\field\Entity\FieldConfigBase
|
||||
*
|
||||
* @MigrateDestination(
|
||||
* id = "entity:field_config"
|
||||
|
|
|
@ -3,7 +3,48 @@
|
|||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
/**
|
||||
* Provides entity field storage configuration plugin.
|
||||
* Provides destination plugin for field_storage_config configuration entities.
|
||||
*
|
||||
* The Field API defines two primary data structures, FieldStorage and Field.
|
||||
* A FieldStorage defines a particular type of data that can be attached to
|
||||
* entities as a Field instance.
|
||||
*
|
||||
* The example below creates a storage for a simple text field. The example uses
|
||||
* the EmptySource source plugin and constant source values for the sake of
|
||||
* simplicity.
|
||||
* @code
|
||||
* id: field_storage_example
|
||||
* label: Field storage example
|
||||
* source:
|
||||
* plugin: empty
|
||||
* constants:
|
||||
* entity_type: node
|
||||
* id: node.field_text_example
|
||||
* field_name: field_text_example
|
||||
* type: string
|
||||
* cardinality: 1
|
||||
* settings:
|
||||
* max_length: 10
|
||||
* langcode: en
|
||||
* translatable: true
|
||||
* process:
|
||||
* entity_type: constants/entity_type
|
||||
* id: constants/id
|
||||
* field_name: constants/field_name
|
||||
* type: constants/type
|
||||
* cardinality: constants/cardinality
|
||||
* settings: constants/settings
|
||||
* langcode: constants/langcode
|
||||
* translatable: constants/translatable
|
||||
* destination:
|
||||
* plugin: entity:field_storage_config
|
||||
* @endcode
|
||||
*
|
||||
* For a full list of the properties of a FieldStorage configuration entity,
|
||||
* refer to \Drupal\field\Entity\FieldStorageConfig.
|
||||
*
|
||||
* For an example on how to migrate a Field instance of this FieldStorage,
|
||||
* refer to \Drupal\migrate\Plugin\migrate\destination\EntityFieldInstance.
|
||||
*
|
||||
* @MigrateDestination(
|
||||
* id = "entity:field_storage_config"
|
||||
|
@ -17,6 +58,10 @@ class EntityFieldStorageConfig extends EntityConfigBase {
|
|||
public function getIds() {
|
||||
$ids['entity_type']['type'] = 'string';
|
||||
$ids['field_name']['type'] = 'string';
|
||||
// @todo: Remove conditional. https://www.drupal.org/node/3004574
|
||||
if ($this->isTranslationDestination()) {
|
||||
$ids['langcode']['type'] = 'string';
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
|
@ -24,8 +69,18 @@ class EntityFieldStorageConfig extends EntityConfigBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollback(array $destination_identifier) {
|
||||
$destination_identifier = implode('.', $destination_identifier);
|
||||
parent::rollback([$destination_identifier]);
|
||||
if ($this->isTranslationDestination()) {
|
||||
$language = $destination_identifier['langcode'];
|
||||
unset($destination_identifier['langcode']);
|
||||
$destination_identifier = [
|
||||
implode('.', $destination_identifier),
|
||||
'langcode' => $language,
|
||||
];
|
||||
}
|
||||
else {
|
||||
$destination_identifier = [implode('.', $destination_identifier)];
|
||||
}
|
||||
parent::rollback($destination_identifier);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
*/
|
||||
|
@ -55,7 +160,9 @@ class EntityRevision extends EntityContentBase {
|
|||
$entity->enforceIsNew(FALSE);
|
||||
$entity->setNewRevision(TRUE);
|
||||
}
|
||||
$this->updateEntity($entity, $row);
|
||||
// We need to update the entity, so that the destination row IDs are
|
||||
// correct.
|
||||
$entity = $this->updateEntity($entity, $row);
|
||||
$entity->isDefaultRevision(FALSE);
|
||||
return $entity;
|
||||
}
|
||||
|
@ -72,10 +179,38 @@ class EntityRevision extends EntityContentBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
if ($key = $this->getKey('revision')) {
|
||||
return [$key => $this->getDefinitionFromEntity($key)];
|
||||
$ids = [];
|
||||
|
||||
$revision_key = $this->getKey('revision');
|
||||
if (!$revision_key) {
|
||||
throw new MigrateException(sprintf('The "%s" entity type does not support revisions.', $this->storage->getEntityTypeId()));
|
||||
}
|
||||
throw new MigrateException('This entity type does not support revisions.');
|
||||
$ids[$revision_key] = $this->getDefinitionFromEntity($revision_key);
|
||||
|
||||
if ($this->isTranslationDestination()) {
|
||||
$langcode_key = $this->getKey('langcode');
|
||||
if (!$langcode_key) {
|
||||
throw new MigrateException(sprintf('The "%s" entity type does not support translations.', $this->storage->getEntityTypeId()));
|
||||
}
|
||||
$ids[$langcode_key] = $this->getDefinitionFromEntity($langcode_key);
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHighestId() {
|
||||
$values = $this->storage->getQuery()
|
||||
->accessCheck(FALSE)
|
||||
->allRevisions()
|
||||
->sort($this->getKey('revision'), 'DESC')
|
||||
->range(0, 1)
|
||||
->execute();
|
||||
// The array keys are the revision IDs.
|
||||
// The array contains only one entry, so we can use key().
|
||||
return (int) key($values);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
223
web/core/modules/migrate/src/Plugin/migrate/id_map/NullIdMap.php
Normal file
223
web/core/modules/migrate/src/Plugin/migrate/id_map/NullIdMap.php
Normal file
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
namespace Drupal\migrate\Plugin\migrate\id_map;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Database\DatabaseException;
|
||||
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;
|
||||
|
@ -26,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.
|
||||
|
@ -55,7 +57,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
|||
protected $messageTableName;
|
||||
|
||||
/**
|
||||
* The migrate message.
|
||||
* The migrate message service.
|
||||
*
|
||||
* @var \Drupal\migrate\MigrateMessageInterface
|
||||
*/
|
||||
|
@ -151,11 +153,26 @@ 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);
|
||||
$this->migration = $migration;
|
||||
$this->eventDispatcher = $event_dispatcher;
|
||||
$this->message = new MigrateMessage();
|
||||
|
||||
if (!isset($this->database)) {
|
||||
$this->database = \Drupal::database();
|
||||
}
|
||||
|
||||
// Default generated table names, limited to 63 characters.
|
||||
$machine_name = str_replace(':', '__', $this->migration->id());
|
||||
$prefix_length = strlen($this->database->tablePrefix());
|
||||
$this->mapTableName = 'migrate_map_' . mb_strtolower($machine_name);
|
||||
$this->mapTableName = mb_substr($this->mapTableName, 0, 63 - $prefix_length);
|
||||
$this->messageTableName = 'migrate_message_' . mb_strtolower($machine_name);
|
||||
$this->messageTableName = mb_substr($this->messageTableName, 0, 63 - $prefix_length);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -182,7 +199,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
|||
* @return string
|
||||
* An hash containing the hashed values of the source identifiers.
|
||||
*/
|
||||
public function getSourceIDsHash(array $source_id_values) {
|
||||
public function getSourceIdsHash(array $source_id_values) {
|
||||
// When looking up the destination ID we require an array with both the
|
||||
// source key and value, e.g. ['nid' => 41]. In this case, $source_id_values
|
||||
// need to be ordered the same order as $this->sourceIdFields().
|
||||
|
@ -241,7 +258,6 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
|||
* The map table name.
|
||||
*/
|
||||
public function mapTableName() {
|
||||
$this->init();
|
||||
return $this->mapTableName;
|
||||
}
|
||||
|
||||
|
@ -252,7 +268,6 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
|||
* The message table name.
|
||||
*/
|
||||
public function messageTableName() {
|
||||
$this->init();
|
||||
return $this->messageTableName;
|
||||
}
|
||||
|
||||
|
@ -273,9 +288,6 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
|||
* The database connection object.
|
||||
*/
|
||||
public function getDatabase() {
|
||||
if (!isset($this->database)) {
|
||||
$this->database = \Drupal::database();
|
||||
}
|
||||
$this->init();
|
||||
return $this->database;
|
||||
}
|
||||
|
@ -286,13 +298,6 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
|||
protected function init() {
|
||||
if (!$this->initialized) {
|
||||
$this->initialized = TRUE;
|
||||
// Default generated table names, limited to 63 characters.
|
||||
$machine_name = str_replace(':', '__', $this->migration->id());
|
||||
$prefix_length = strlen($this->getDatabase()->tablePrefix());
|
||||
$this->mapTableName = 'migrate_map_' . Unicode::strtolower($machine_name);
|
||||
$this->mapTableName = Unicode::substr($this->mapTableName, 0, 63 - $prefix_length);
|
||||
$this->messageTableName = 'migrate_message_' . Unicode::strtolower($machine_name);
|
||||
$this->messageTableName = Unicode::substr($this->messageTableName, 0, 63 - $prefix_length);
|
||||
$this->ensureTables();
|
||||
}
|
||||
}
|
||||
|
@ -486,7 +491,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
|||
public function getRowBySource(array $source_id_values) {
|
||||
$query = $this->getDatabase()->select($this->mapTableName(), 'map')
|
||||
->fields('map');
|
||||
$query->condition(static::SOURCE_IDS_HASH, $this->getSourceIDsHash($source_id_values));
|
||||
$query->condition(static::SOURCE_IDS_HASH, $this->getSourceIdsHash($source_id_values));
|
||||
$result = $query->execute();
|
||||
return $result->fetchAssoc();
|
||||
}
|
||||
|
@ -523,7 +528,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lookupSourceID(array $destination_id_values) {
|
||||
public function lookupSourceId(array $destination_id_values) {
|
||||
$source_id_fields = $this->sourceIdFields();
|
||||
$query = $this->getDatabase()->select($this->mapTableName(), 'map');
|
||||
foreach ($source_id_fields as $source_field_name => $idmap_field_name) {
|
||||
|
@ -557,9 +562,13 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
|||
$conditions = [];
|
||||
foreach ($this->sourceIdFields() as $field_name => $db_field) {
|
||||
if ($is_associative) {
|
||||
// Associative $source_id_values can have fields out of order.
|
||||
if (isset($source_id_values[$field_name])) {
|
||||
$conditions[$db_field] = $source_id_values[$field_name];
|
||||
// Ensure to handle array elements with a NULL value.
|
||||
if (array_key_exists($field_name, $source_id_values)) {
|
||||
// Associative $source_id_values can have fields out of order.
|
||||
if (isset($source_id_values[$field_name])) {
|
||||
// Only add a condition if the value is not NULL.
|
||||
$conditions[$db_field] = $source_id_values[$field_name];
|
||||
}
|
||||
unset($source_id_values[$field_name]);
|
||||
}
|
||||
}
|
||||
|
@ -574,14 +583,15 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
|||
}
|
||||
|
||||
if (!empty($source_id_values)) {
|
||||
throw new MigrateException("Extra unknown items in source IDs");
|
||||
$var_dump = var_export($source_id_values, TRUE);
|
||||
throw new MigrateException(sprintf("Extra unknown items in source IDs: %s", $var_dump));
|
||||
}
|
||||
|
||||
$query = $this->getDatabase()->select($this->mapTableName(), 'map')
|
||||
->fields('map', $this->destinationIdFields());
|
||||
if (count($this->sourceIdFields()) === count($conditions)) {
|
||||
// Optimization: Use the primary key.
|
||||
$query->condition(self::SOURCE_IDS_HASH, $this->getSourceIDsHash(array_values($conditions)));
|
||||
$query->condition(self::SOURCE_IDS_HASH, $this->getSourceIdsHash(array_values($conditions)));
|
||||
}
|
||||
else {
|
||||
foreach ($conditions as $db_field => $value) {
|
||||
|
@ -631,7 +641,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
|||
if ($this->migration->getTrackLastImported()) {
|
||||
$fields['last_imported'] = time();
|
||||
}
|
||||
$keys = [static::SOURCE_IDS_HASH => $this->getSourceIDsHash($source_id_values)];
|
||||
$keys = [static::SOURCE_IDS_HASH => $this->getSourceIdsHash($source_id_values)];
|
||||
// Notify anyone listening of the map row we're about to save.
|
||||
$this->eventDispatcher->dispatch(MigrateEvents::MAP_SAVE, new MigrateMapSaveEvent($this, $fields));
|
||||
$this->getDatabase()->merge($this->mapTableName())
|
||||
|
@ -650,7 +660,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
|||
return;
|
||||
}
|
||||
}
|
||||
$fields[static::SOURCE_IDS_HASH] = $this->getSourceIDsHash($source_id_values);
|
||||
$fields[static::SOURCE_IDS_HASH] = $this->getSourceIdsHash($source_id_values);
|
||||
$fields['level'] = $level;
|
||||
$fields['message'] = $message;
|
||||
$this->getDatabase()->insert($this->messageTableName())
|
||||
|
@ -669,7 +679,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
|||
$query = $this->getDatabase()->select($this->messageTableName(), 'msg')
|
||||
->fields('msg');
|
||||
if ($source_id_values) {
|
||||
$query->condition(static::SOURCE_IDS_HASH, $this->getSourceIDsHash($source_id_values));
|
||||
$query->condition(static::SOURCE_IDS_HASH, $this->getSourceIdsHash($source_id_values));
|
||||
}
|
||||
|
||||
if ($level) {
|
||||
|
@ -691,21 +701,17 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function processedCount() {
|
||||
return $this->getDatabase()->select($this->mapTableName())
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
return $this->countHelper(NULL, $this->mapTableName());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function importedCount() {
|
||||
return $this->getDatabase()->select($this->mapTableName())
|
||||
->condition('source_row_status', [MigrateIdMapInterface::STATUS_IMPORTED, MigrateIdMapInterface::STATUS_NEEDS_UPDATE], 'IN')
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
return $this->countHelper([
|
||||
MigrateIdMapInterface::STATUS_IMPORTED,
|
||||
MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -732,20 +738,28 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
|||
/**
|
||||
* Counts records in a table.
|
||||
*
|
||||
* @param int $status
|
||||
* An integer for the source_row_status column.
|
||||
* @param int|array $status
|
||||
* (optional) Status code(s) to filter the source_row_status column.
|
||||
* @param string $table
|
||||
* (optional) The table to work. Defaults to NULL.
|
||||
*
|
||||
* @return int
|
||||
* The number of records.
|
||||
*/
|
||||
protected function countHelper($status, $table = NULL) {
|
||||
$query = $this->getDatabase()->select($table ?: $this->mapTableName());
|
||||
protected function countHelper($status = NULL, $table = NULL) {
|
||||
// Use database directly to avoid creating tables.
|
||||
$query = $this->database->select($table ?: $this->mapTableName());
|
||||
if (isset($status)) {
|
||||
$query->condition('source_row_status', $status);
|
||||
$query->condition('source_row_status', $status, is_array($status) ? 'IN' : '=');
|
||||
}
|
||||
return $query->countQuery()->execute()->fetchField();
|
||||
try {
|
||||
$count = (int) $query->countQuery()->execute()->fetchField();
|
||||
}
|
||||
catch (DatabaseException $e) {
|
||||
// The table does not exist, therefore there are no records.
|
||||
$count = 0;
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -758,13 +772,13 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
|||
|
||||
if (!$messages_only) {
|
||||
$map_query = $this->getDatabase()->delete($this->mapTableName());
|
||||
$map_query->condition(static::SOURCE_IDS_HASH, $this->getSourceIDsHash($source_id_values));
|
||||
$map_query->condition(static::SOURCE_IDS_HASH, $this->getSourceIdsHash($source_id_values));
|
||||
// Notify anyone listening of the map row we're about to delete.
|
||||
$this->eventDispatcher->dispatch(MigrateEvents::MAP_DELETE, new MigrateMapDeleteEvent($this, $source_id_values));
|
||||
$map_query->execute();
|
||||
}
|
||||
$message_query = $this->getDatabase()->delete($this->messageTableName());
|
||||
$message_query->condition(static::SOURCE_IDS_HASH, $this->getSourceIDsHash($source_id_values));
|
||||
$message_query->condition(static::SOURCE_IDS_HASH, $this->getSourceIdsHash($source_id_values));
|
||||
$message_query->execute();
|
||||
}
|
||||
|
||||
|
@ -774,7 +788,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
|||
public function deleteDestination(array $destination_id_values) {
|
||||
$map_query = $this->getDatabase()->delete($this->mapTableName());
|
||||
$message_query = $this->getDatabase()->delete($this->messageTableName());
|
||||
$source_id_values = $this->lookupSourceID($destination_id_values);
|
||||
$source_id_values = $this->lookupSourceId($destination_id_values);
|
||||
if (!empty($source_id_values)) {
|
||||
foreach ($this->destinationIdFields() as $field_name => $destination_id) {
|
||||
$map_query->condition($destination_id, $destination_id_values[$field_name]);
|
||||
|
@ -783,7 +797,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
|||
$this->eventDispatcher->dispatch(MigrateEvents::MAP_DELETE, new MigrateMapDeleteEvent($this, $source_id_values));
|
||||
$map_query->execute();
|
||||
|
||||
$message_query->condition(static::SOURCE_IDS_HASH, $this->getSourceIDsHash($source_id_values));
|
||||
$message_query->condition(static::SOURCE_IDS_HASH, $this->getSourceIdsHash($source_id_values));
|
||||
$message_query->execute();
|
||||
}
|
||||
}
|
||||
|
@ -923,4 +937,70 @@ 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 the map_table does not exist then continue on to the next map_table.
|
||||
if (!$this->getDatabase()->schema()->tableExists($map_table)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -49,11 +49,21 @@ class Callback extends ProcessPluginBase {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (is_callable($this->configuration['callable'])) {
|
||||
$value = call_user_func($this->configuration['callable'], $value);
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
|
||||
if (!isset($configuration['callable'])) {
|
||||
throw new \InvalidArgumentException('The "callable" must be set.');
|
||||
}
|
||||
return $value;
|
||||
elseif (!is_callable($configuration['callable'])) {
|
||||
throw new \InvalidArgumentException('The "callable" must be a valid function or method.');
|
||||
}
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
return call_user_func($this->configuration['callable'], $value);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ Drupal 8.4.x and will be removed before Drupal 9.0.0. Instead, use ' . __NAMESPA
|
|||
*
|
||||
* @deprecated in Drupal 8.4.x and will be removed in Drupal 9.0.x. Use
|
||||
* \Drupal\migrate\Plugin\migrate\process\MakeUniqueBase instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2873762
|
||||
*/
|
||||
abstract class DedupeBase extends MakeUniqueBase {
|
||||
}
|
||||
|
|
|
@ -19,5 +19,7 @@ Drupal 8.4.x and will be removed before Drupal 9.0.0. Instead, use ' . __NAMESPA
|
|||
*
|
||||
* @deprecated in Drupal 8.4.x and will be removed in Drupal 9.0.x. Use
|
||||
* \Drupal\migrate\Plugin\migrate\process\MakeUniqueEntityField instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2873762
|
||||
*/
|
||||
class DedupeEntity extends MakeUniqueEntityField { }
|
||||
class DedupeEntity extends MakeUniqueEntityField {}
|
||||
|
|
|
@ -6,7 +6,6 @@ use Drupal\Core\File\FileSystemInterface;
|
|||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
use GuzzleHttp\Client;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
@ -19,8 +18,12 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
|||
* - destination URI, e.g. 'public://images/foo.img'
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - rename: (optional) If set, a unique destination URI is generated. If not
|
||||
* set, the destination URI will be overwritten if it exists.
|
||||
* - file_exists: (optional) Replace behavior when the destination file already
|
||||
* exists:
|
||||
* - 'replace' - (default) Replace the existing file.
|
||||
* - 'rename' - Append _{incrementing number} until the filename is
|
||||
* unique.
|
||||
* - 'use existing' - Do nothing and return FALSE.
|
||||
* - guzzle_options: (optional)
|
||||
* @link http://docs.guzzlephp.org/en/latest/request-options.html Array of request options for Guzzle. @endlink
|
||||
*
|
||||
|
@ -42,7 +45,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
|||
* source:
|
||||
* - source_url
|
||||
* - destination_uri
|
||||
* rename: true
|
||||
* file_exists: rename
|
||||
* @endcode
|
||||
*
|
||||
* This will download source_url to destination_uri and ensure that the
|
||||
|
@ -53,7 +56,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
|||
* id = "download"
|
||||
* )
|
||||
*/
|
||||
class Download extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
class Download extends FileProcessBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The file system service.
|
||||
|
@ -85,7 +88,6 @@ class Download extends ProcessPluginBase implements ContainerFactoryPluginInterf
|
|||
*/
|
||||
public function __construct(array $configuration, $plugin_id, array $plugin_definition, FileSystemInterface $file_system, Client $http_client) {
|
||||
$configuration += [
|
||||
'rename' => FALSE,
|
||||
'guzzle_options' => [],
|
||||
];
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
@ -118,10 +120,12 @@ class Download extends ProcessPluginBase implements ContainerFactoryPluginInterf
|
|||
list($source, $destination) = $value;
|
||||
|
||||
// Modify the destination filename if necessary.
|
||||
$replace = !empty($this->configuration['rename']) ?
|
||||
FILE_EXISTS_RENAME :
|
||||
FILE_EXISTS_REPLACE;
|
||||
$final_destination = file_destination($destination, $replace);
|
||||
$final_destination = file_destination($destination, $this->configuration['file_exists']);
|
||||
|
||||
// Reuse if file exists.
|
||||
if (!$final_destination) {
|
||||
return $destination;
|
||||
}
|
||||
|
||||
// Try opening the file first, to avoid calling file_prepare_directory()
|
||||
// unnecessarily. We're suppressing fopen() errors because we want to try
|
||||
|
|
|
@ -9,7 +9,6 @@ use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
|
|||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Plugin\MigrateProcessInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
|
@ -19,19 +18,19 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
|||
* The file can be moved, reused, or set to be automatically renamed if a
|
||||
* duplicate exists.
|
||||
*
|
||||
* The source value is an array of two values:
|
||||
* - source: The source path or URI, e.g. '/path/to/foo.txt' or
|
||||
* 'public://bar.txt'.
|
||||
* - destination: The destination path or URI, e.g. '/path/to/bar.txt' or
|
||||
* 'public://foo.txt'.
|
||||
* The source value is an indexed array of two values:
|
||||
* - The source path or URI, e.g. '/path/to/foo.txt' or 'public://bar.txt'.
|
||||
* - The destination URI, e.g. 'public://foo.txt'.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - move: (optional) Boolean, if TRUE, move the file, otherwise copy the file.
|
||||
* Defaults to FALSE.
|
||||
* - rename: (optional) Boolean, if TRUE, rename the file by appending a number
|
||||
* until the name is unique. Defaults to FALSE.
|
||||
* - reuse: (optional) Boolean, if TRUE, reuse the current file in its existing
|
||||
* location rather than move/copy/rename the file. Defaults to FALSE.
|
||||
* - file_exists: (optional) Replace behavior when the destination file already
|
||||
* exists:
|
||||
* - 'replace' - (default) Replace the existing file.
|
||||
* - 'rename' - Append _{incrementing number} until the filename is
|
||||
* unique.
|
||||
* - 'use existing' - Do nothing and return FALSE.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
|
@ -39,8 +38,9 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
|||
* process:
|
||||
* path_to_file:
|
||||
* plugin: file_copy
|
||||
* source: /path/to/file.png
|
||||
* destination: /new/path/to/file.png
|
||||
* source:
|
||||
* - /path/to/file.png
|
||||
* - public://new/path/to/file.png
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
|
@ -49,7 +49,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
|||
* id = "file_copy"
|
||||
* )
|
||||
*/
|
||||
class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
class FileCopy extends FileProcessBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The stream wrapper manager service.
|
||||
|
@ -91,8 +91,6 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf
|
|||
public function __construct(array $configuration, $plugin_id, array $plugin_definition, StreamWrapperManagerInterface $stream_wrappers, FileSystemInterface $file_system, MigrateProcessInterface $download_plugin) {
|
||||
$configuration += [
|
||||
'move' => FALSE,
|
||||
'rename' => FALSE,
|
||||
'reuse' => FALSE,
|
||||
];
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->streamWrapperManager = $stream_wrappers;
|
||||
|
@ -110,7 +108,7 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf
|
|||
$plugin_definition,
|
||||
$container->get('stream_wrapper_manager'),
|
||||
$container->get('file_system'),
|
||||
$container->get('plugin.manager.migrate.process')->createInstance('download')
|
||||
$container->get('plugin.manager.migrate.process')->createInstance('download', $configuration)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -151,7 +149,7 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf
|
|||
}
|
||||
}
|
||||
|
||||
$final_destination = $this->writeFile($source, $destination, $this->getOverwriteMode());
|
||||
$final_destination = $this->writeFile($source, $destination, $this->configuration['file_exists']);
|
||||
if ($final_destination) {
|
||||
return $final_destination;
|
||||
}
|
||||
|
@ -182,24 +180,6 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf
|
|||
return $function($source, $destination, $replace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines how to handle file conflicts.
|
||||
*
|
||||
* @return int
|
||||
* FILE_EXISTS_REPLACE (default), FILE_EXISTS_RENAME, or FILE_EXISTS_ERROR
|
||||
* depending on the current configuration.
|
||||
*/
|
||||
protected function getOverwriteMode() {
|
||||
if (!empty($this->configuration['rename'])) {
|
||||
return FILE_EXISTS_RENAME;
|
||||
}
|
||||
if (!empty($this->configuration['reuse'])) {
|
||||
return FILE_EXISTS_ERROR;
|
||||
}
|
||||
|
||||
return FILE_EXISTS_REPLACE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directory component of a URI or path.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
|
||||
/**
|
||||
* Provides functionality for file process plugins.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - file_exists: (optional) Replace behavior when the destination file already
|
||||
* exists:
|
||||
* - 'replace' - (default) Replace the existing file.
|
||||
* - 'rename' - Append _{incrementing number} until the filename is
|
||||
* unique.
|
||||
* - 'use existing' - Do nothing and return FALSE.
|
||||
*/
|
||||
abstract class FileProcessBase extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* Constructs a file process plugin.
|
||||
*
|
||||
* @param array $configuration
|
||||
* The plugin configuration.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin definition.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, array $plugin_definition) {
|
||||
if (array_key_exists('file_exists', $configuration)) {
|
||||
switch ($configuration['file_exists']) {
|
||||
case 'use existing':
|
||||
$configuration['file_exists'] = FILE_EXISTS_ERROR;
|
||||
break;
|
||||
case 'rename':
|
||||
$configuration['file_exists'] = FILE_EXISTS_RENAME;
|
||||
break;
|
||||
default:
|
||||
$configuration['file_exists'] = FILE_EXISTS_REPLACE;
|
||||
}
|
||||
}
|
||||
if (array_key_exists('reuse', $configuration)) {
|
||||
@trigger_error("Using the key 'reuse' is deprecated, use 'file_exists' => 'use existing' instead. See https://www.drupal.org/node/2981389.", E_USER_DEPRECATED);
|
||||
if (!empty($configuration['reuse'])) {
|
||||
$configuration['file_exists'] = FILE_EXISTS_ERROR;
|
||||
}
|
||||
}
|
||||
if (array_key_exists('rename', $configuration)) {
|
||||
@trigger_error("Using the key 'rename' is deprecated, use 'file_exists' => 'rename' instead. See https://www.drupal.org/node/2981389.", E_USER_DEPRECATED);
|
||||
if (!empty($configuration['rename'])) {
|
||||
$configuration['file_exists'] = FILE_EXISTS_RENAME;
|
||||
}
|
||||
}
|
||||
$configuration += ['file_exists' => FILE_EXISTS_REPLACE];
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
|
|
@ -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,65 +2,19 @@
|
|||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
@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);
|
||||
|
||||
/**
|
||||
* This plugin iterates and processes an array.
|
||||
* Iterates and processes an associative array.
|
||||
*
|
||||
* @link https://www.drupal.org/node/2135345 Online handbook documentation for iterator process plugin @endlink
|
||||
* @deprecated in Drupal 8.4.x and will be removed in Drupal 9.0.x. Use
|
||||
* \Drupal\migrate\Plugin\migrate\process\SubProcess instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2880427
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "iterator",
|
||||
* handle_multiples = TRUE
|
||||
* )
|
||||
*/
|
||||
class Iterator extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* Runs a process pipeline on each destination property per list item.
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$return = [];
|
||||
if (!is_null($value)) {
|
||||
foreach ($value as $key => $new_value) {
|
||||
$new_row = new Row($new_value, []);
|
||||
$migrate_executable->processRow($new_row, $this->configuration['process']);
|
||||
$destination = $new_row->getDestination();
|
||||
if (array_key_exists('key', $this->configuration)) {
|
||||
$key = $this->transformKey($key, $migrate_executable, $new_row);
|
||||
}
|
||||
$return[$key] = $destination;
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the process pipeline for the current key.
|
||||
*
|
||||
* @param string|int $key
|
||||
* The current key.
|
||||
* @param \Drupal\migrate\MigrateExecutableInterface $migrate_executable
|
||||
* The migrate executable helper class.
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The current row after processing.
|
||||
*
|
||||
* @return mixed
|
||||
* The transformed key.
|
||||
*/
|
||||
protected function transformKey($key, MigrateExecutableInterface $migrate_executable, Row $row) {
|
||||
$process = ['key' => $this->configuration['key']];
|
||||
$migrate_executable->processRow($row, $process, $key);
|
||||
return $row->getDestinationProperty('key');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function multiple() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
class Iterator extends SubProcess {}
|
||||
|
|
|
@ -6,7 +6,6 @@ use Drupal\migrate\MigrateExecutableInterface;
|
|||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
|
||||
/**
|
||||
* Logs values without changing them.
|
||||
*
|
||||
|
|
|
@ -6,7 +6,6 @@ use Drupal\migrate\ProcessPluginBase;
|
|||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
|
||||
/**
|
||||
* This plugin ensures the source value is unique.
|
||||
|
@ -56,7 +55,7 @@ abstract class MakeUniqueBase extends ProcessPluginBase {
|
|||
throw new MigrateException('The character length configuration key should be an integer. Omit this key to capture the entire string.');
|
||||
}
|
||||
// Use optional start or length to return a portion of the unique value.
|
||||
$value = Unicode::substr($value, $start, $length);
|
||||
$value = mb_substr($value, $start, $length);
|
||||
$new_value = $value;
|
||||
while ($this->exists($new_value)) {
|
||||
$new_value = $value . $postfix . $i++;
|
||||
|
|
|
@ -134,7 +134,7 @@ class MakeUniqueEntityField extends MakeUniqueBase implements ContainerFactoryPl
|
|||
$idMap = $this->migration->getIdMap();
|
||||
foreach ($query->execute() as $id) {
|
||||
$dest_id_values[$this->configuration['field']] = $id;
|
||||
if ($idMap->lookupSourceID($dest_id_values)) {
|
||||
if ($idMap->lookupSourceId($dest_id_values)) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ class MenuLinkParent extends ProcessPluginBase implements ContainerFactoryPlugin
|
|||
}
|
||||
}
|
||||
}
|
||||
throw new MigrateSkipRowException();
|
||||
throw new MigrateSkipRowException(sprintf("No parent link found for plid '%d' in menu '%s'.", $parent_id, $value[0]));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -17,4 +16,4 @@ Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use ' . __NAMESPA
|
|||
* @deprecated in Drupal 8.3.x and will be removed in Drupal 9.0.x.
|
||||
* Use \Drupal\migrate\Plugin\migrate\process\MigrationLookup instead.
|
||||
*/
|
||||
class Migration extends MigrationLookup { }
|
||||
class Migration extends MigrationLookup {}
|
||||
|
|
|
@ -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;
|
||||
|
@ -217,20 +208,21 @@ class MigrationLookup extends ProcessPluginBase implements ContainerFactoryPlugi
|
|||
$values[$source_id] = $source_id_values[$migration->id()][$index];
|
||||
}
|
||||
|
||||
$stub_row = new Row($values + $migration->getSourceConfiguration(), $source_ids, TRUE);
|
||||
$stub_row = $this->createStubRow($values + $migration->getSourceConfiguration(), $source_ids);
|
||||
|
||||
// Do a normal migration with the stub row.
|
||||
$migrate_executable->processRow($stub_row, $process);
|
||||
$destination_ids = [];
|
||||
$id_map = $migration->getIdMap();
|
||||
try {
|
||||
$destination_ids = $destination_plugin->import($stub_row);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$migration->getIdMap()->saveMessage($stub_row->getSourceIdValues(), $e->getMessage());
|
||||
$id_map->saveMessage($stub_row->getSourceIdValues(), $e->getMessage());
|
||||
}
|
||||
|
||||
if ($destination_ids) {
|
||||
$migration->getIdMap()->saveIdMapping($stub_row, $destination_ids, MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
|
||||
$id_map->saveIdMapping($stub_row, $destination_ids, MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
|
||||
}
|
||||
}
|
||||
if ($destination_ids) {
|
||||
|
@ -246,7 +238,7 @@ class MigrationLookup extends ProcessPluginBase implements ContainerFactoryPlugi
|
|||
/**
|
||||
* Skips the migration process entirely if the value is FALSE.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param array $value
|
||||
* The incoming value to transform.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateSkipProcessException
|
||||
|
@ -257,4 +249,23 @@ class MigrationLookup extends ProcessPluginBase implements ContainerFactoryPlugi
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a stub row source for later import as stub data.
|
||||
*
|
||||
* This simple wrapper of the Row constructor allows sub-classing plugins to
|
||||
* have more control over the row.
|
||||
*
|
||||
* @param array $values
|
||||
* An array of values to add as properties on the object.
|
||||
* @param array $source_ids
|
||||
* An array containing the IDs of the source using the keys as the field
|
||||
* names.
|
||||
*
|
||||
* @return \Drupal\migrate\Row
|
||||
* The stub row.
|
||||
*/
|
||||
protected function createStubRow(array $values, array $source_ids) {
|
||||
return new Row($values, $source_ids, TRUE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Component\Utility\Variable;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
|
@ -17,10 +18,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 +30,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 +42,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(
|
||||
|
@ -131,7 +141,7 @@ class StaticMap extends ProcessPluginBase {
|
|||
return $this->configuration['default_value'];
|
||||
}
|
||||
if (empty($this->configuration['bypass'])) {
|
||||
throw new MigrateSkipRowException();
|
||||
throw new MigrateSkipRowException(sprintf("No static mapping found for '%s' and no default value provided for destination '%s'.", Variable::export($value), $destination_property));
|
||||
}
|
||||
else {
|
||||
return $value;
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Runs an array of arrays through its own process pipeline.
|
||||
*
|
||||
* The sub_process plugin accepts an array of associative arrays and runs each
|
||||
* one through its own process pipeline, producing a newly keyed associative
|
||||
* array of transformed values.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - process: the plugin(s) that will process each element of the source.
|
||||
* - key: runs the process pipeline for the key to determine a new dynamic
|
||||
* name.
|
||||
*
|
||||
* Example 1:
|
||||
*
|
||||
* This example demonstrates how migration_lookup process plugin can be applied
|
||||
* on the following source data.
|
||||
* @code
|
||||
* source: Array
|
||||
* (
|
||||
* [upload] => Array
|
||||
* (
|
||||
* [0] => Array
|
||||
* (
|
||||
* [fid] => 1
|
||||
* [list] => 0
|
||||
* [description] => "File number 1"
|
||||
* )
|
||||
* [1] => Array
|
||||
* (
|
||||
* [fid] => 2
|
||||
* [list] => 1
|
||||
* [description] => "File number 2"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* ...
|
||||
* @endcode
|
||||
* The sub_process process plugin will take these arrays one at a time and run
|
||||
* its own process for each of them:
|
||||
* @code
|
||||
* process:
|
||||
* upload:
|
||||
* plugin: sub_process
|
||||
* source: upload
|
||||
* process:
|
||||
* target_id:
|
||||
* plugin: migration_lookup
|
||||
* migration: d6_file
|
||||
* source: fid
|
||||
* 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 using the migration_lookup
|
||||
* process plugin. The display and description fields will be mapped directly.
|
||||
*
|
||||
* 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
|
||||
* (
|
||||
* [format] => 1
|
||||
* [name] => Filtered HTML
|
||||
* ...
|
||||
* [filters] => Array
|
||||
* (
|
||||
* [0] => Array
|
||||
* (
|
||||
* [module] => filter
|
||||
* [delta] => 2
|
||||
* [weight] => 0
|
||||
* )
|
||||
* [1] => Array
|
||||
* (
|
||||
* [module] => filter
|
||||
* [delta] => 0
|
||||
* [weight] => 1
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* ...
|
||||
* @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
|
||||
* source: filters
|
||||
* key: "@id"
|
||||
* process:
|
||||
* id:
|
||||
* plugin: concat
|
||||
* source:
|
||||
* - module
|
||||
* - delta
|
||||
* delimiter: _
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\process\MigrationLookup
|
||||
* @see \Drupal\migrate\Plugin\migrate\process\StaticMap
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "sub_process",
|
||||
* handle_multiples = TRUE
|
||||
* )
|
||||
*/
|
||||
class SubProcess extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$return = [];
|
||||
if (is_array($value) || $value instanceof \Traversable) {
|
||||
foreach ($value as $key => $new_value) {
|
||||
$new_row = new Row($new_value, []);
|
||||
$migrate_executable->processRow($new_row, $this->configuration['process']);
|
||||
$destination = $new_row->getDestination();
|
||||
if (array_key_exists('key', $this->configuration)) {
|
||||
$key = $this->transformKey($key, $migrate_executable, $new_row);
|
||||
}
|
||||
$return[$key] = $destination;
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the process pipeline for the key to determine its dynamic name.
|
||||
*
|
||||
* @param string|int $key
|
||||
* The current key.
|
||||
* @param \Drupal\migrate\MigrateExecutableInterface $migrate_executable
|
||||
* The migrate executable helper class.
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The current row after processing.
|
||||
*
|
||||
* @return mixed
|
||||
* The transformed key.
|
||||
*/
|
||||
protected function transformKey($key, MigrateExecutableInterface $migrate_executable, Row $row) {
|
||||
$process = ['key' => $this->configuration['key']];
|
||||
$migrate_executable->processRow($row, $process, $key);
|
||||
return $row->getDestinationProperty('key');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function multiple() {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
|
@ -6,25 +6,23 @@ use Drupal\migrate\ProcessPluginBase;
|
|||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate\MigrateException;
|
||||
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 mb_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 +31,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(
|
||||
|
@ -71,7 +83,7 @@ class Substr extends ProcessPluginBase {
|
|||
}
|
||||
|
||||
// Use optional start or length to return a portion of $value.
|
||||
$new_value = Unicode::substr($value, $start, $length);
|
||||
$new_value = mb_substr($value, $start, $length);
|
||||
return $new_value;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ trait DummyQueryTrait {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
// Pass an arbritrary table name - the query should never be executed
|
||||
// Pass an arbitrary table name - the query should never be executed
|
||||
// anyway.
|
||||
$query = $this->select(uniqid(), 's')
|
||||
->range(0, 1);
|
||||
|
@ -27,7 +27,7 @@ trait DummyQueryTrait {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count() {
|
||||
public function count($refresh = FALSE) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,8 +28,8 @@ use Drupal\migrate\Plugin\MigrationInterface;
|
|||
* -
|
||||
* channel_machine_name: movies
|
||||
* channel_description: Movies
|
||||
* ids:
|
||||
* channel_machine_name:
|
||||
* ids:
|
||||
* channel_machine_name:
|
||||
* type: string
|
||||
* @endcode
|
||||
*
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,12 +3,24 @@
|
|||
namespace Drupal\migrate\Plugin\migrate\source;
|
||||
|
||||
/**
|
||||
* Source returning an empty row.
|
||||
* Source returning a row based on the constants provided.
|
||||
*
|
||||
* This is generally useful when needing to create a field using a migration..
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* plugin: empty
|
||||
* constants:
|
||||
* entity_type: user
|
||||
* field_name: image
|
||||
* @endcode
|
||||
*
|
||||
* This will return a single row containing 'entity_type' and 'field_name'
|
||||
* elements, with values of 'user' and 'image', respectively.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "empty"
|
||||
* id = "empty",
|
||||
* source_module = "migrate"
|
||||
* )
|
||||
*/
|
||||
class EmptySource extends SourcePluginBase {
|
||||
|
@ -47,7 +59,7 @@ class EmptySource extends SourcePluginBase {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count() {
|
||||
public function count($refresh = FALSE) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,49 @@ use Drupal\migrate\Plugin\MigrateSourceInterface;
|
|||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* The base class for all source plugins.
|
||||
* The base class for source plugins.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - cache_counts: (optional) If set, cache the source count.
|
||||
* - skip_count: (optional) If set, do not attempt to count the source.
|
||||
* - track_changes: (optional) If set, track changes to incoming data.
|
||||
* - high_water_property: (optional) It is an array of name & alias values
|
||||
* (optional table alias). This high_water_property is typically a timestamp
|
||||
* or serial id showing what was the last imported record. Only content with a
|
||||
* higher value will be imported.
|
||||
*
|
||||
* The high_water_property and track_changes are mutually exclusive.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* plugin: some_source_plugin_name
|
||||
* cache_counts: true
|
||||
* track_changes: true
|
||||
* @endcode
|
||||
*
|
||||
* This example uses the plugin "some_source_plugin_name" and caches the count
|
||||
* of available source records to save calculating it every time count() is
|
||||
* called. Changes to incoming data are watched (because track_changes is true),
|
||||
* which can affect the result of prepareRow().
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* plugin: some_source_plugin_name
|
||||
* skip_count: true
|
||||
* high_water_property:
|
||||
* name: changed
|
||||
* alias: n
|
||||
* @endcode
|
||||
*
|
||||
* In this example, skip_count is true which means count() will not attempt to
|
||||
* count the available source records, but just always return -1 instead. The
|
||||
* high_water_property defines which field marks the last imported row of the
|
||||
* migration. This will get converted into a SQL condition that looks like
|
||||
* 'n.changed' or 'changed' if no alias.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigratePluginManager
|
||||
* @see \Drupal\migrate\Annotation\MigrateSource
|
||||
|
@ -174,10 +216,10 @@ 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.
|
||||
*/
|
||||
protected abstract function initializeIterator();
|
||||
abstract protected function initializeIterator();
|
||||
|
||||
/**
|
||||
* Gets the module handler.
|
||||
|
@ -394,7 +436,10 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
|
|||
* Returns -1 if the source is not countable.
|
||||
*
|
||||
* @param bool $refresh
|
||||
* (optional) Whether or not to refresh the count. Defaults to FALSE.
|
||||
* (optional) Whether or not to refresh the count. Defaults to FALSE. Not
|
||||
* all implementations support the reset flag. In such instances this
|
||||
* parameter is ignored and the result of calling the method will always be
|
||||
* up to date.
|
||||
*
|
||||
* @return int
|
||||
* The count.
|
||||
|
@ -541,4 +586,17 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
|
|||
$this->saveHighWater(NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSourceModule() {
|
||||
if (!empty($this->configuration['source_module'])) {
|
||||
return $this->configuration['source_module'];
|
||||
}
|
||||
elseif (!empty($this->pluginDefinition['source_module'])) {
|
||||
return $this->pluginDefinition['source_module'];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,13 +15,53 @@ use Drupal\migrate\Plugin\RequirementsInterface;
|
|||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Sources whose data may be fetched via DBTNG.
|
||||
* Sources whose data may be fetched via a database connection.
|
||||
*
|
||||
* By default, an existing database connection with key 'migrate' and target
|
||||
* 'default' is used. These may be overridden with explicit 'key' and/or
|
||||
* 'target' configuration keys. In addition, if the configuration key 'database'
|
||||
* is present, it is used as a database connection information array to define
|
||||
* the connection.
|
||||
* Available configuration keys:
|
||||
* - database_state_key: (optional) Name of the state key which contains an
|
||||
* array with database connection information.
|
||||
* - key: (optional) The database key name. Defaults to 'migrate'.
|
||||
* - target: (optional) The database target name. Defaults to 'default'.
|
||||
* - batch_size: (optional) Number of records to fetch from the database during
|
||||
* each batch. If omitted, all records are fetched in a single query.
|
||||
* - ignore_map: (optional) Source data is joined to the map table by default to
|
||||
* improve migration performance. If set to TRUE, the map table will not be
|
||||
* joined. Using expressions in the query may result in column aliases in the
|
||||
* JOIN clause which would be invalid SQL. If you run into this, set
|
||||
* ignore_map to TRUE.
|
||||
*
|
||||
* For other optional configuration keys inherited from the parent class, refer
|
||||
* to \Drupal\migrate\Plugin\migrate\source\SourcePluginBase.
|
||||
*
|
||||
* About the source database determination:
|
||||
* - If the source plugin configuration contains 'database_state_key', its value
|
||||
* is taken as the name of a state key which contains an array with the
|
||||
* database configuration.
|
||||
* - Otherwise, if the source plugin configuration contains 'key', the database
|
||||
* configuration with that name is used.
|
||||
* - If both 'database_state_key' and 'key' are omitted in the source plugin
|
||||
* configuration, the database connection named 'migrate' is used by default.
|
||||
* - If all of the above steps fail, RequirementsException is thrown.
|
||||
*
|
||||
* Drupal Database API supports multiple database connections. The connection
|
||||
* parameters are defined in $databases array in settings.php or
|
||||
* settings.local.php. It is also possible to modify the $databases array in
|
||||
* runtime. For example, Migrate Drupal, which provides the migrations from
|
||||
* Drupal 6 / 7, asks for the source database connection parameters in the UI
|
||||
* and then adds the $databases['migrate'] connection in runtime before the
|
||||
* migrations are executed.
|
||||
*
|
||||
* As described above, the default source database is $databases['migrate']. If
|
||||
* the source plugin needs another source connection, the database connection
|
||||
* parameters should be added to the $databases array as, for instance,
|
||||
* $databases['foo']. The source plugin can then use this connection by setting
|
||||
* 'key' to 'foo' in its configuration.
|
||||
*
|
||||
* For a complete example on migrating data from an SQL source, refer to
|
||||
* https://www.drupal.org/docs/8/api/migrate-api/migrating-data-from-sql-source
|
||||
*
|
||||
* @see https://www.drupal.org/docs/8/api/database-api
|
||||
* @see \Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase
|
||||
*/
|
||||
abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPluginInterface, RequirementsInterface {
|
||||
|
||||
|
@ -101,16 +141,21 @@ abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPlugi
|
|||
*/
|
||||
public function getDatabase() {
|
||||
if (!isset($this->database)) {
|
||||
// See if the database info is in state - if not, fallback to
|
||||
// configuration.
|
||||
// Look first for an explicit state key containing the configuration.
|
||||
if (isset($this->configuration['database_state_key'])) {
|
||||
$this->database = $this->setUpDatabase($this->state->get($this->configuration['database_state_key']));
|
||||
}
|
||||
// Next, use explicit configuration in the source plugin.
|
||||
elseif (isset($this->configuration['key'])) {
|
||||
$this->database = $this->setUpDatabase($this->configuration);
|
||||
}
|
||||
// Next, try falling back to the global state key.
|
||||
elseif (($fallback_state_key = $this->state->get('migrate.fallback_state_key'))) {
|
||||
$this->database = $this->setUpDatabase($this->state->get($fallback_state_key));
|
||||
}
|
||||
// If all else fails, let setUpDatabase() fallback to the 'migrate' key.
|
||||
else {
|
||||
$this->database = $this->setUpDatabase($this->configuration);
|
||||
$this->database = $this->setUpDatabase([]);
|
||||
}
|
||||
}
|
||||
return $this->database;
|
||||
|
@ -274,11 +319,17 @@ abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPlugi
|
|||
}
|
||||
// 2. If we are using high water marks, also include rows above the mark.
|
||||
// But, include all rows if the high water mark is not set.
|
||||
if ($this->getHighWaterProperty() && ($high_water = $this->getHighWater())) {
|
||||
if ($this->getHighWaterProperty()) {
|
||||
$high_water_field = $this->getHighWaterField();
|
||||
$conditions->condition($high_water_field, $high_water, '>');
|
||||
$high_water = $this->getHighWater();
|
||||
if ($high_water) {
|
||||
$conditions->condition($high_water_field, $high_water, '>');
|
||||
$condition_added = TRUE;
|
||||
}
|
||||
// Always sort by the high water field, to ensure that the first run
|
||||
// (before we have a high water value) also has the results in a
|
||||
// consistent order.
|
||||
$this->query->orderBy($high_water_field);
|
||||
$condition_added = TRUE;
|
||||
}
|
||||
if ($condition_added) {
|
||||
$this->query->condition($conditions);
|
||||
|
@ -298,7 +349,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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -330,8 +383,8 @@ abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPlugi
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count() {
|
||||
return $this->query()->countQuery()->execute()->fetchField();
|
||||
public function count($refresh = FALSE) {
|
||||
return (int) $this->query()->countQuery()->execute()->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -77,6 +77,13 @@ class Row {
|
|||
*/
|
||||
protected $isStub = FALSE;
|
||||
|
||||
/**
|
||||
* The empty destination properties.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $emptyDestinationProperties = [];
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\Migrate\Row object.
|
||||
*
|
||||
|
@ -97,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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -229,6 +236,26 @@ class Row {
|
|||
NestedArray::unsetValue($this->destination, explode(static::PROPERTY_SEPARATOR, $property));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a destination to be empty.
|
||||
*
|
||||
* @param string $property
|
||||
* The destination property.
|
||||
*/
|
||||
public function setEmptyDestinationProperty($property) {
|
||||
$this->emptyDestinationProperties[] = $property;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the empty destination properties.
|
||||
*
|
||||
* @return array
|
||||
* An array of destination properties.
|
||||
*/
|
||||
public function getEmptyDestinationProperties() {
|
||||
return $this->emptyDestinationProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the whole destination array.
|
||||
*
|
||||
|
|
|
@ -4,5 +4,5 @@ package: Testing
|
|||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- node
|
||||
- migrate
|
||||
- drupal:node
|
||||
- drupal:migrate
|
||||
|
|
|
@ -8,7 +8,8 @@ use Drupal\migrate\Plugin\migrate\source\SourcePluginBase;
|
|||
* A simple migrate source for our tests.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "migrate_external_translated_test"
|
||||
* id = "migrate_external_translated_test",
|
||||
* source_module = "migrate_external_translated_test"
|
||||
* )
|
||||
*/
|
||||
class MigrateExternalTranslatedTestSource extends SourcePluginBase {
|
||||
|
|
|
@ -4,4 +4,4 @@ description: 'Provides test fixtures for testing high water marks.'
|
|||
package: Testing
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- migrate
|
||||
- drupal:migrate
|
||||
|
|
|
@ -4,4 +4,4 @@ description: 'Provides a database table and records for SQL import with batch te
|
|||
package: Testing
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- migrate
|
||||
- drupal:migrate
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
name: 'Migration directory test'
|
||||
type: module
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- drupal:migrate
|
|
@ -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;
|
||||
|
@ -42,7 +41,7 @@ class DownloadFunctionalTest extends BrowserTestBase {
|
|||
'uri' => [
|
||||
'plugin' => 'download',
|
||||
'source' => ['url', 'uri'],
|
||||
]
|
||||
],
|
||||
],
|
||||
'destination' => [
|
||||
'plugin' => 'entity:file',
|
||||
|
@ -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.
|
||||
|
|
|
@ -18,13 +18,14 @@ class MigrateBundleTest extends MigrateTestBase {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['taxonomy', 'text'];
|
||||
public static $modules = ['taxonomy', 'text', 'user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('taxonomy_vocabulary');
|
||||
$this->installEntitySchema('taxonomy_term');
|
||||
$this->installConfig(['taxonomy']);
|
||||
|
@ -65,7 +66,7 @@ class MigrateBundleTest extends MigrateTestBase {
|
|||
// Import and validate the term entity was created with the correct bundle.
|
||||
$term_executable = new MigrateExecutable($term_migration, $this);
|
||||
$term_executable->import();
|
||||
/** @var Term $term */
|
||||
/** @var \Drupal\taxonomy\Entity\Term $term */
|
||||
$term = Term::load(1);
|
||||
$this->assertEquals($term->bundle(), 'categories');
|
||||
}
|
||||
|
@ -103,7 +104,7 @@ class MigrateBundleTest extends MigrateTestBase {
|
|||
// Import and validate the term entities were created with the correct bundle.
|
||||
$term_executable = new MigrateExecutable($term_migration, $this);
|
||||
$term_executable->import();
|
||||
/** @var Term $term */
|
||||
/** @var \Drupal\taxonomy\Entity\Term $term */
|
||||
$term = Term::load(1);
|
||||
$this->assertEquals($term->bundle(), 'categories');
|
||||
$term = Term::load(2);
|
||||
|
@ -145,7 +146,7 @@ class MigrateBundleTest extends MigrateTestBase {
|
|||
// Import and validate the term entities were created with the correct bundle.
|
||||
$term_executable = new MigrateExecutable($term_migration, $this);
|
||||
$term_executable->import();
|
||||
/** @var Term $term */
|
||||
/** @var \Drupal\taxonomy\Entity\Term $term */
|
||||
$term = Term::load(1);
|
||||
$this->assertEquals($term->bundle(), 'categories');
|
||||
$term = Term::load(2);
|
||||
|
|
|
@ -34,7 +34,7 @@ class MigrateConfigRollbackTest extends MigrateTestBase {
|
|||
$ids = [
|
||||
'id' =>
|
||||
[
|
||||
'type' => 'string'
|
||||
'type' => 'string',
|
||||
],
|
||||
];
|
||||
$definition = [
|
||||
|
@ -100,12 +100,12 @@ class MigrateConfigRollbackTest extends MigrateTestBase {
|
|||
$ids = [
|
||||
'id' =>
|
||||
[
|
||||
'type' => 'string'
|
||||
'type' => 'string',
|
||||
],
|
||||
'language' =>
|
||||
[
|
||||
'type' => 'string'
|
||||
]
|
||||
'type' => 'string',
|
||||
],
|
||||
];
|
||||
$definition = [
|
||||
'id' => 'i18n_config',
|
||||
|
|
|
@ -2,14 +2,15 @@
|
|||
|
||||
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;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_entity_test\Entity\StringIdEntityTest;
|
||||
|
||||
/**
|
||||
* Tests the EntityContentBase destination.
|
||||
|
@ -44,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();
|
||||
|
@ -190,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);
|
||||
|
||||
|
@ -204,4 +210,95 @@ class MigrateEntityContentBaseTest extends KernelTestBase {
|
|||
$this->assertEquals(123456789012, $map_row['destid1']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests empty destinations.
|
||||
*/
|
||||
public function testEmptyDestinations() {
|
||||
$this->enableModules(['migrate_entity_test']);
|
||||
$this->installEntitySchema('migrate_string_id_entity_test');
|
||||
|
||||
$definition = [
|
||||
'source' => [
|
||||
'plugin' => 'embedded_data',
|
||||
'data_rows' => [
|
||||
['id' => 123, 'version' => 'foo'],
|
||||
// This integer needs an 'int' schema with 'big' size. If 'destid1'
|
||||
// is not correctly taking the definition from the destination entity
|
||||
// type, the import will fail with an SQL exception.
|
||||
['id' => 123456789012, 'version' => 'bar'],
|
||||
],
|
||||
'ids' => [
|
||||
'id' => ['type' => 'integer', 'size' => 'big'],
|
||||
'version' => ['type' => 'string'],
|
||||
],
|
||||
'constants' => ['null' => NULL],
|
||||
],
|
||||
'process' => [
|
||||
'id' => 'id',
|
||||
'version' => 'version',
|
||||
],
|
||||
'destination' => [
|
||||
'plugin' => 'entity:migrate_string_id_entity_test',
|
||||
],
|
||||
];
|
||||
|
||||
$migration = \Drupal::service('plugin.manager.migration')
|
||||
->createStubMigration($definition);
|
||||
$executable = new MigrateExecutable($migration);
|
||||
$executable->import();
|
||||
|
||||
/** @var \Drupal\migrate_entity_test\Entity\StringIdEntityTest $entity */
|
||||
$entity = StringIdEntityTest::load('123');
|
||||
$this->assertSame('foo', $entity->version->value);
|
||||
$entity = StringIdEntityTest::load('123456789012');
|
||||
$this->assertSame('bar', $entity->version->value);
|
||||
|
||||
// Rerun the migration forcing the version to NULL.
|
||||
$definition['process'] = [
|
||||
'id' => 'id',
|
||||
'version' => 'constants/null',
|
||||
];
|
||||
|
||||
$migration = \Drupal::service('plugin.manager.migration')
|
||||
->createStubMigration($definition);
|
||||
$executable = new MigrateExecutable($migration);
|
||||
$executable->import();
|
||||
|
||||
/** @var \Drupal\migrate_entity_test\Entity\StringIdEntityTest $entity */
|
||||
$entity = StringIdEntityTest::load('123');
|
||||
$this->assertNull($entity->version->value);
|
||||
$entity = StringIdEntityTest::load('123456789012');
|
||||
$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();
|
||||
|
|
|
@ -17,13 +17,14 @@ class MigrateRollbackEntityConfigTest extends MigrateTestBase {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['field', 'taxonomy', 'text', 'language', 'config_translation'];
|
||||
public static $modules = ['field', 'taxonomy', 'text', 'language', 'config_translation', 'user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('taxonomy_vocabulary');
|
||||
$this->installEntitySchema('taxonomy_term');
|
||||
$this->installConfig(['taxonomy']);
|
||||
|
@ -82,14 +83,14 @@ class MigrateRollbackEntityConfigTest extends MigrateTestBase {
|
|||
'name' => '1',
|
||||
'language' => 'fr',
|
||||
'property' => 'name',
|
||||
'translation' => 'fr - categories'
|
||||
'translation' => 'fr - categories',
|
||||
],
|
||||
[
|
||||
'id' => '2',
|
||||
'name' => '2',
|
||||
'language' => 'fr',
|
||||
'property' => 'name',
|
||||
'translation' => 'fr - tags'
|
||||
'translation' => 'fr - tags',
|
||||
],
|
||||
];
|
||||
$ids = [
|
||||
|
@ -105,7 +106,7 @@ class MigrateRollbackEntityConfigTest extends MigrateTestBase {
|
|||
'ids' => $ids,
|
||||
'constants' => [
|
||||
'name' => 'name',
|
||||
]
|
||||
],
|
||||
],
|
||||
'process' => [
|
||||
'vid' => 'id',
|
||||
|
|
|
@ -20,13 +20,14 @@ class MigrateRollbackTest extends MigrateTestBase {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['field', 'taxonomy', 'text'];
|
||||
public static $modules = ['field', 'taxonomy', 'text', 'user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('taxonomy_vocabulary');
|
||||
$this->installEntitySchema('taxonomy_term');
|
||||
$this->installConfig(['taxonomy']);
|
||||
|
@ -68,7 +69,7 @@ class MigrateRollbackTest extends MigrateTestBase {
|
|||
$vocabulary_executable = new MigrateExecutable($vocabulary_migration, $this);
|
||||
$vocabulary_executable->import();
|
||||
foreach ($vocabulary_data_rows as $row) {
|
||||
/** @var Vocabulary $vocabulary */
|
||||
/** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary */
|
||||
$vocabulary = Vocabulary::load($row['id']);
|
||||
$this->assertTrue($vocabulary);
|
||||
$map_row = $vocabulary_id_map->getRowBySource(['id' => $row['id']]);
|
||||
|
@ -121,7 +122,7 @@ class MigrateRollbackTest extends MigrateTestBase {
|
|||
$map_row['source_row_status'], MigrateIdMapInterface::ROLLBACK_PRESERVE);
|
||||
|
||||
foreach ($term_data_rows as $row) {
|
||||
/** @var Term $term */
|
||||
/** @var \Drupal\taxonomy\Entity\Term $term */
|
||||
$term = Term::load($row['id']);
|
||||
$this->assertTrue($term);
|
||||
$map_row = $term_id_map->getRowBySource(['id' => $row['id']]);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ abstract class MigrateSourceTestBase extends KernelTestBase {
|
|||
/**
|
||||
* The mocked migration.
|
||||
*
|
||||
* @var MigrationInterface|\Prophecy\Prophecy\ObjectProphecy
|
||||
* @var \Drupal\migrate\Plugin\MigrationInterface|\Prophecy\Prophecy\ObjectProphecy
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
|
|
|
@ -39,7 +39,9 @@ abstract class MigrateSqlSourceTestBase extends MigrateSourceTestBase {
|
|||
->createTable($table, [
|
||||
// SQLite uses loose affinity typing, so it's OK for every field to
|
||||
// be a text field.
|
||||
'fields' => array_map(function() { return ['type' => 'text']; }, $pilot),
|
||||
'fields' => array_map(function () {
|
||||
return ['type' => 'text'];
|
||||
}, $pilot),
|
||||
]);
|
||||
|
||||
$fields = array_keys($pilot);
|
||||
|
|
|
@ -230,7 +230,9 @@ abstract class MigrateTestBase extends KernelTestBase implements MigrateMessageI
|
|||
$migration = $this->getMigration($migration);
|
||||
}
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
|
||||
$destination = array_map(function() { return NULL; }, $migration->getDestinationPlugin()->getIds());
|
||||
$destination = array_map(function () {
|
||||
return NULL;
|
||||
}, $migration->getDestinationPlugin()->getIds());
|
||||
$row = new Row($row, $migration->getSourcePlugin()->getIds());
|
||||
$migration->getIdMap()->saveIdMapping($row, $destination, $status);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\migrate\Kernel;
|
||||
|
||||
use Drupal\comment\Entity\CommentType;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
|
||||
/**
|
||||
* Provides methods for testing node and comment combinations.
|
||||
*/
|
||||
trait NodeCommentCombinationTrait {
|
||||
|
||||
/**
|
||||
* Creates a node type with a corresponding comment type.
|
||||
*
|
||||
* @param string $node_type
|
||||
* The node type ID.
|
||||
* @param string $comment_type
|
||||
* (optional) The comment type ID, if not provided defaults to
|
||||
* comment_node_{type}.
|
||||
*/
|
||||
protected function createNodeCommentCombination($node_type, $comment_type = NULL) {
|
||||
if (!$comment_type) {
|
||||
$comment_type = "comment_node_$node_type";
|
||||
}
|
||||
NodeType::create([
|
||||
'type' => $node_type,
|
||||
'label' => $this->randomString(),
|
||||
])->save();
|
||||
|
||||
CommentType::create([
|
||||
'id' => $comment_type,
|
||||
'label' => $this->randomString(),
|
||||
'target_entity_type_id' => 'node',
|
||||
])->save();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\migrate\Kernel\Plugin;
|
||||
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\Tests\migrate\Kernel\MigrateTestBase;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests the EntityRevision destination plugin.
|
||||
*
|
||||
* @group migrate
|
||||
*/
|
||||
class EntityRevisionTest extends MigrateTestBase {
|
||||
|
||||
use ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'content_translation',
|
||||
'field',
|
||||
'filter',
|
||||
'language',
|
||||
'node',
|
||||
'system',
|
||||
'text',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installConfig('node');
|
||||
$this->installSchema('node', ['node_access']);
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('user');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that EntityRevision correctly handles revision translations.
|
||||
*/
|
||||
public function testRevisionTranslation() {
|
||||
ConfigurableLanguage::createFromLangcode('fr')->save();
|
||||
|
||||
/** @var \Drupal\node\NodeInterface $node */
|
||||
$node = Node::create([
|
||||
'type' => $this->createContentType()->id(),
|
||||
'title' => 'Default 1',
|
||||
]);
|
||||
$node->addTranslation('fr', [
|
||||
'title' => 'French 1',
|
||||
]);
|
||||
$node->save();
|
||||
$node->setNewRevision();
|
||||
$node->setTitle('Default 2');
|
||||
$node->getTranslation('fr')->setTitle('French 2');
|
||||
$node->save();
|
||||
|
||||
$migration = [
|
||||
'source' => [
|
||||
'plugin' => 'embedded_data',
|
||||
'data_rows' => [
|
||||
[
|
||||
'nid' => $node->id(),
|
||||
'vid' => $node->getRevisionId(),
|
||||
'langcode' => 'fr',
|
||||
'title' => 'Titre nouveau, tabarnak!',
|
||||
],
|
||||
],
|
||||
'ids' => [
|
||||
'nid' => [
|
||||
'type' => 'integer',
|
||||
],
|
||||
'vid' => [
|
||||
'type' => 'integer',
|
||||
],
|
||||
'langcode' => [
|
||||
'type' => 'string',
|
||||
],
|
||||
],
|
||||
],
|
||||
'process' => [
|
||||
'nid' => 'nid',
|
||||
'vid' => 'vid',
|
||||
'langcode' => 'langcode',
|
||||
'title' => 'title',
|
||||
],
|
||||
'destination' => [
|
||||
'plugin' => 'entity_revision:node',
|
||||
'translations' => TRUE,
|
||||
],
|
||||
];
|
||||
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
|
||||
$migration = $this->container
|
||||
->get('plugin.manager.migration')
|
||||
->createStubMigration($migration);
|
||||
|
||||
$this->executeMigration($migration);
|
||||
|
||||
// The entity_revision destination uses the revision ID and langcode as its
|
||||
// keys (the langcode is only used if the destination is configured for
|
||||
// translation), so we should be able to look up the source IDs by revision
|
||||
// ID and langcode.
|
||||
$source_ids = $migration->getIdMap()->lookupSourceID([
|
||||
'vid' => $node->getRevisionId(),
|
||||
'langcode' => 'fr',
|
||||
]);
|
||||
$this->assertNotEmpty($source_ids);
|
||||
$this->assertSame($node->id(), $source_ids['nid']);
|
||||
$this->assertSame($node->getRevisionId(), $source_ids['vid']);
|
||||
$this->assertSame('fr', $source_ids['langcode']);
|
||||
|
||||
// Confirm the french revision was used in the migration, instead of the
|
||||
// default revision.
|
||||
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
|
||||
$entity_type_manager = \Drupal::entityTypeManager();
|
||||
$revision = $entity_type_manager->getStorage('node')->loadRevision(1);
|
||||
$this->assertSame('Default 1', $revision->label());
|
||||
$this->assertSame('French 1', $revision->getTranslation('fr')->label());
|
||||
$revision = $entity_type_manager->getStorage('node')->loadRevision(2);
|
||||
$this->assertSame('Default 2', $revision->label());
|
||||
$this->assertSame('Titre nouveau, tabarnak!', $revision->getTranslation('fr')->label());
|
||||
}
|
||||
|
||||
}
|
|
@ -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'));
|
||||
}
|
||||
|
||||
}
|
|
@ -7,6 +7,7 @@ use Drupal\KernelTests\KernelTestBase;
|
|||
use Drupal\migrate\Exception\RequirementsException;
|
||||
use Drupal\migrate\Plugin\migrate\source\SqlBase;
|
||||
use Drupal\migrate\Plugin\RequirementsInterface;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceTestTrait;
|
||||
|
||||
/**
|
||||
* Tests the migration plugin manager.
|
||||
|
@ -16,6 +17,8 @@ use Drupal\migrate\Plugin\RequirementsInterface;
|
|||
*/
|
||||
class MigrationPluginListTest extends KernelTestBase {
|
||||
|
||||
use EntityReferenceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -30,6 +33,7 @@ class MigrationPluginListTest extends KernelTestBase {
|
|||
'book',
|
||||
'comment',
|
||||
'contact',
|
||||
'content_translation',
|
||||
'dblog',
|
||||
'field',
|
||||
'file',
|
||||
|
@ -41,6 +45,7 @@ class MigrationPluginListTest extends KernelTestBase {
|
|||
'menu_link_content',
|
||||
'menu_ui',
|
||||
'node',
|
||||
'options',
|
||||
'path',
|
||||
'search',
|
||||
'shortcut',
|
||||
|
@ -59,6 +64,11 @@ class MigrationPluginListTest extends KernelTestBase {
|
|||
* @covers ::getDefinitions
|
||||
*/
|
||||
public function testGetDefinitions() {
|
||||
// Create an entity reference field to make sure that migrations derived by
|
||||
// EntityReferenceTranslationDeriver do not get discovered without
|
||||
// migrate_drupal enabled.
|
||||
$this->createEntityReferenceField('user', 'user', 'field_entity_reference', 'Entity Reference', 'node');
|
||||
|
||||
// Make sure retrieving all the core migration plugins does not throw any
|
||||
// errors.
|
||||
$migration_plugins = $this->container->get('plugin.manager.migration')->getDefinitions();
|
||||
|
@ -89,7 +99,9 @@ class MigrationPluginListTest extends KernelTestBase {
|
|||
// Any database-based source plugins should fail a requirements test in the
|
||||
// absence of a source database connection (e.g., a connection with the
|
||||
// 'migrate' key).
|
||||
$source_plugins = array_map(function ($migration_plugin) { return $migration_plugin->getSourcePlugin(); }, $migration_plugins);
|
||||
$source_plugins = array_map(function ($migration_plugin) {
|
||||
return $migration_plugin->getSourcePlugin();
|
||||
}, $migration_plugins);
|
||||
foreach ($source_plugins as $id => $source_plugin) {
|
||||
if ($source_plugin instanceof RequirementsInterface) {
|
||||
try {
|
||||
|
@ -129,6 +141,11 @@ class MigrationPluginListTest extends KernelTestBase {
|
|||
$migration_plugins = $this->container->get('plugin.manager.migration')->getDefinitions();
|
||||
// All the plugins provided by core depend on migrate_drupal.
|
||||
$this->assertNotEmpty($migration_plugins);
|
||||
|
||||
// Test that migrations derived by EntityReferenceTranslationDeriver are
|
||||
// discovered now that migrate_drupal is enabled.
|
||||
$this->assertArrayHasKey('d6_entity_reference_translation:user__user', $migration_plugins);
|
||||
$this->assertArrayHasKey('d7_entity_reference_translation:user__user', $migration_plugins);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,227 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\migrate\Kernel\Plugin;
|
||||
|
||||
use Drupal\KernelTests\FileSystemModuleDiscoveryDataProviderTrait;
|
||||
use Drupal\migrate\Plugin\Exception\BadPluginDefinitionException;
|
||||
use Drupal\migrate_drupal\Plugin\MigrateFieldPluginManager;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\MigrateDrupalTestBase;
|
||||
|
||||
/**
|
||||
* Tests that modules exist for all source and destination plugins.
|
||||
*
|
||||
* @group migrate_drupal_ui
|
||||
*/
|
||||
class MigrationProvidersExistTest extends MigrateDrupalTestBase {
|
||||
|
||||
use FileSystemModuleDiscoveryDataProviderTrait;
|
||||
|
||||
/**
|
||||
* Tests that a missing source_module property raises an exception.
|
||||
*/
|
||||
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 plugins.
|
||||
*/
|
||||
public function testProvidersExist() {
|
||||
$this->enableAllModules();
|
||||
|
||||
/** @var \Drupal\migrate\Plugin\MigrateSourcePluginManager $plugin_manager */
|
||||
$plugin_manager = $this->container->get('plugin.manager.migrate.source');
|
||||
|
||||
foreach ($plugin_manager->getDefinitions() as $definition) {
|
||||
$id = $definition['id'];
|
||||
$this->assertArrayHasKey('source_module', $definition, "No source_module property in '$id'");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -3,6 +3,8 @@
|
|||
namespace Drupal\Tests\migrate\Kernel\Plugin;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateSkipRowException;
|
||||
|
||||
/**
|
||||
* Tests the migration plugin.
|
||||
|
@ -27,6 +29,17 @@ class MigrationTest extends KernelTestBase {
|
|||
$this->assertEquals([], $migration->getProcessPlugins([]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Migration::getProcessPlugins() throws an exception.
|
||||
*
|
||||
* @covers ::getProcessPlugins
|
||||
*/
|
||||
public function testGetProcessPluginsException() {
|
||||
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration([]);
|
||||
$this->setExpectedException(MigrateException::class, 'Invalid process configuration for foobar');
|
||||
$migration->getProcessPlugins(['foobar' => ['plugin' => 'get']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Migration::getMigrationDependencies()
|
||||
*
|
||||
|
@ -39,10 +52,10 @@ class MigrationTest extends KernelTestBase {
|
|||
'f1' => 'bar',
|
||||
'f2' => [
|
||||
'plugin' => 'migration',
|
||||
'migration' => 'm1'
|
||||
'migration' => 'm1',
|
||||
],
|
||||
'f3' => [
|
||||
'plugin' => 'iterator',
|
||||
'plugin' => 'sub_process',
|
||||
'process' => [
|
||||
'target_id' => [
|
||||
'plugin' => 'migration',
|
||||
|
@ -50,10 +63,32 @@ class MigrationTest extends KernelTestBase {
|
|||
],
|
||||
],
|
||||
],
|
||||
'f4' => [
|
||||
'plugin' => 'migration_lookup',
|
||||
'migration' => 'm3',
|
||||
],
|
||||
'f5' => [
|
||||
'plugin' => 'sub_process',
|
||||
'process' => [
|
||||
'target_id' => [
|
||||
'plugin' => 'migration_lookup',
|
||||
'migration' => 'm4',
|
||||
],
|
||||
],
|
||||
],
|
||||
'f6' => [
|
||||
'plugin' => 'iterator',
|
||||
'process' => [
|
||||
'target_id' => [
|
||||
'plugin' => 'migration_lookup',
|
||||
'migration' => 'm5',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$migration = $plugin_manager->createStubMigration($plugin_definition);
|
||||
$this->assertSame(['required' => [], 'optional' => ['m1', 'm2']], $migration->getMigrationDependencies());
|
||||
$this->assertSame(['required' => [], 'optional' => ['m1', 'm2', 'm3', 'm4', 'm5']], $migration->getMigrationDependencies());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -81,4 +116,15 @@ class MigrationTest extends KernelTestBase {
|
|||
$this->assertEquals(TRUE, $migration->isTrackLastImported());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Migration::getDestinationPlugin()
|
||||
*
|
||||
* @covers ::getDestinationPlugin
|
||||
*/
|
||||
public function testGetDestinationPlugin() {
|
||||
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration(['destination' => ['no_stub' => TRUE]]);
|
||||
$this->setExpectedException(MigrateSkipRowException::class, "Stub requested but not made because no_stub configuration is set.");
|
||||
$migration->getDestinationPlugin(TRUE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ class QueryBatchTest extends KernelTestBase {
|
|||
/**
|
||||
* The mocked migration.
|
||||
*
|
||||
* @var MigrationInterface|\Prophecy\Prophecy\ObjectProphecy
|
||||
* @var \Drupal\migrate\Plugin\MigrationInterface|\Prophecy\Prophecy\ObjectProphecy
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
|
|
|
@ -7,8 +7,13 @@
|
|||
|
||||
namespace Drupal\Tests\migrate\Kernel;
|
||||
|
||||
use Drupal\migrate\Plugin\migrate\source\TestSqlBase;
|
||||
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;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
|
||||
/**
|
||||
* Tests the functionality of SqlBase.
|
||||
|
@ -17,17 +22,47 @@ use Drupal\Core\Database\Database;
|
|||
*/
|
||||
class SqlBaseTest extends MigrateTestBase {
|
||||
|
||||
/**
|
||||
* The (probably mocked) migration under test.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationInterface
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->migration = $this->getMock(MigrationInterface::class);
|
||||
$this->migration->method('id')->willReturn('fubar');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests different connection types.
|
||||
*/
|
||||
public function testConnectionTypes() {
|
||||
$sql_base = new TestSqlBase();
|
||||
$sql_base = new TestSqlBase([], $this->migration);
|
||||
|
||||
// Check the default values.
|
||||
$sql_base->setConfiguration([]);
|
||||
$this->assertIdentical($sql_base->getDatabase()->getTarget(), 'default');
|
||||
$this->assertIdentical($sql_base->getDatabase()->getKey(), 'migrate');
|
||||
// Verify that falling back to the default 'migrate' connection (defined in
|
||||
// the base class) works.
|
||||
$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';
|
||||
$key = 'test_fallback_key';
|
||||
$config = ['target' => $target, 'key' => $key];
|
||||
$database_state_key = 'test_fallback_state';
|
||||
\Drupal::state()->set($database_state_key, $config);
|
||||
\Drupal::state()->set('migrate.fallback_state_key', $database_state_key);
|
||||
// Create a test connection using the default database configuration.
|
||||
Database::addConnectionInfo($key, $target, Database::getConnectionInfo('default')['default']);
|
||||
$this->assertSame($sql_base->getDatabase()->getTarget(), $target);
|
||||
$this->assertSame($sql_base->getDatabase()->getKey(), $key);
|
||||
|
||||
// Verify that setting explicit connection information overrides fallbacks.
|
||||
$target = 'test_db_target';
|
||||
$key = 'test_migrate_connection';
|
||||
$config = ['target' => $target, 'key' => $key];
|
||||
|
@ -35,11 +70,11 @@ class SqlBaseTest extends MigrateTestBase {
|
|||
Database::addConnectionInfo($key, $target, Database::getConnectionInfo('default')['default']);
|
||||
|
||||
// Validate we have injected our custom key and target.
|
||||
$this->assertIdentical($sql_base->getDatabase()->getTarget(), $target);
|
||||
$this->assertIdentical($sql_base->getDatabase()->getKey(), $key);
|
||||
$this->assertSame($sql_base->getDatabase()->getTarget(), $target);
|
||||
$this->assertSame($sql_base->getDatabase()->getKey(), $key);
|
||||
|
||||
// Now test we can have SqlBase create the connection from an info array.
|
||||
$sql_base = new TestSqlBase();
|
||||
$sql_base = new TestSqlBase([], $this->migration);
|
||||
|
||||
$target = 'test_db_target2';
|
||||
$key = 'test_migrate_connection2';
|
||||
|
@ -51,7 +86,7 @@ class SqlBaseTest extends MigrateTestBase {
|
|||
$sql_base->getDatabase();
|
||||
|
||||
// Validate the connection has been created with the right values.
|
||||
$this->assertIdentical(Database::getConnectionInfo($key)[$target], $database);
|
||||
$this->assertSame(Database::getConnectionInfo($key)[$target], $database);
|
||||
|
||||
// Now, test this all works when using state to store db info.
|
||||
$target = 'test_state_db_target';
|
||||
|
@ -63,11 +98,11 @@ class SqlBaseTest extends MigrateTestBase {
|
|||
Database::addConnectionInfo($key, $target, Database::getConnectionInfo('default')['default']);
|
||||
|
||||
// Validate we have injected our custom key and target.
|
||||
$this->assertIdentical($sql_base->getDatabase()->getTarget(), $target);
|
||||
$this->assertIdentical($sql_base->getDatabase()->getKey(), $key);
|
||||
$this->assertSame($sql_base->getDatabase()->getTarget(), $target);
|
||||
$this->assertSame($sql_base->getDatabase()->getKey(), $key);
|
||||
|
||||
// Now test we can have SqlBase create the connection from an info array.
|
||||
$sql_base = new TestSqlBase();
|
||||
$sql_base = new TestSqlBase([], $this->migration);
|
||||
|
||||
$target = 'test_state_db_target2';
|
||||
$key = 'test_state_migrate_connection2';
|
||||
|
@ -81,13 +116,68 @@ class SqlBaseTest extends MigrateTestBase {
|
|||
$sql_base->getDatabase();
|
||||
|
||||
// Validate the connection has been created with the right values.
|
||||
$this->assertIdentical(Database::getConnectionInfo($key)[$target], $database);
|
||||
$this->assertSame(Database::getConnectionInfo($key)[$target], $database);
|
||||
|
||||
// Verify that falling back to 'migrate' when the connection is not defined
|
||||
// throws a RequirementsException.
|
||||
\Drupal::state()->delete('migrate.fallback_state_key');
|
||||
$sql_base->setConfiguration([]);
|
||||
Database::renameConnection('migrate', 'fallback_connection');
|
||||
$this->setExpectedException(RequirementsException::class,
|
||||
'No database connection configured for source plugin');
|
||||
$sql_base->getDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that SqlBase respects high-water values.
|
||||
*
|
||||
* @param mixed $high_water
|
||||
* (optional) The high-water value to set.
|
||||
* @param array $query_result
|
||||
* (optional) The expected query results.
|
||||
*
|
||||
* @dataProvider highWaterDataProvider
|
||||
*/
|
||||
public function testHighWater($high_water = NULL, array $query_result = []) {
|
||||
$configuration = [
|
||||
'high_water_property' => [
|
||||
'name' => 'order',
|
||||
],
|
||||
];
|
||||
$source = new TestSqlBase($configuration, $this->migration);
|
||||
|
||||
if ($high_water) {
|
||||
$source->getHighWaterStorage()->set($this->migration->id(), $high_water);
|
||||
}
|
||||
|
||||
$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);
|
||||
$query->method('orConditionGroup')->willReturn($condition_group);
|
||||
|
||||
$source->setQuery($query);
|
||||
$source->rewind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for ::testHighWater().
|
||||
*
|
||||
* @return array
|
||||
* The scenarios to test.
|
||||
*/
|
||||
public function highWaterDataProvider() {
|
||||
return [
|
||||
'no high-water value set' => [],
|
||||
'high-water value set' => [33],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\source;
|
||||
|
||||
/**
|
||||
* A dummy source to help with testing SqlBase.
|
||||
*
|
||||
|
@ -96,10 +186,22 @@ namespace Drupal\migrate\Plugin\migrate\source;
|
|||
class TestSqlBase extends SqlBase {
|
||||
|
||||
/**
|
||||
* Overrides the constructor so we can create one easily.
|
||||
* The query to execute.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Query\SelectInterface
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->state = \Drupal::state();
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* Overrides the constructor so we can create one easily.
|
||||
*
|
||||
* @param array $configuration
|
||||
* The plugin instance configuration.
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* (optional) The migration being run.
|
||||
*/
|
||||
public function __construct(array $configuration = [], MigrationInterface $migration = NULL) {
|
||||
parent::__construct($configuration, 'sql_base', [], $migration, \Drupal::state());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -133,6 +235,25 @@ class TestSqlBase extends SqlBase {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {}
|
||||
public function query() {
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the query to execute.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Query\SelectInterface $query
|
||||
* The query to execute.
|
||||
*/
|
||||
public function setQuery(SelectInterface $query) {
|
||||
$this->query = $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHighWaterStorage() {
|
||||
return parent::getHighWaterStorage();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -52,7 +51,7 @@ class DownloadTest extends FileTestBase {
|
|||
$destination_uri = $this->createUri('another_existing_file.txt');
|
||||
|
||||
// Test non-destructive download.
|
||||
$actual_destination = $this->doTransform($destination_uri, ['rename' => TRUE]);
|
||||
$actual_destination = $this->doTransform($destination_uri, ['file_exists' => 'rename']);
|
||||
$this->assertSame('public://another_existing_file_0.txt', $actual_destination, 'Import returned a renamed destination');
|
||||
$this->assertFileExists($actual_destination, 'Downloaded file was created');
|
||||
}
|
||||
|
@ -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.
|
||||
|
|
|
@ -9,6 +9,7 @@ use Drupal\migrate\Plugin\migrate\process\FileCopy;
|
|||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Plugin\MigrateProcessInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
/**
|
||||
* Tests the file_copy process plugin.
|
||||
|
@ -50,17 +51,17 @@ class FileCopyTest extends FileTestBase {
|
|||
// Test a local to local copy.
|
||||
[
|
||||
$this->root . '/core/modules/simpletest/files/image-test.jpg',
|
||||
'public://file1.jpg'
|
||||
'public://file1.jpg',
|
||||
],
|
||||
// Test a temporary file using an absolute path.
|
||||
[
|
||||
$file_absolute,
|
||||
'temporary://test.jpg'
|
||||
'temporary://test.jpg',
|
||||
],
|
||||
// Test a temporary file using a relative path.
|
||||
[
|
||||
$file_absolute,
|
||||
'temporary://core/modules/simpletest/files/test.jpg'
|
||||
'temporary://core/modules/simpletest/files/test.jpg',
|
||||
],
|
||||
];
|
||||
foreach ($data_sets as $data) {
|
||||
|
@ -76,30 +77,52 @@ class FileCopyTest extends FileTestBase {
|
|||
|
||||
/**
|
||||
* Test successful file reuse.
|
||||
*
|
||||
* @dataProvider providerSuccessfulReuse
|
||||
*
|
||||
* @param string $source_path
|
||||
* Source path to copy from.
|
||||
* @param string $destination_path
|
||||
* The destination path to copy to.
|
||||
*/
|
||||
public function testSuccessfulReuse() {
|
||||
$source_path = $this->root . '/core/modules/simpletest/files/image-test.jpg';
|
||||
$destination_path = 'public://file1.jpg';
|
||||
$file_reuse = file_unmanaged_copy($source_path, $destination_path);
|
||||
public function testSuccessfulReuse($source_path, $destination_path) {
|
||||
$file_reuse = $this->doTransform($source_path, $destination_path);
|
||||
clearstatcache(TRUE, $destination_path);
|
||||
|
||||
$timestamp = (new \SplFileInfo($file_reuse))->getMTime();
|
||||
$this->assertInternalType('int', $timestamp);
|
||||
|
||||
// We need to make sure the modified timestamp on the file is sooner than
|
||||
// the attempted migration.
|
||||
sleep(1);
|
||||
$configuration = ['reuse' => TRUE];
|
||||
$configuration = ['file_exists' => 'use existing'];
|
||||
$this->doTransform($source_path, $destination_path, $configuration);
|
||||
clearstatcache(TRUE, $destination_path);
|
||||
$modified_timestamp = (new \SplFileInfo($destination_path))->getMTime();
|
||||
$this->assertEquals($timestamp, $modified_timestamp);
|
||||
|
||||
$configuration = ['reuse' => FALSE];
|
||||
$this->doTransform($source_path, $destination_path, $configuration);
|
||||
$this->doTransform($source_path, $destination_path);
|
||||
clearstatcache(TRUE, $destination_path);
|
||||
$modified_timestamp = (new \SplFileInfo($destination_path))->getMTime();
|
||||
$this->assertGreaterThan($timestamp, $modified_timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the source and destination path files.
|
||||
*/
|
||||
public function providerSuccessfulReuse() {
|
||||
return [
|
||||
[
|
||||
'local_source_path' => static::getDrupalRoot() . '/core/modules/simpletest/files/image-test.jpg',
|
||||
'local_destination_path' => 'public://file1.jpg',
|
||||
],
|
||||
[
|
||||
'remote_source_path' => 'https://www.drupal.org/favicon.ico',
|
||||
'remote_destination_path' => 'public://file2.jpg',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test successful moves.
|
||||
*/
|
||||
|
@ -113,17 +136,17 @@ class FileCopyTest extends FileTestBase {
|
|||
// Test a local to local copy.
|
||||
[
|
||||
$local_file,
|
||||
'public://file1.jpg'
|
||||
'public://file1.jpg',
|
||||
],
|
||||
// Test a temporary file using an absolute path.
|
||||
[
|
||||
$file_1_absolute,
|
||||
'temporary://test.jpg'
|
||||
'temporary://test.jpg',
|
||||
],
|
||||
// Test a temporary file using a relative path.
|
||||
[
|
||||
$file_2_absolute,
|
||||
'temporary://core/modules/simpletest/files/test.jpg'
|
||||
'temporary://core/modules/simpletest/files/test.jpg',
|
||||
],
|
||||
];
|
||||
foreach ($data_sets as $data) {
|
||||
|
@ -179,7 +202,7 @@ class FileCopyTest extends FileTestBase {
|
|||
$source = $this->createUri(NULL, NULL, 'temporary');
|
||||
$destination = $this->createUri('foo.txt', NULL, 'public');
|
||||
$expected_destination = 'public://foo_0.txt';
|
||||
$actual_destination = $this->doTransform($source, $destination, ['rename' => TRUE]);
|
||||
$actual_destination = $this->doTransform($source, $destination, ['file_exists' => 'rename']);
|
||||
$this->assertFileExists($expected_destination, 'File was renamed on import');
|
||||
$this->assertSame($actual_destination, $expected_destination, 'The importer returned the renamed filename.');
|
||||
}
|
||||
|
@ -222,6 +245,9 @@ class FileCopyTest extends FileTestBase {
|
|||
* The URI of the copied file.
|
||||
*/
|
||||
protected function doTransform($source_path, $destination_path, $configuration = []) {
|
||||
// Prepare a mock HTTP client.
|
||||
$this->container->set('http_client', $this->createMock(Client::class));
|
||||
|
||||
$plugin = FileCopy::create($this->container, $configuration, 'file_copy', []);
|
||||
$executable = $this->prophesize(MigrateExecutableInterface::class)->reveal();
|
||||
$row = new Row([], []);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
namespace Drupal\Tests\migrate\Unit\Event;
|
||||
|
||||
use Drupal\migrate\Event\EventBase;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\migrate\Event\EventBase
|
||||
* @group migrate
|
||||
*/
|
||||
class EventBaseTest extends \PHPUnit_Framework_TestCase {
|
||||
class EventBaseTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* Test getMigration method.
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue